1

[Attatched project file : http://forums.xamarin.com/discussion/45900/application-packagename-property-override-problem-is-this-xamarin-bug]

I tried to override Application PackageName Property. But java.lang.UnsatisfiedLinkError (Native method not found) occurred. So I search this problem in google, and I find a question about perfectly same problem(https://forums.xamarin.com/discussion/45434/application-packagename-property-override-problem). But unfortunately there was not an answer. What can I do this problem.

[Application]
public class TestApplication : Application
{
    public override string PackageName
    {
        get
        {
            return "com.fake.packagename";
        }
    }
}

error log

07-17 23:06:57.179 E/AndroidRuntime(25801): FATAL EXCEPTION: main
07-17 23:06:57.179 E/AndroidRuntime(25801): Process: TestApp.TestApp, PID: 25801
07-17 23:06:57.179 E/AndroidRuntime(25801): java.lang.UnsatisfiedLinkError: Native method not found: md5cf27010e14af20e69784a5a54418b85f.TestApplication.n_getPackageName:()Ljava/lang/String;
07-17 23:06:57.179 E/AndroidRuntime(25801): at md5cf27010e14af20e69784a5a54418b85f.TestApplication.n_getPackageName(Native Method)
07-17 23:06:57.179 E/AndroidRuntime(25801): at md5cf27010e14af20e69784a5a54418b85f.TestApplication.getPackageName(TestApplication.java:25)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.app.ActivityThread.installProvider(ActivityThread.java:4855)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.app.ActivityThread.installContentProviders(ActivityThread.java:4476)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4413)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.app.ActivityThread.access$1500(ActivityThread.java:142)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1263)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.os.Handler.dispatchMessage(Handler.java:102)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.os.Looper.loop(Looper.java:136)
07-17 23:06:57.179 E/AndroidRuntime(25801): at android.app.ActivityThread.main(ActivityThread.java:5120)
07-17 23:06:57.179 E/AndroidRuntime(25801): at java.lang.reflect.Method.invokeNative(Native Method)
07-17 23:06:57.179 E/AndroidRuntime(25801): at java.lang.reflect.Method.invoke(Method.java:515)
07-17 23:06:57.179 E/AndroidRuntime(25801): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
07-17 23:06:57.179 E/AndroidRuntime(25801): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
07-17 23:06:57.179 E/AndroidRuntime(25801): at dalvik.system.NativeStart.main(Native Method)
poupou
  • 43,413
  • 6
  • 77
  • 174
wplong11
  • 247
  • 3
  • 10

1 Answers1

3

This looks like both a limitation and a bug in MonoAndroid; the java.lang.UnsatisfiedLinkError exception occurs because the method n_getPackageName has not yet been registered with the Mono runtime and fails to resolve. The short answer is:

  • It's probably not possible to override the PackageName property for an Application because of complexities in MonoAndroids startup sequence.
  • File a bug report with Xamarin. This a bug in the startup routine for MonoAndroid (Outlined below).

Down The Rabbit Hole

Let's dig into the bridging Java code to determine why this happens. When you build a MonoAndroid application, a bunch of Java code is generated in the [ProjectName]/obj/[Configuration]/android/src directory of your project. This source code is the interop layer that allows applications built in C# to execute on an Android device via Dalvik or ART. Each class in your project that is an Android component (Application, Service, Activity, Fragment etc) will have a corresponding .java source code file generated.

This is the one for your TestApp class:

package md5cf27010e14af20e69784a5a54418b85f;

public class TestApplication
    extends mono.android.app.Application
    implements
        mono.android.IGCUserPeer
{
    static final String __md_methods;
    static {
        __md_methods = 
            "n_getPackageName:()Ljava/lang/String;:GetGetPackageNameHandler\n" +
            "";
    }

    public TestApplication () throws java.lang.Throwable
    {
        super ();
    }

    public java.lang.String getPackageName ()
    {
        return n_getPackageName ();
    }

    private native java.lang.String n_getPackageName ();

    public void onCreate ()
    {
        mono.android.Runtime.register ("TestApp.TestApplication, TestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", TestApplication.class, __md_methods);
        super.onCreate ();
    }

    java.util.ArrayList refList;
    public void monodroidAddReference (java.lang.Object obj)
    {
        if (refList == null)
            refList = new java.util.ArrayList ();
        refList.add (obj);
    }

    public void monodroidClearReferences ()
    {
        if (refList != null)
            refList.clear ();
    }
}

The important things to note here are:

  • The definition of overridden methods that link into your .NET assembly:

    static final String __md_methods;
    static {
        __md_methods = 
            "n_getPackageName:()Ljava/lang/String;:GetGetPackageNameHandler\n" +
            "";
    }
    
  • The linkup code for your PackageName property.

    public java.lang.String getPackageName ()
    {
        return n_getPackageName ();
    }
    
    private native java.lang.String n_getPackageName ();
    
  • And the registration code:

    public void onCreate ()
    {
        mono.android.Runtime.register ("TestApp.TestApplication, TestApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", TestApplication.class, __md_methods);
        super.onCreate ();
    }
    

Now, the most probable reason it crashes upon the n_getPackageName() invocation is because an android.app.Applications getPackageName() method is called before it's onCreate invocation.

If then you dissect MonoPackageManager and MonoRuntimeProvider, you find the cause of the issue:

MonoRuntimeProvider.java

@Override
public void attachInfo (android.content.Context context, android.content.pm.ProviderInfo info)
{
    // Mono Runtime Initialization {{{
    android.content.pm.ApplicationInfo apiInfo = null;

    String platformPackage  = mono.MonoPackageManager.getApiPackageName ();
    if (platformPackage != null) {
        Throwable t = null;
        try {
            apiInfo = context.getPackageManager ().getApplicationInfo (platformPackage, 0);
        } catch (android.content.pm.PackageManager.NameNotFoundException e) {
            // ignore
        }
        if (apiInfo == null) {
            try {
                apiInfo = context.getPackageManager ().getApplicationInfo ("Xamarin.Android.Platform", 0);
            } catch (android.content.pm.PackageManager.NameNotFoundException e) {
                t = e;
            }
        }
        if (apiInfo == null)
            throw new RuntimeException ("Unable to find application " + platformPackage + " or Xamarin.Android.Platform!", t);
    }
    try {
        android.content.pm.ApplicationInfo runtimeInfo = context.getPackageManager ().getApplicationInfo ("Mono.Android.DebugRuntime", 0);
        mono.MonoPackageManager.LoadApplication (context, runtimeInfo.dataDir,
                apiInfo != null
                ? new String[]{runtimeInfo.sourceDir, apiInfo.sourceDir, context.getApplicationInfo ().sourceDir}
                : new String[]{runtimeInfo.sourceDir, context.getApplicationInfo ().sourceDir});
    } catch (android.content.pm.PackageManager.NameNotFoundException e) {
        throw new RuntimeException ("Unable to find application Mono.Android.DebugRuntime!", e);
    }
    // }}}
    super.attachInfo (context, info);
}

MonoPackageManager.java

// ...
public static void LoadApplication (Context context, String runtimeDataDir, String[] apks)
{
    synchronized (lock) {
        if (!initialized) {
            System.loadLibrary("monodroid");
            Locale locale       = Locale.getDefault ();
            String language     = locale.getLanguage () + "-" + locale.getCountry ();
            String filesDir     = context.getFilesDir ().getAbsolutePath ();
            String cacheDir     = context.getCacheDir ().getAbsolutePath ();
            String dataDir      = context.getApplicationInfo ().dataDir + "/lib";
            ClassLoader loader  = context.getClassLoader ();

            Runtime.init (
                    language,
                    apks,
                    runtimeDataDir,
                    new String[]{
                        filesDir,
                        cacheDir,
                        dataDir,
                    },
                    loader,
                    new java.io.File (
                        android.os.Environment.getExternalStorageDirectory (),
                        "Android/data/" + context.getPackageName () + "/files/.__override__").getAbsolutePath (),
                    MonoPackageManager_Resources.Assemblies,
                    context.getPackageName ());
            initialized = true;
        }
    }
}
// ...

The cause of the crash becomes clear when you walk through the startup routine:

  • Android loads the MonoRuntimeProvider class and executes attachInfo.
  • attachInfo invokes LoadApplication.
  • LoadApplication calls uses the Application contexts getPackageName() method.
  • getPackageName() calls through to the n_getPackageName() method.

According to the docs for android.app.Application:

public void onCreate ()

Called when the application is starting, before any activity, service, or receiver objects (excluding content providers) have been created. Implementations should be as quick as possible (for example using lazy initialization of state) since the time spent in this function directly impacts the performance of starting the first activity, service, or receiver in a process. If you override this method, be sure to call super.onCreate().

Because an Applications onCreate is called after a ContentProviders onCreate, the native method won't have been bound by Mono and therefore can't be resolved! So there is the root cause of the java.lang.UnsatisfiedLinkError exception.

To summarise, it's both a bug and a startup complexity.

matthewrdev
  • 11,930
  • 5
  • 52
  • 64
  • In Activity case, registration code is called at static constructor and inherited PackageName property works well. If i modify apk file to call an registration code at static constructor like Activity, then there is no error? – wplong11 Jul 17 '15 at 07:37
  • No worries, I like solving a good problem; this one was a lot more complex than I originally thought ;) – matthewrdev Jul 17 '15 at 07:43
  • Don't modify the apk; that's generally not good practice. Why do you need to change the return value of `PackageName`? – matthewrdev Jul 17 '15 at 07:44
  • 1
    I want like this. http://stackoverflow.com/questions/19720059 The answer was overriding getPackageName in Application. It works well in Java as i wanted. – wplong11 Jul 17 '15 at 12:12