PackageName 属性 覆盖 Xamarin 中的错误

PackageName property override bug in Xamarin

[附加的项目文件:http://forums.xamarin.com/discussion/45900/application-packagename-property-override-problem-is-this-xamarin-bug]

我试图覆盖 Application PackageName 属性。但是 java.lang.UnsatisfiedLinkError (Native method not found) 发生了。所以我在 google 中搜索这个问题,我发现了一个关于完全相同问题的问题 (https://forums.xamarin.com/discussion/45434/application-packagename-property-override-problem)。但不幸的是没有答案。这个问题我该怎么办

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

错误日志

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.access00(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)

这看起来像是 Mono 中的一个限制和一个错误Android; java.lang.UnsatisfiedLinkError 异常发生是因为方法 n_getPackageName 尚未注册到 Mono 运行时并且无法解析。简短的回答是:

  • 可能无法覆盖 ApplicationPackageName 属性,因为 MonoAndroid 的启动顺序很复杂。
  • 使用 Xamarin 提交错误报告。这是 MonoAndroid 启动例程中的错误(如下所述)。

掉进兔子洞

让我们深入研究桥接 Java 代码以确定发生这种情况的原因。当您构建 MonoAndroid 应用程序时,会在您项目的 [ProjectName]/obj/[Configuration]/android/src 目录中生成一堆 Java 代码。此源代码是互操作层,它允许使用 C# 构建的应用程序通过 Dalvik 或 ART 在 Android 设备上执行。您项目中的每个 class 作为 Android 组件(ApplicationServiceActivityFragment 等)都会有一个对应的 .java源代码文件生成。

这是给你的 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 ();
    }
}

这里需要注意的重要事项是:

  • link 到您的 .NET 程序集中的覆盖方法的定义:

    static final String __md_methods;
    static {
        __md_methods = 
            "n_getPackageName:()Ljava/lang/String;:GetGetPackageNameHandler\n" +
            "";
    }
    
  • link您的PackageName属性的up代码。

    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 ();
    }
    

现在,它在 n_getPackageName() 调用时崩溃的最可能原因是因为 android.app.Applications getPackageName() 方法在 之前被调用 它是onCreate 调用。

如果你剖析MonoPackageManagerMonoRuntimeProvider,你会发现问题的原因:

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;
        }
    }
}
// ...

当您完成启动例程时,崩溃的原因就很清楚了:

  • Android 加载 MonoRuntimeProvider class 并执行 attachInfo.
  • attachInfo 调用 LoadApplication.
  • LoadApplication 调用使用应用程序上下文 getPackageName() 方法。
  • getPackageName() 调用 n_getPackageName() 方法。

根据 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().

因为Applications onCreate 在ContentProviders onCreate 之后调用,native 方法不会被Mono 绑定,因此无法解析!所以有java.lang.UnsatisfiedLinkError异常的根本原因。

总而言之,这既是错误又是启动复杂性。