体系结构
Xamarin.Android 应用程序在 Mono 执行环境中运行。 此执行环境与 Android 运行时 (ART) 虚拟机并行运行。 这两个运行时环境都在 Linux 内核上运行,并向允许用户访问基础系统的用户代码公开各种 API。 Mono 运行时是用 C 语言编写的。
可以使用系统、System.IO、System.Net 和 .NET 类库的其余部分来访问基础 Linux 操作系统设施。
在 Android 上,大多数系统设施(如音频、图形、OpenGL 和电话)无法直接提供给本机应用程序,它们只能通过驻留在 Java.* 命名空间中的 Android 运行时 Java API 或 Android.* 命名空间中公开。 体系结构大致如下所示:
Xamarin.Android 开发人员通过调用已知 .NET API(用于低级别访问)或使用 Android 命名空间中公开的类来访问操作系统中的各种功能,从而为 Android 运行时公开的 Java API 提供桥梁。
有关 Android 类如何与 Android 运行时类通信的详细信息,请参阅 API 设计文档。
应用程序包
Android 应用程序包是具有 .apk 文件扩展名的 ZIP 容器。 Xamarin.Android 应用程序包的结构和布局与普通 Android 包相同,新增了以下内容:
应用程序程序集(包含 IL)以未压缩的形式存储在程序集文件夹中。 在 Release 中的进程启动过程中,.apk mmap() 进入进程,并从内存中加载程序集。 这允许更快的应用启动,因为在执行之前不需要提取程序集。
注意:不能依赖于发布版本中的程序集位置信息(如 Assembly.Location 和 Assembly.CodeBase)。 它们不存在为不同的文件系统条目,并且没有可用位置。
包含 Mono 运行时的本机库存在于 .apk 中。 Xamarin.Android 应用程序必须包含适用于所需/目标 Android 体系结构的本机库,例如 armeabi、armeabi-v7a、x86。 除非它包含适当的运行时库,否则 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.DoNotGenerateAcw 为 false,这是默认值。)
与托管可调用包装器一样,托管可调用包装器子类还包含全局引用,可通过 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) 构造函数:
Android.App.Application 已子类化。 应用程序很特别;默认 Applicaton 构造函数永远不会被调用,必须改为提供 (IntPtr, JniHandleOwnership) 构造函数。
从基类构造函数调用虚拟方法。
请注意,(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) 构造函数。
事件的顺序:
布局 XML 加载到 ContentView中。
Android 实例化 Layout 对象图,并实例化 monodroid.apidemo.LogTextBox(适用于 LogTextBox 的 ACW)的实例。
monodroid.apidemo.LogTextBox 构造函数执行 android.widget.TextView 构造函数。
TextView 构造函数调用 monodroid.apidemo.LogTextBox.getDefaultMovementMethod()。
monodroid.apidemo.LogTextBox.getDefaultMovementMethod() 调用 LogTextBox.n_getDefaultMovementMethod(),调用 TextView.n_GetDefaultMovementMethod(),调用 Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer)。
Java.Lang.Object.GetObject<TextView>() 检查,以查看是否有相应的 C# 实例用于句柄。 如果有,则返回它。 在此情形中,不存在相应的实例,因此 Object.GetObject<T>() 必须创建一个。
Object.GetObject<T>() 查找 LogTextBox(IntPtr, JniHandleOwneship) 构造函数,调用它,在句柄和创建的实例之间创建映射,并返回创建的实例。
TextView.n_GetDefaultMovementMethod() 调用 LogTextBox.DefaultMovementMethod 属性 Getter。
控件返回到 android.widget.TextView 构造函数,该构造函数完成执行。
monodroid.apidemo.LogTextBox 构造函数执行,调用 TypeManager.Activate()。
LogTextBox (Context, IAttributeSet, int) 构造函数在 (7) 中创建的同一实例上执行。
如果找不到 (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),然后就搞定了。