体系结构

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

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

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

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

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

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

应用程序包

Android 应用程序包是文件扩展名 为 .apk 的 ZIP 容器。 Xamarin.Android 应用程序包的结构和布局与普通 Android 包相同,新增功能如下:

  • 包含 IL) 的应用程序程序集 (未压缩地存储在程序集文件夹中。 在发布中的进程启动期间, 生成 .apkmmap () 到进程中,并从内存加载程序集。 这允许更快的应用启动,因为不需要在执行之前提取程序集。

  • 注意: 在发布版本中,程序集位置信息(如 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 类型。 (具体而言,这些是不包含RegisterAttribute 自定义属性或 RegisterAttribute.DoNotGenerateAcw为 false 的任何 Java.Lang.Object 子类,即 default.)

与托管可调用包装器一样,托管可调用包装器子类也包含可通过 Java.Lang.Object.Handle 属性访问的全局引用。 与托管可调用包装一样,可以通过调用 Java.Lang.Object.Dispose () 显式释放全局引用。 与托管可调用包装器不同,在释放此类实例之前应 格外小心 ,因为实例的 Dispose () -ing 会中断 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.LogTextBoxLogTextBox 的 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> (句柄 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);
    }
}

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

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) 方法。 Xamarin.Android 通过添加单声道来挂钩到此 。MonoRuntimeProviderContentProvider 在生成过程中AndroidManifest.xml。 单声道。MonoRuntimeProvider.attachInfo () 方法负责将 Mono 运行时加载到进程中。 在此之前使用 Mono 的任何尝试都将失败。 ( 注意:这就是为什么键入哪些子类 Android.App.Application 需要提供 (IntPtr、JniHandleOwnership) 构造函数,因为应用程序实例是在 Mono 初始化之前创建的。)

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