体系结构

Xamarin.Android 应用程序在 Mono 执行环境中运行。 此执行环境与 Android 运行时 (ART) 虚拟机并行运行。 这两个运行时环境都在 Linux 内核上运行,并向允许用户访问基础系统的用户代码公开各种 API。 Mono 运行时是用 C 语言编写的。

可以使用系统System.IOSystem.Net 和 .NET 类库的其余部分来访问基础 Linux 操作系统设施。

在 Android 上,大多数系统设施(如音频、图形、OpenGL 和电话)无法直接提供给本机应用程序,它们只能通过驻留在 Java.* 命名空间中的 Android 运行时 Java API 或 Android.* 命名空间中公开。 体系结构大致如下所示:

内核上方和 .NET/Java + 绑定上方的 Mono 和 ART 示意图

Xamarin.Android 开发人员通过调用已知 .NET API(用于低级别访问)或使用 Android 命名空间中公开的类来访问操作系统中的各种功能,从而为 Android 运行时公开的 Java API 提供桥梁。

有关 Android 类如何与 Android 运行时类通信的详细信息,请参阅 API 设计文档。

应用程序包

Android 应用程序包是具有 .apk 文件扩展名的 ZIP 容器。 Xamarin.Android 应用程序包的结构和布局与普通 Android 包相同,新增了以下内容:

  • 应用程序程序集(包含 IL)以未压缩的形式存储程序集文件夹中。 在 Release 中的进程启动过程中,.apk mmap() 进入进程,并从内存中加载程序集。 这允许更快的应用启动,因为在执行之前不需要提取程序集。

  • 注意:不能依赖于发布版本中的程序集位置信息(如 Assembly.LocationAssembly.CodeBase)。 它们不存在为不同的文件系统条目,并且没有可用位置。

  • 包含 Mono 运行时的本机库存在于 .apk 中。 Xamarin.Android 应用程序必须包含适用于所需/目标 Android 体系结构的本机库,例如 armeabiarmeabi-v7ax86。 除非它包含适当的运行时库,否则 Xamarin.Android 应用程序无法在平台上运行。

Xamarin.Android 应用程序还包含 Android 可调用包装器,以允许 Android 调用托管代码。

Android 可调用包装器

  • Android 可调用包装器是一个 JNI 桥,在 Android 运行时需要调用托管代码时使用。 Android 可调用包装器是如何重写虚拟方法,并且可以实现 Java 接口。 有关详细信息,请参阅 Java 集成概述文档。

托管可调用包装器

托管可调用包装器是一个 JNI 桥,用于随时使用托管代码来调用 Android 代码,并支持重写虚拟方法和实现 Java 接口。 整个 Android.* 和相关命名空间都是通过 .jar绑定生成的托管可调用包装器。 托管可调用包装器负责在托管和 Android 类型之间转换,并通过 JNI 调用基础 Android 平台方法。

每个创建的托管可调用包装器都包含一个 Java 全局引用,可通过 Android.Runtime.IJavaObject.Handle 属性进行访问。 全局引用用于提供 Java 实例和托管实例之间的映射。 全局引用是有限的资源:仿真器一次只允许存在 2000 个全局引用,而大多数硬件允许一次存在超过 52,000 个全局引用。

若要跟踪何时创建和销毁全局引用,可以将 debug.mono.log 系统属性设置为包含 gref

可以通过对托管可调用包装器调用 Java.Lang.Object.Dispose() 显式释放全局引用。 这将移除 Java 实例和托管实例之间的映射,并允许收集 Java 实例。 如果从托管代码重新访问 Java 实例,则会为其创建新的托管可调用包装器。

如果实例可在线程之间意外共享,则处理托管可调用包装器时必须谨慎,因为释放实例会影响来自任何其他线程的引用。 为了获得最大安全性,只有通过Dispose() new 从已知始终分配新实例的方法分配的实例,而不是缓存实例,这可能会导致线程之间意外共享实例。

托管可调用包装器子类

托管可调用包装器子类是所有特定于应用程序的“有趣”逻辑可能都位于其中。 其中包括自定义 Android.App.Activity 子类(例如默认项目模板中的 Activity1 类型)。 (具体而言,这些是任何 Java.Lang.Object 子类, 包含 RegisterAttribute 自定义属性,或 RegisterAttribute.DoNotGenerateAcwfalse,这是默认值。)

与托管可调用包装器一样,托管可调用包装器子类还包含全局引用,可通过 Java.Lang.Object.Handle 属性进行访问。 与托管可调用包装器一样,可以通过调用 Java.Lang.Object.Dispose () 显式释放全局引用。 与托管可调用包装器不同,在处理此类实例之前,应非常小心,例如 Dispose() 实例会破坏 Java 实例(Android 可调用包装实例)与托管实例之间的映射。

Java 激活

从 Java 创建 Android 可调用包装器 (ACW) 时,ACW 构造函数将导致调用相应的 C# 构造函数。 例如,MainActivity 的 ACW 将包含一个默认构造函数,该构造函数将调用 MainActivity 的默认构造函数。 (这是通过 ACW 构造函数中的 TypeManager.Activate () 调用完成的。)

另外还有一个构造函数签名:(IntPtr, JniHandleOwnership) 构造函数。 每当向托管代码公开 Java 对象并且需要构造托管可调用包装器来管理 JNI 句柄时,都会调用 (IntPtr, JniHandleOwnership) 构造函数。 这通常是自动完成的。

在以下两种情况下,必须在托管可调用包装器子类上手动提供 (IntPtr, JniHandleOwnership) 构造函数:

  1. Android.App.Application 已子类化。 应用程序很特别;默认 Applicaton 构造函数永远不会被调用,必须改为提供 (IntPtr, JniHandleOwnership) 构造函数

  2. 从基类构造函数调用虚拟方法。

请注意,(2) 是一个泄漏的抽象。 在 Java 中,与 C# 一样,从构造函数调用虚拟方法始终调用最派生的方法实现。 例如,TextView (Context, AttributeSet, int) 构造函数调用虚拟方法 TextView.getDefaultMovementMethod(),且绑定为 TextView.DefaultMovementMethod 属性。 因此,如果 LogTextBox 的类型是 (1) 子类 TextView,(2) 替代 TextView.DefaultMovementMethod,并且 (3) 通过 XML 激活该类的实例,且会在 ACW 构造函数有机会执行之前调用被替代的 DefaultMovementMethod 属性,并在 C# 构造函数有机会执行之前发生。

在 ACW LogTextBox 实例首次输入托管代码时,通过 LogTextView (IntPtr, JniHandleOwnership) 构造函数实例化 LogTextBox 示例,然后在 ACW 构造函数执行时,在同一实例上调用 LogTextBox(Context, IAttributeSet, int) 构造函数。

事件的顺序:

  1. 布局 XML 加载到 ContentView中。

  2. Android 实例化 Layout 对象图,并实例化 monodroid.apidemo.LogTextBox(适用于 LogTextBox 的 ACW)的实例。

  3. monodroid.apidemo.LogTextBox 构造函数执行 android.widget.TextView 构造函数。

  4. TextView 构造函数调用 monodroid.apidemo.LogTextBox.getDefaultMovementMethod()

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() 调用 LogTextBox.n_getDefaultMovementMethod(),调用 TextView.n_GetDefaultMovementMethod(),调用 Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer)

  6. Java.Lang.Object.GetObject<TextView>() 检查,以查看是否有相应的 C# 实例用于句柄。 如果有,则返回它。 在此情形中,不存在相应的实例,因此 Object.GetObject<T>() 必须创建一个。

  7. Object.GetObject<T>() 查找 LogTextBox(IntPtr, JniHandleOwneship) 构造函数,调用它,在句柄和创建的实例之间创建映射,并返回创建的实例。

  8. TextView.n_GetDefaultMovementMethod() 调用 LogTextBox.DefaultMovementMethod 属性 Getter。

  9. 控件返回到 android.widget.TextView 构造函数,该构造函数完成执行。

  10. monodroid.apidemo.LogTextBox 构造函数执行,调用 TypeManager.Activate()

  11. LogTextBox (Context, IAttributeSet, int) 构造函数在 (7) 中创建的同一实例上执行。

  12. 如果找不到 (IntPtr, JniHandleOwnership) 构造函数,则将引发 System.MissingMethodException](xref:System.MissingMethodException)。

过早释放() 调用

JNI 句柄与相应的 C# 实例之间存在映射。 Java.Lang.Object.Dispose() 中断此映射。 如果 JNI 句柄在映射中断后进入托管代码,则看起来类似于 Java 激活,并且将检查和调用 (IntPtr、JniHandleOwnership) 构造函数。 如果构造函数不存在,则会引发异常。

例如,给定以下托管可调用包装器子类:

class ManagedValue : Java.Lang.Object {

    public string Value {get; private set;}

    public ManagedValue (string value)
    {
        Value = value;
    }

    public override string ToString ()
    {
        return string.Format ("[Managed: Value={0}]", Value);
    }
}

如果我们创建了一个实例,用 Dispose() 释放它,并导致重新创建托管可调用包装器:

var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());

程序将死亡:

E/mono    ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   --- End of inner exception stack trace ---
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000

如果子类包含 (IntPtr, JniHandleOwnership) 构造函数,则将创建该类型的实例。 因此,实例将显示为“丢失”所有实例数据,因为它是一个新实例。 (请注意,该值为 null。)

I/mono-stdout( 2993): [Managed: Value=]

只有在知道 Java 对象将不再被使用,或者子类不包含实例数据且已提供 (IntPtr, JniHandleOwnership) 构造函数的情况下,才能对托管可调用封装子类进行 Dispose() 处理。

应用程序启动

启动活动、服务等时,Android 将首先检查是否有进程正在运行来托管活动/服务/等。如果不存在此类进程,则会创建新进程、读取 AndroidManifest.xml,且在 /manifest/application/@android:name 属性中指定的类型会加载和实例化。 接下来,实例化 /manifest/application/provider/@android:name 属性值指定的所有类型,并调用其 ContentProvider.attachInfo%28) 方法。 通过添加 mono 将 Xamarin.Android 挂钩到此中。MonoRuntimeProvider ContentProvider 在生成过程中AndroidManifest.xml。 mono.MonoRuntimeProvider.attachInfo() 方法负责将 Mono 运行时加载到进程中。 在此点之前使用 Mono 的任何尝试都将失败。 (:这就是为什么子类 Android.App.Application 的类型需要提供 (IntPtr, JniHandleOwnership) 构造函数,因为 Application 实例是在 Mono 初始化之前创建的。)

进程初始化完成后,将咨询 AndroidManifest.xml 以查找要启动的活动/服务/等的类名。 例如,/manifest/application/activity/@android:name 属性用于确定要加载的活动的名称。 对于活动,此类型必须继承 android.app.Activity。 指定的类型通过 Class.forName() 加载(这要求类型为 Java 类型,因此为 Android 可调用包装器),然后实例化。 创建 Android 可调用包装器实例将触发创建相应 C# 类型的实例。 然后,Android 将调用 Activity.onCreate(Bundle),这将导致调用相应的 Activity.OnCreate(Bundle),然后就搞定了。