使用 JNIWorking With JNI

Xamarin.Android 允许编写 Android 应用中的C#而不是 Java。多个程序集提供使用 Xamarin.Android 提供的 Java 库,包括 Mono.Android.dll 和 Mono.Android.GoogleMaps.dll 绑定。但是,对于每个可能的 Java 库,不提供了绑定,并且提供的绑定可能不会绑定每个 Java 类型和成员。若要使用未绑定的 Java 类型和成员,可以使用 Java 本机接口 (JNI)。本文将演示如何使用 JNI 与 Java 类型和成员从 Xamarin.Android 应用程序进行交互。Xamarin.Android permits writing Android apps within C# instead of Java. Several assemblies are provided with Xamarin.Android which provide bindings for Java libraries, including Mono.Android.dll and Mono.Android.GoogleMaps.dll. However, bindings are not provided for every possible Java library, and the bindings that are provided may not bind every Java type and member. To use unbound Java types and members, the Java Native Interface (JNI) may be used. This article illustrates how to use JNI to interact with Java types and members from Xamarin.Android applications.

概述Overview

它并不总是需要或不能创建托管可调用包装 (MCW) 来调用 Java 代码。It is not always necessary or possible to create a Managed Callable Wrapper (MCW) to invoke Java code. 在许多情况下,"内联"JNI 是完全可接受和用于一次性使用未绑定 Java 成员。In many cases, "inline" JNI is perfectly acceptable and useful for one-off use of unbound Java members. 通常是使用 JNI 调用上比以生成整个.jar 绑定一个 Java 类的单个方法简单得多。It is often simpler to use JNI to invoke a single method on a Java class than to generate an entire .jar binding.

Xamarin.Android 提供了Mono.Android.dll程序集,提供适用于 Android 的绑定android.jar库。Xamarin.Android provides the Mono.Android.dll assembly, which provides a binding for Android's android.jar library. 类型和成员中不存在Mono.Android.dll中不存在类型和android.jar可能由手动将其绑定。Types and members not present within Mono.Android.dll and types not present within android.jar may be used by manually binding them. 若要将绑定 Java 类型和成员,请使用Java 本机接口(JNI) 若要查找类型、 读写字段,并调用方法。To bind Java types and members, you use the Java Native Interface (JNI) to lookup types, read and write fields, and invoke methods.

在 Xamarin.Android 中的 JNI API 是从概念上讲非常类似于System.Reflection在.NET 中的 API: 它可以为您要查找类型和成员名称,通过读取和写入字段值,调用方法和的详细信息。The JNI API in Xamarin.Android is conceptually very similar to the System.Reflection API in .NET: it makes it possible for you to look up types and members by name, read and write field values, invoke methods, and more. 可以使用 JNI 和Android.Runtime.RegisterAttribute自定义特性来声明可以绑定到支持重写的虚方法。You can use JNI and the Android.Runtime.RegisterAttribute custom attribute to declare virtual methods that can be bound to support overriding. 可以将绑定接口,以便它们可以实现在C#。You can bind interfaces so that they can be implemented in C#.

本文档说明:This document explains:

  • 如何 JNI 引用类型。How JNI refers to types.
  • 如何查找、 读取和写入的字段。How to lookup, read, and write fields.
  • 如何查找和调用方法。How to lookup and invoke methods.
  • 如何公开虚方法来从托管代码重写。How to expose virtual methods to allow overriding from managed code.
  • 如何公开接口。How to expose interfaces.

要求Requirements

JNI,如通过公开Android.Runtime.JNIEnv 命名空间,可在每个版本的 Xamarin.Android。JNI, as exposed through the Android.Runtime.JNIEnv namespace, is available in every version of Xamarin.Android. 若要将绑定 Java 类型和接口,必须使用 Xamarin.Android 4.0 或更高版本。To bind Java types and interfaces, you must use Xamarin.Android 4.0 or later.

托管的可调用包装器Managed Callable Wrappers

一个托管可调用包装器(MCW) 是绑定Java 类或接口,从而将所有的 JNI 机制因此该客户端C#无需代码担心的 JNI 底层的复杂性。A Managed Callable Wrapper (MCW) is a binding for a Java class or interface which wraps up the all the JNI machinery so that client C# code doesn't need to worry about the underlying complexity of JNI. 大多数的Mono.Android.dll包含托管的可调用包装器。Most of Mono.Android.dll consists of managed callable wrappers.

托管的可调用包装器有两种用途:Managed callable wrappers serve two purposes:

  1. 封装 JNI 使用,从而使客户端代码不需要了解的有关底层的复杂性。Encapsulate JNI use so that client code doesn't need to know about the underlying complexity.
  2. 让可以子类 Java 类型和实现 Java 接口。Make it possible to sub-class Java types and implement Java interfaces.

第一个用途是纯粹是为了方便使用和复杂程度的封装,以便使用者具有一套简单、 托管的要使用的类。The first purpose is purely for convenience and encapsulation of complexity so that consumers have a simple, managed set of classes to use. 这需要使用的各种JNIEnv成员在本文后面部分所述。This requires use of the various JNIEnv members as described later in this article. 请记住,管理可调用包装器并不是必需–"内联"JNI 使用是完全可以接受,并可用于一次性使用未绑定 Java 成员。Keep in mind that managed callable wrappers aren't strictly necessary – "inline" JNI use is perfectly acceptable and is useful for one-off use of unbound Java members. 子类和接口的实现需要使用托管的可调用包装器。Sub-classing and interface implementation requires the use of managed callable wrappers.

Android 可调用包装器Android Callable Wrappers

Android 可调用包装器 (ACW) 是必需的每当需要调用托管的代码; Android 运行时 (图片)需要这些包装器,因为没有方法在运行时与艺术注册类。Android callable wrappers (ACW) are required whenever the Android runtime (ART) needs to invoke managed code; these wrappers are required because there is no way to register classes with ART at runtime. (具体而言, DefineClass JNI 函数不受 Android 运行时。(Specifically, the DefineClass JNI function is not supported by the Android runtime. Android 可调用包装器因此弥补缺少的运行时类型注册支持。)Android callable wrappers thus make up for the lack of runtime type registration support.)

每当 Android 代码需要执行的虚拟或接口方法重写或实现是在托管代码中,Xamarin.Android 必须提供 Java 代理,以便此方法获取调度到相应的托管类型。Whenever Android code needs to execute a virtual or interface method that is overridden or implemented in managed code, Xamarin.Android must provide a Java proxy so that this method gets dispatched to the appropriate managed type. 这些 Java 代理类型是具有与托管类型实现相同的构造函数并声明任何重写的基类和接口方法"相同"的基类和 Java 接口列表的 Java 代码。These Java proxy types are Java code that have the "same" base class and Java interface list as the managed type, implementing the same constructors and declaring any overridden base class and interface methods.

Android 可调用包装器生成的monodroid.exe程序期间生成过程,并为 (直接或间接) 继承的所有类型生成Java.Lang.ObjectAndroid callable wrappers are generated by the monodroid.exe program during the build process, and are generated for all types that (directly or indirectly) inherit Java.Lang.Object.

实现接口Implementing Interfaces

有时您可能需要实现一个 Android 接口 (如Android.Content.IComponentCallbacks)。There are times when you may need to implement an Android interface, (such as Android.Content.IComponentCallbacks).

所有 Android 类和接口扩展Android.Runtime.IJavaObject接口; 因此,所有 Android 类型必须实现IJavaObjectAll Android classes and interfaces extend the Android.Runtime.IJavaObject interface; therefore, all Android types must implement IJavaObject. Xamarin.Android 利用这一事实–它使用IJavaObject,让 Android Java 代理 (Android 可调用包装) 针对给定托管类型。Xamarin.Android takes advantage of this fact – it uses IJavaObject to provide Android with a Java proxy (an Android callable wrapper) for the given managed type. 因为monodroid.exe只寻找Java.Lang.Object子类 (必须实现IJavaObject)、 生成子类Java.Lang.Object为我们提供了一种在托管代码中实现接口方法。Because monodroid.exe only looks for Java.Lang.Object subclasses (which must implement IJavaObject), subclassing Java.Lang.Object provides us with a way to implement interfaces in managed code. 例如:For example:

class MyComponentCallbacks : Java.Lang.Object, Android.Content.IComponentCallbacks {
    public void OnConfigurationChanged (Android.Content.Res.Configuration newConfig) {
        // implementation goes here...
    }
    public void OnLowMemory () {
        // implementation goes here...
    }
}

实现详细信息Implementation Details

本文的其余部分提供了实现详细信息,如有更改,恕不另行通知(和此处只是因为开发人员可能会好奇这怎么回事实质上提供)。The remainder of this article provides implementation details subject to change without notice (and is presented here only because developers may be curious about what's going on under the hood).

例如,假定有以下C#源:For example, given the following C# source:

using System;
using Android.App;
using Android.OS;

namespace Mono.Samples.HelloWorld
{
    public class HelloAndroid : Activity
    {
        protected override void OnCreate (Bundle savedInstanceState)
        {
            base.OnCreate (savedInstanceState);
            SetContentView (R.layout.main);
        }
    }
}

Mandroid.exe程序将生成以下 Android 可调用包装器:The mandroid.exe program will generate the following Android Callable Wrapper:

package mono.samples.helloWorld;

public class HelloAndroid extends android.app.Activity {
    static final String __md_methods;
    static {
        __md_methods =
            "n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" +
            "";
        mono.android.Runtime.register (
                "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                HelloAndroid.class,
                __md_methods);
    }

    public HelloAndroid ()
    {
        super ();
        if (getClass () == HelloAndroid.class)
            mono.android.TypeManager.Activate (
                "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
                "", this, new java.lang.Object[] { });
    }

    @Override
    public void onCreate (android.os.Bundle p0)
    {
        n_onCreate (p0);
    }

    private native void n_onCreate (android.os.Bundle p0);
}

请注意保留的基类,并为托管代码中重写每个方法提供了本机方法声明。Notice that the base class is preserved, and native method declarations are provided for each method that is overridden within managed code.

ExportAttribute 和 ExportFieldAttributeExportAttribute and ExportFieldAttribute

通常情况下,Xamarin.Android 会自动生成包含 ACW; 的 Java 代码这一代基于时的类派生自 Java 类和重写现有的 Java 方法的类和方法名称。Typically, Xamarin.Android automatically generates the Java code that comprises the ACW; this generation is based on the class and method names when a class derives from a Java class and overrides existing Java methods. 但是,在某些情况下,代码生成是不足够,如下所述:However, in some scenarios, the code generation is not adequate, as outlined below:

  • Android 支持操作名称中的布局 XML 特性,例如android: onClick XML 特性。Android supports action names in layout XML attributes, for example the android:onClick XML attribute. 如果指定,增加的视图实例将尝试查找 Java 方法。When it is specified, the inflated View instance tries to look up the Java method.

  • Java.io.Serializable接口要求readObjectwriteObject方法。The java.io.Serializable interface requires readObject and writeObject methods. 由于它们不是此接口的成员,因此我们相应的托管的实现不公开这些方法对 Java 代码。Since they are not members of this interface, our corresponding managed implementation does not expose these methods to Java code.

  • Android.os.Parcelable接口需要实现类必须具有一个静态字段CREATOR类型的Parcelable.CreatorThe android.os.Parcelable interface expects that an implementation class must have a static field CREATOR of type Parcelable.Creator. 生成的 Java 代码需要一些显式字段。The generated Java code requires some explicit field. 与我们的标准方案,是用 Java 代码的输出字段无法从托管代码。With our standard scenario, there is no way to output field in Java code from managed code.

代码生成不提供解决方案,以生成具有随机名称的任意 Java 方法,因为从 Xamarin.Android 4.2 开始ExportAttributeExportFieldAttribute已引入了提供上述方案的解决方案。Because code generation does not provide a solution to generate arbitrary Java methods with arbitrary names, starting with Xamarin.Android 4.2, the ExportAttribute and ExportFieldAttribute were introduced to offer a solution to the above scenarios. 这两个属性位于Java.Interop命名空间:Both attributes reside in the Java.Interop namespace:

  • ExportAttribute – 指定方法名称和其预期的异常类型 (若要为提供显式"抛出"在 Java 中)。ExportAttribute – specifies a method name and its expected exception types (to give explicit "throws" in Java). 在方法上使用它时,该方法将"导出"生成调度代码对相应 JNI 调用托管方法的 Java 方法。When it is used on a method, the method will "export" a Java method that generates a dispatch code to the corresponding JNI invocation to the managed method. 这可以用于android:onClickjava.io.SerializableThis can be used with android:onClick and java.io.Serializable.

  • ExportFieldAttribute – 指定的字段名称。ExportFieldAttribute – specifies a field name. 它驻留在充当字段初始值设定项的方法。It resides on a method that works as a field initializer. 这可以用于android.os.ParcelableThis can be used with android.os.Parcelable.

ExportAttribute示例项目演示了如何使用这些属性。The ExportAttribute sample project illustrates how to use these attributes.

ExportAttribute 和 ExportFieldAttribute 故障排除Troubleshooting ExportAttribute and ExportFieldAttribute

  • 由于缺少打包失败Mono.Android.Export.dll –如果您使用过ExportAttributeExportFieldAttribute您的代码或依赖库中的一些方法,您必须添加Mono.Android.Export.dllPackaging fails due to missing Mono.Android.Export.dll – if you used ExportAttribute or ExportFieldAttribute on some methods in your code or dependent libraries, you have to add Mono.Android.Export.dll. 此程序集是隔离,以支持通过 Java 回调代码。This assembly is isolated to support callback code from Java. 它是独立于Mono.Android.dll因为它对应用程序添加了额外的大小。It is separate from Mono.Android.dll as it adds additional size to the application.

  • 在发布版本MissingMethodException发生的导出方法–中发布版本,MissingMethodException发生的导出方法。In Release build, MissingMethodException occurs for Export methods – In Release build, MissingMethodException occurs for Export methods. (此问题已修复在 Xamarin.Android 的最新版本。)(This issue is fixed in the latest version of Xamarin.Android.)

ExportParameterAttributeExportParameterAttribute

ExportAttributeExportFieldAttribute运行时代码可以使用该 Java 提供的功能。ExportAttribute and ExportFieldAttribute provide functionality that Java run-time code can use. 此运行时代码生成的 JNI 方法取决于这些属性通过访问托管的代码。This run-time code accesses managed code through the generated JNI methods driven by those attributes. 因此,没有任何现有的 Java 方法的托管的方法绑定;因此,从托管的方法签名生成 Java 方法。As a result, there is no existing Java method that the managed method binds; hence, the Java method is generated from a managed method signature.

但是,这种情况下不是完全决定的。However, this case is not fully determinant. 最值得注意的是,这是在托管的类型和 Java 类型,如之间的一些高级映射,则返回 true:Most notably, this is true in some advanced mappings between managed types and Java types such as:

  • InputStreamInputStream
  • OutputStreamOutputStream
  • XmlPullParserXmlPullParser
  • XmlResourceParserXmlResourceParser

如果需要为导出的方法,这些类型ExportParameterAttribute必须用于显式提供相应的参数或返回值的类型。When types such as these are needed for exported methods, the ExportParameterAttribute must be used to explicitly give the corresponding parameter or return value a type.

批注特性Annotation Attribute

在 Xamarin.Android 4.2 我们转换IAnnotation到属性 (System.Attribute) 和添加了的对 Java 包装器中的批注生成的实现类型。In Xamarin.Android 4.2, we converted IAnnotation implementation types into attributes (System.Attribute), and added support for annotation generation in Java wrappers.

这意味着以下方向更改:This means the following directional changes:

  • 绑定生成器生成Java.Lang.DeprecatedAttributejava.Lang.Deprecated(虽然它应该是[Obsolete]在托管代码中)。The binding generator generates Java.Lang.DeprecatedAttribute from java.Lang.Deprecated (while it should be [Obsolete] in managed code).

  • 这并不意味着,现有Java.Lang.Deprecated类会消失。This does not mean that existing Java.Lang.Deprecated class will vanish. (如果存在此类使用情况),可以为常用的 Java 对象仍使用这些基于 Java 的对象。These Java-based objects could be still used as usual Java objects (if such usage exists). 将有DeprecatedDeprecatedAttribute类。There will be Deprecated and DeprecatedAttribute classes.

  • Java.Lang.DeprecatedAttribute类标记为[Annotation]The Java.Lang.DeprecatedAttribute class is marked as [Annotation] . 继承自的自定义属性时[Annotation]特性,msbuild 任务将生成该自定义属性的 Java 批注 (@Deprecated) 在 Android 可调用包装器 (ACW)。When there is a custom attribute that is inherited from this [Annotation] attribute, msbuild task will generate a Java annotation for that custom attribute (@Deprecated) in the Android Callable Wrapper (ACW).

  • 批注无法生成到类、 方法和导出字段 (这是在托管代码中的方法)。Annotations could be generated onto classes, methods and exported fields (which is a method in managed code).

如果未注册包含类 (批注的类本身或包含带批注的成员的类),整个 Java 类不生成源,包括的批注。If the containing class (the annotated class itself, or the class that contains the annotated members) is not registered, the entire Java class source is not generated at all, including annotations. 对于方法,可以指定ExportAttribute获取显式生成并批注的方法。For methods, you can specify the ExportAttribute to get the method explicitly generated and annotated. 此外,它不是一项功能"生成"Java 批注类定义。Also, it is not a feature to "generate" a Java annotation class definition. 换而言之,如果定义特定批注的自定义托管的属性,需要添加另一个.jar 库,它包含相应的 Java 批注类。In other words, if you define a custom managed attribute for a certain annotation, you'll have to add another .jar library that contains the corresponding Java annotation class. 添加用于定义批注类型的 Java 源代码文件是不够的。Adding a Java source file that defines the annotation type is not sufficient. Java 编译器不能像那样aptThe Java compiler does not work in the same way as apt.

此外,以下限制适用:Additionally the following limitations apply:

  • 此转换过程不会考虑@Target上批注类型的批注到目前为止。This conversion process does not consider @Target annotation on the annotation type so far.

  • 到属性的属性无效。Attributes onto a property does not work. 改为使用为属性 getter 或 setter 的属性。Use attributes for property getter or setter instead.

类绑定Class Binding

绑定类意味着编写托管可调用包装器,以简化的基础的 Java 类型的调用。Binding a class means writing a managed callable wrapper to simplify invocation of the underlying Java type.

绑定虚拟和抽象方法,以便允许重写从C#需要 Xamarin.Android 4.0。Binding virtual and abstract methods to permit overriding from C# requires Xamarin.Android 4.0. 但是,任何版本的 Xamarin.Android 可以绑定的非虚拟方法、 静态方法或虚方法不支持替代情况下。However, any version of Xamarin.Android can bind non-virtual methods, static methods, or virtual methods without supporting overrides.

绑定通常包含以下各项:A binding typically contains the following items:

声明类型句柄Declaring Type Handle

字段和方法查找方法要求其声明的类型引用的对象引用。The field and method lookup methods require an object reference referring to their declaring type. 按照约定,这将保存在class_ref字段:By convention, this is held in a class_ref field:

static IntPtr class_ref = JNIEnv.FindClass(CLASS);

请参阅JNI 类型引用有关详细信息部分有关CLASS令牌。See the JNI Type References section for details about the CLASS token.

绑定字段Binding Fields

Java 字段公开为C#属性,例如 Java 字段java.lang.System.in绑定为C#属性Java.Lang.JavaSystem.InJava fields are exposed as C# properties, for example the Java field java.lang.System.in is bound as the C# property Java.Lang.JavaSystem.In. 此外,由于 JNI 区分静态字段和实例字段,实现属性时使用不同的方法。Furthermore, since JNI distinguishes between static fields and instance fields, different methods be used when implementing the properties.

字段绑定涉及到三个集的方法:Field binding involves three sets of methods:

  1. 获取字段 id方法。The get field id method. 获取字段 id方法负责返回字段句柄获取字段值设置字段值方法将使用。The get field id method is responsible for returning a field handle that the get field value and set field value methods will use. 获取字段 id 需要知道声明类型,该字段的名称和JNI 类型签名的字段。Obtaining the field id requires knowing the declaring type, the name of the field, and the JNI type signature of the field.

  2. 获取字段值方法。The get field value methods. 这些方法需要字段句柄,并负责从 Java 读取字段的值。These methods require the field handle and are responsible for reading the field's value from Java. 要使用的方法取决于字段的类型。The method to use depends upon the field's type.

  3. 设置字段值方法。The set field value methods. 这些方法需要字段句柄,并且负责编写在 Java 中的字段的值。These methods require the field handle and are responsible for writing the field's value within Java. 要使用的方法取决于字段的类型。The method to use depends upon the field's type.

静态字段使用JNIEnv.GetStaticFieldIDJNIEnv.GetStatic*Field,并JNIEnv.SetStaticField方法。Static fields use the JNIEnv.GetStaticFieldID, JNIEnv.GetStatic*Field, and JNIEnv.SetStaticField methods.

实例字段使用JNIEnv.GetFieldIDJNIEnv.Get*Field,并JNIEnv.SetField方法。Instance fields use the JNIEnv.GetFieldID, JNIEnv.Get*Field, and JNIEnv.SetField methods.

例如,静态属性JavaSystem.In可以作为实现:For example, the static property JavaSystem.In can be implemented as:

static IntPtr in_jfieldID;
public static System.IO.Stream In
{
    get {
        if (in_jfieldId == IntPtr.Zero)
            in_jfieldId = JNIEnv.GetStaticFieldID (class_ref, "in", "Ljava/io/InputStream;");
        IntPtr __ret = JNIEnv.GetStaticObjectField (class_ref, in_jfieldId);
        return InputStreamInvoker.FromJniHandle (__ret, JniHandleOwnership.TransferLocalRef);
    }
}

注意:我们将使用InputStreamInvoker.FromJniHandle要转换到的 JNI 引用System.IO.Stream使用的实例,然后我们JniHandleOwnership.TransferLocalRef因为JNIEnv.GetStaticObjectField返回本地引用。Note: We're using InputStreamInvoker.FromJniHandle to convert the JNI reference into a System.IO.Stream instance, and we're using JniHandleOwnership.TransferLocalRef because JNIEnv.GetStaticObjectField returns a local reference.

许多Android.Runtime类型具有FromJniHandle方法会将转换 JNI 引用到所需的类型。Many of the Android.Runtime types have FromJniHandle methods which will convert a JNI reference into the desired type.

方法绑定Method Binding

Java 方法公开为C#方法以及C#属性。Java methods are exposed as C# methods and as C# properties. 例如,Java 方法java.lang.Runtime.runFinalizersOnExit方法绑定为Java.Lang.Runtime.RunFinalizersOnExit方法,和java.lang.Object.getClass方法绑定为Java.Lang.Object.Class属性。For example, the Java method java.lang.Runtime.runFinalizersOnExit method is bound as the Java.Lang.Runtime.RunFinalizersOnExit method, and the java.lang.Object.getClass method is bound as the Java.Lang.Object.Class property.

方法调用是一个两步过程:Method invocation is a two-step process:

  1. 获取方法 id为要调用的方法。The get method id for the method to invoke. 获取方法 id方法负责返回方法调用方法将使用的方法句柄。The get method id method is responsible for returning a method handle that the method invocation methods will use. 获取方法 id 需要知道声明类型,该方法的名称和JNI 类型签名的方法。Obtaining the method id requires knowing the declaring type, the name of the method, and the JNI type signature of the method.

  2. 调用方法。Invoke the method.

就像使用字段,将使用以获取方法 id 并调用该方法的方法的静态方法和实例方法之间存在差异。Just as with fields, the methods to use to get the method id and invoke the method differ between static methods and instance methods.

静态方法使用JNIEnv.GetStaticMethodID()查找方法 id,并使用JNIEnv.CallStatic*Method系列方法调用。Static methods use JNIEnv.GetStaticMethodID() to lookup the method id, and use the JNIEnv.CallStatic*Method family of methods for invocation.

实例方法使用JNIEnv.GetMethodID查找方法 id,并使用JNIEnv.Call*MethodJNIEnv.CallNonvirtual*Method系列的调用的方法。Instance methods use JNIEnv.GetMethodID to lookup the method id, and use the JNIEnv.Call*Method and JNIEnv.CallNonvirtual*Method families of methods for invocation.

方法绑定是可能不止是方法调用。Method binding is potentially more than just method invocation. 方法绑定还包括允许的方法重写 (适用于抽象的并且非最终方法),或实现 (适用于接口方法)。Method binding also includes allowing a method to be overridden (for abstract and non-final methods) or implemented (for interface methods). 支持继承接口部分介绍了支持的虚拟方法和接口方法的复杂性。The Supporting Inheritance, Interfaces section covers the complexities of supporting virtual methods and interface methods.

静态方法Static Methods

绑定的静态方法涉及到使用JNIEnv.GetStaticMethodID若要获取的方法句柄,然后使用相应JNIEnv.CallStatic*Method方法,具体取决于方法的返回类型。Binding a static method involves using JNIEnv.GetStaticMethodID to obtain a method handle, then using the appropriate JNIEnv.CallStatic*Method method, depending on the method's return type. 以下是有关绑定的示例Runtime.getRuntime方法:The following is an example of a binding for the Runtime.getRuntime method:

static IntPtr id_getRuntime;

[Register ("getRuntime", "()Ljava/lang/Runtime;", "")]
public static Java.Lang.Runtime GetRuntime ()
{
    if (id_getRuntime == IntPtr.Zero)
        id_getRuntime = JNIEnv.GetStaticMethodID (class_ref,
                "getRuntime", "()Ljava/lang/Runtime;");

    return Java.Lang.Object.GetObject<Java.Lang.Runtime> (
            JNIEnv.CallStaticObjectMethod  (class_ref, id_getRuntime),
            JniHandleOwnership.TransferLocalRef);
}

请注意,我们在静态字段中,存储方法句柄id_getRuntimeNote that we store the method handle in a static field, id_getRuntime. 这是一种性能优化,因此方法句柄不需要在每次调用上查找。This is a performance optimization, so that the method handle doesn't need to be looked up on every invocation. 不需要缓存方法句柄以这种方式。It is not necessary to cache the method handle in this way. 获取方法句柄后, JNIEnv.CallStaticObjectMethod用于调用该方法。Once the method handle is obtained, JNIEnv.CallStaticObjectMethod is used to invoke the method. JNIEnv.CallStaticObjectMethod 返回IntPtr其中包含返回的 Java 实例的句柄。JNIEnv.CallStaticObjectMethod returns an IntPtr which contains the handle of the returned Java instance. Java.Lang.Object.GetObject<T>(IntPtr,JniHandleOwnership)用于将 Java 句柄转换为强类型化的对象实例。Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) is used to convert the Java handle into a strongly typed object instance.

非虚拟实例方法绑定Non-virtual Instance Method Binding

绑定final实例方法或实例方法不需要重写时,涉及到使用JNIEnv.GetMethodID若要获取的方法句柄,然后使用相应JNIEnv.Call*Method方法,具体取决于方法的返回类型。Binding a final instance method, or an instance method which doesn't require overriding, involves using JNIEnv.GetMethodID to obtain a method handle, then using the appropriate JNIEnv.Call*Method method, depending on the method's return type. 以下是有关绑定的示例Object.Class属性:The following is an example of a binding for the Object.Class property:

static IntPtr id_getClass;
public Java.Lang.Class Class {
    get {
        if (id_getClass == IntPtr.Zero)
            id_getClass = JNIEnv.GetMethodID (class_ref, "getClass", "()Ljava/lang/Class;");
        return Java.Lang.Object.GetObject<Java.Lang.Class> (
                JNIEnv.CallObjectMethod (Handle, id_getClass),
                JniHandleOwnership.TransferLocalRef);
    }
}

请注意,我们在静态字段中,存储方法句柄id_getClassNote that we store the method handle in a static field, id_getClass. 这是一种性能优化,因此方法句柄不需要在每次调用上查找。This is a performance optimization, so that the method handle doesn't need to be looked up on every invocation. 不需要缓存方法句柄以这种方式。It is not necessary to cache the method handle in this way. 获取方法句柄后, JNIEnv.CallStaticObjectMethod用于调用该方法。Once the method handle is obtained, JNIEnv.CallStaticObjectMethod is used to invoke the method. JNIEnv.CallStaticObjectMethod 返回IntPtr其中包含返回的 Java 实例的句柄。JNIEnv.CallStaticObjectMethod returns an IntPtr which contains the handle of the returned Java instance. Java.Lang.Object.GetObject<T>(IntPtr,JniHandleOwnership)用于将 Java 句柄转换为强类型化的对象实例。Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) is used to convert the Java handle into a strongly typed object instance.

绑定的构造函数Binding Constructors

构造函数是具有名称的 Java 方法"<init>"Constructors are Java methods with the name "<init>". 只需与 Java 实例方法一样,JNIEnv.GetMethodID用于查找的构造函数的句柄。Just as with Java instance methods, JNIEnv.GetMethodID is used to lookup the constructor handle. 与 Java 方法不同JNIEnv.NewObject方法用于调用构造函数方法句柄。Unlike Java methods, the JNIEnv.NewObject methods are used to invoke the constructor method handle. 返回值JNIEnv.NewObject是 JNI 本地引用:The return value of JNIEnv.NewObject is a JNI local reference:

int value = 42;
IntPtr class_ref    = JNIEnv.FindClass ("java/lang/Integer");
IntPtr id_ctor_I    = JNIEnv.GetMethodID (class_ref, "<init>", "(I)V");
IntPtr lrefInstance = JNIEnv.NewObject (class_ref, id_ctor_I, new JValue (value));
// Dispose of lrefInstance, class_ref…

类绑定通常将子类Java.Lang.ObjectNormally a class binding will subclass Java.Lang.Object. 子类化时Java.Lang.Object、 其他语义派上用场:Java.Lang.Object的实例将保留通过 Java 实例的全局引用Java.Lang.Object.Handle属性。When subclassing Java.Lang.Object, an additional semantic comes into play: a Java.Lang.Object instance maintains a global reference to a Java instance through the Java.Lang.Object.Handle property.

  1. Java.Lang.Object默认构造函数将分配一个 Java 实例。The Java.Lang.Object default constructor will allocate a Java instance.

  2. 如果该类型具有RegisterAttribute,并RegisterAttribute.DoNotGenerateAcwtrue,然后实例RegisterAttribute.Name通过其默认构造函数创建类型。If the type has a RegisterAttribute , and RegisterAttribute.DoNotGenerateAcw is true , then an instance of the RegisterAttribute.Name type is created through its default constructor.

  3. 否则为Android 可调用包装器(ACW) 对应于this.GetType通过其默认构造函数实例化。Otherwise, the Android Callable Wrapper (ACW) corresponding to this.GetType is instantiated through its default constructor. 在为包创建过程中生成 android 可调用包装器每隔Java.Lang.Object为其子类RegisterAttribute.DoNotGenerateAcw未设置为trueAndroid Callable Wrappers are generated during package creation for every Java.Lang.Object subclass for which RegisterAttribute.DoNotGenerateAcw is not set to true.

类型不类绑定,这是预期语义: 实例化Mono.Samples.HelloWorld.HelloAndroidC#实例应构造 Javamono.samples.helloworld.HelloAndroid实例,其中是生成 Android 可调用包装器。For types which are not class bindings, this is the expected semantic: instantiating a Mono.Samples.HelloWorld.HelloAndroid C# instance should construct a Java mono.samples.helloworld.HelloAndroid instance which is a generated Android Callable Wrapper.

对于类的绑定,这可能是正确的行为,如果 Java 类型包含一个默认构造函数和/或任何其他构造函数需要调用。For class bindings, this may be the correct behavior if the Java type contains a default constructor and/or no other constructor needs to be invoked. 否则,必须执行以下操作提供一个构造函数:Otherwise, a constructor must be provided which performs the following actions:

  1. 调用Java.Lang.Object (IntPtr,JniHandleOwnership)而不是默认Java.Lang.Object构造函数。Invoking the Java.Lang.Object(IntPtr, JniHandleOwnership) instead of the default Java.Lang.Object constructor. 需要这些信息来避免创建新的 Java 实例。This is needed to avoid creating a new Java instance.

  2. 检查的值Java.Lang.Object.Handle之前创建的任何 Java 实例。Check the value of Java.Lang.Object.Handle before creating any Java instances. Object.Handle属性将具有一个值,而不IntPtr.Zero如果 Android 可调用包装器构造在 Java 代码中,并且正在构造的类绑定以包含所创建的 Android 可调用包装器实例。The Object.Handle property will have a value other than IntPtr.Zero if an Android Callable Wrapper was constructed in Java code, and the class binding is being constructed to contain the created Android Callable Wrapper instance. 例如,当 Android 创建mono.samples.helloworld.HelloAndroid实例,将创建 Android 可调用包装器,第一个和 JavaHelloAndroid构造函数将创建一个实例的相应Mono.Samples.HelloWorld.HelloAndroid类型,与Object.Handle属性将设置为在构造函数执行之前的 Java 实例。For example, when Android creates a mono.samples.helloworld.HelloAndroid instance, the Android Callable Wrapper will be created first , and the Java HelloAndroid constructor will create an instance of the corresponding Mono.Samples.HelloWorld.HelloAndroid type, with the Object.Handle property being set to the Java instance prior to constructor execution.

  3. 如果当前的运行时类型不相同声明类型,则相应的 Android 可调用包装器的实例必须创建并使用Object.SetHandle用于存储返回的句柄JNIEnv.CreateInstanceIf the current runtime type is not the same as the declaring type, then an instance of the corresponding Android Callable Wrapper must be created, and use Object.SetHandle to store the handle returned by JNIEnv.CreateInstance.

  4. 如果当前的运行时类型声明的类型相同,然后调用 Java 构造函数,并使用Object.SetHandle用于存储返回的句柄JNIEnv.NewInstanceIf the current runtime type is the same as the declaring type, then invoke the Java constructor and use Object.SetHandle to store the handle returned by JNIEnv.NewInstance .

例如,考虑java.lang.Integer(int)构造函数。For example, consider the java.lang.Integer(int) constructor. 此绑定为:This is bound as:

// Cache the constructor's method handle for later use
static IntPtr id_ctor_I;

// Need [Register] for subclassing
// RegisterAttribute.Name is always ".ctor"
// RegisterAttribute.Signature is tye JNI type signature of constructor
// RegisterAttribute.Connector is ignored; use ""
[Register (".ctor", "(I)V", "")]
public Integer (int value)
    // 1. Prevent Object default constructor execution
    : base (IntPtr.Zero, JniHandleOwnership.DoNotTransfer)
{
    // 2. Don't allocate Java instance if already allocated
    if (Handle != IntPtr.Zero)
        return;

    // 3. Derived type? Create Android Callable Wrapper
    if (GetType () != typeof (Integer)) {
        SetHandle (
                Android.Runtime.JNIEnv.CreateInstance (GetType (), "(I)V", new JValue (value)),
                JniHandleOwnership.TransferLocalRef);
        return;
    }

    // 4. Declaring type: lookup &amp; cache method id...
    if (id_ctor_I == IntPtr.Zero)
        id_ctor_I = JNIEnv.GetMethodID (class_ref, "<init>", "(I)V");
    // ...then create the Java instance and store
    SetHandle (
            JNIEnv.NewObject (class_ref, id_ctor_I, new JValue (value)),
            JniHandleOwnership.TransferLocalRef);
}

JNIEnv.CreateInstance方法是帮助程序执行JNIEnv.FindClassJNIEnv.GetMethodIDJNIEnv.NewObject,并且JNIEnv.DeleteGlobalReference从返回的值上JNIEnv.FindClassThe JNIEnv.CreateInstance methods are helpers to perform a JNIEnv.FindClass, JNIEnv.GetMethodID, JNIEnv.NewObject, and JNIEnv.DeleteGlobalReference on the value returned from JNIEnv.FindClass. 有关详细信息,请参阅下一节。See the next section for details.

支持继承,接口Supporting Inheritance, Interfaces

子类化的 Java 类型或实现 Java 接口需要的新一代Android 可调用包装器(ACWs) 为生成每个Java.Lang.Object在打包过程的子类。Subclassing a Java type or implementing a Java interface requires the generation of Android Callable Wrappers (ACWs) that are generated for every Java.Lang.Object subclass during the packaging process. 通过控制 ACW 代Android.Runtime.RegisterAttribute自定义属性。ACW generation is controlled through the Android.Runtime.RegisterAttribute custom attribute.

有关C#类型,[Register]自定义特性构造函数需要一个参数: JNI 简化类型引用为相应的 Java 类型。For C# types, the [Register] custom attribute constructor requires one argument: the JNI simplified type reference for the corresponding Java type. 这允许提供 Java 之间不同的名称和C#。This allows providing different names between Java and C#.

Xamarin.Android 4.0 之前[Register]自定义属性均不可用"alias"现有的 Java 类型。Prior to Xamarin.Android 4.0, the [Register] custom attribute was unavailable to "alias" existing Java types. 这是因为 ACW 生成过程会生成 ACWs 为每个Java.Lang.Object遇到子类。This is because the ACW generation process would generate ACWs for every Java.Lang.Object subclass encountered.

Xamarin.Android 4.0 引入了RegisterAttribute.DoNotGenerateAcw属性。Xamarin.Android 4.0 introduced the RegisterAttribute.DoNotGenerateAcw property. 此属性指示到 ACW 生成过程跳过带批注的类型,允许的新托管可调用包装器不会导致 ACWs 正在创建包时生成的声明。This property instructs the ACW generation process to skip the annotated type, allowing the declaration of new Managed Callable Wrappers that will not result in ACWs being generated at package creation time. 这允许绑定现有的 Java 类型。This allows binding existing Java types. 例如,考虑以下简单的 Java 类Adder,其中包含一种方法, add,用于将添加到整数并返回结果:For instance, consider the following simple Java class, Adder, which contains one method, add, that adds to integers and returns the result:

package mono.android.test;
public class Adder {
    public int add (int a, int b) {
        return a + b;
    }
}

Adder无法作为绑定类型:The Adder type could be bound as:

[Register ("mono/android/test/Adder", DoNotGenerateAcw=true)]
public partial class Adder : Java.Lang.Object {
    static IntPtr class_ref = JNIEnv.FindClass ( "mono/android/test/Adder");

    public Adder ()
    {
    }

    public Adder (IntPtr handle, JniHandleOwnership transfer)
        : base (handle, transfer)
    {
    }
}
partial class ManagedAdder : Adder {
}

在这里, Adder C#类型别名 Adder Java 类型。Here, the Adder C# type aliases the Adder Java type. [Register]特性用于指定的 JNI 名称mono.android.test.AdderJava 类型和DoNotGenerateAcw属性用来抑制 ACW 生成。The [Register] attribute is used to specify the JNI name of the mono.android.test.Adder Java type, and the DoNotGenerateAcw property is used to inhibit ACW generation. 这将导致为 ACW 新一代ManagedAdder类型,可在正确子类mono.android.test.Adder类型。This will result in the generation of an ACW for the ManagedAdder type, which properly subclasses the mono.android.test.Adder type. 如果RegisterAttribute.DoNotGenerateAcw属性未被使用,则 Xamarin.Android 生成过程应已生成一个新mono.android.test.AdderJava 类型。If the RegisterAttribute.DoNotGenerateAcw property hadn't been used, then the Xamarin.Android build process would have generated a new mono.android.test.Adder Java type. 这会导致编译错误,因为mono.android.test.Adder类型都会出现两次,在两个单独的文件。This would result in compilation errors, as the mono.android.test.Adder type would be present twice, in two separate files.

绑定的虚拟方法Binding Virtual Methods

ManagedAdder 子类 JavaAdder类型,但它不是特别有趣: C# Adder类型未定义任何虚拟方法,因此ManagedAdder不能重写任何内容。ManagedAdder subclasses the Java Adder type, but it isn't particularly interesting: the C# Adder type doesn't define any virtual methods, so ManagedAdder can't override anything.

绑定virtual允许由子类重写的方法需要多项操作需要执行这分为以下两个类别:Binding virtual methods to permit overriding by subclasses requires several things that need to be done which fall into the following two categories:

  1. 方法绑定Method Binding

  2. 方法注册Method Registration

方法绑定Method Binding

方法绑定需要添加到两个支持成员C#Adder定义: ThresholdType,并ThresholdClassA method binding requires the addition of two support members to the C# Adder definition: ThresholdType, and ThresholdClass.

ThresholdTypeThresholdType

ThresholdType属性返回当前绑定的类型:The ThresholdType property returns the current type of the binding:

partial class Adder {
    protected override System.Type ThresholdType {
        get {
            return typeof (Adder);
        }
    }
}

ThresholdType 用于在方法绑定中确定何时应执行虚拟和非虚方法调度。ThresholdType is used in the Method Binding to determine when it should perform virtual vs. non-virtual method dispatch. 应始终返回System.Type实例与声明对应的C#类型。It should always return a System.Type instance which corresponds to the declaring C# type.

ThresholdClassThresholdClass

ThresholdClass属性返回的绑定类型的 JNI 类引用:The ThresholdClass property returns the JNI class reference for the bound type:

partial class Adder {
    protected override IntPtr ThresholdClass {
        get {
            return class_ref;
        }
    }
}

ThresholdClass 在方法绑定中调用时所使用的非虚拟方法。ThresholdClass is used in the Method Binding when invoking non-virtual methods.

绑定实现Binding Implementation

方法绑定实现负责的 Java 方法的运行时调用。The method binding implementation is responsible for runtime invocation of the Java method. 它还包含[Register]是方法注册的一部分,将在方法注册部分中讨论的自定义特性声明:It also contains a [Register] custom attribute declaration that is part of the method registration, and will be discussed in the Method Registration section:

[Register ("add", "(II)I", "GetAddHandler")]
    public virtual int Add (int a, int b)
    {
        if (id_add == IntPtr.Zero)
            id_add = JNIEnv.GetMethodID (class_ref, "add", "(II)I");
        if (GetType () == ThresholdType)
            return JNIEnv.CallIntMethod (Handle, id_add, new JValue (a), new JValue (b));
        return JNIEnv.CallNonvirtualIntMethod (Handle, ThresholdClass, id_add, new JValue (a), new JValue (b));
    }
}

id_add字段包含要调用的 Java 方法的方法 ID。The id_add field contains the method ID for the Java method to invoke. id_add值从获取JNIEnv.GetMethodID,这需要声明类 (class_ref),Java 方法名称 ("add"),和方法的 JNI 签名 ("(II)I")。The id_add value is obtained from JNIEnv.GetMethodID, which requires the declaring class (class_ref), the Java method name ("add"), and the JNI signature of the method ("(II)I").

获取方法 ID 后,GetType进行比较的ThresholdType以确定是否需要虚拟或非虚拟调度。Once the method ID is obtained, GetType is compared to ThresholdType to determine if virtual or non-virtual dispatch is required. 虚拟调度时,必须GetType匹配ThresholdType,作为Handle可能指 Java 分配的子类,这会重写该方法。Virtual dispatch is required when GetType matches ThresholdType, as Handle may refer to a Java-allocated subclass which overrides the method.

GetType不匹配ThresholdTypeAdder子类别 (例如通过ManagedAdder),并且Adder.Add如果子类调用,将只调用实现base.AddWhen GetType doesn't match ThresholdType, Adder has been subclassed (e.g. by ManagedAdder), and the Adder.Add implementation will only be invoked if the subclass invoked base.Add. 这种非虚拟调度情况,这是 whereThresholdClass传入。This is the non-virtual dispatch case, which is where ThresholdClass comes in. ThresholdClass 指定的 Java 类将提供要调用的方法的实现。ThresholdClass specifies which Java class will provide the implementation of the method to invoke.

方法注册Method Registration

假设我们有的已更新ManagedAdder定义,这会重写Adder.Add方法:Assume we have an updated ManagedAdder definition which overrides the Adder.Add method:

partial class ManagedAdder : Adder {
    public override int Add (int a, int b) {
        return (a*2) + (b*2);
    }
}

请记住,Adder.Add[Register]自定义属性:Recall that Adder.Add had a [Register] custom attribute:

[Register ("add", "(II)I", "GetAddHandler")]

[Register]自定义特性构造函数接受三个值:The [Register] custom attribute constructor accepts three values:

  1. Java 方法的名称"add"这种情况下。The name of the Java method, "add" in this case.

  2. JNI 类型签名的方法,"(II)I"这种情况下。The JNI Type Signature of the method, "(II)I" in this case.

  3. 连接器方法GetAddHandler这种情况下。The connector method , GetAddHandler in this case. 连接器方法将在下文。Connector methods will be discussed later.

前两个参数允许 ACW 生成过程生成一个方法声明重写的方法。The first two parameters allow the ACW generation process to generate a method declaration to override the method. 生成 ACW 将包含某些下面的代码:The resulting ACW would contain some of the following code:

public class ManagedAdder extends mono.android.test.Adder {
    static final String __md_methods;
    static {
        __md_methods = "n_add:(II)I:GetAddHandler\n" +
            "";
        mono.android.Runtime.register (...);
    }
    @Override
    public int add (int p0, int p1) {
        return n_add (p0, p1);
    }
    private native int n_add (int p0, int p1);
    // ...
}

请注意,@Override方法声明,该委托给n_-前缀相同名称的方法。Note that an @Override method is declared, which delegates to an n_-prefixed method of the same name. 这确保 Java 代码调用时ManagedAdder.addManagedAdder.n_add将调用,以便重写C#ManagedAdder.Add要执行方法。This ensure that when Java code invokes ManagedAdder.add, ManagedAdder.n_add will be invoked, which will allow the overriding C# ManagedAdder.Add method to be executed.

因此,最重要的问题: 如何将ManagedAdder.n_add挂接到ManagedAdder.AddThus, the most important question: how is ManagedAdder.n_add hooked up to ManagedAdder.Add?

Javanative与 Java (Android 运行时) 运行时通过注册方法JNI RegisterNatives 函数Java native methods are registered with the Java (the Android runtime) runtime through the JNI RegisterNatives function. RegisterNatives 将遵循结构包含要调用的 Java 方法名称、 JNI 类型签名和函数指针的数组JNI 调用约定RegisterNatives takes an array of structures containing the Java method name, the JNI Type Signature, and a function pointer to invoke that follows JNI calling convention. 函数指针必须是采用两个指针自变量跟方法参数的函数。The function pointer must be a function that takes two pointer arguments followed by the method parameters. JavaManagedAdder.n_add必须通过具有以下 C 原型的函数的实现方法:The Java ManagedAdder.n_add method must be implemented through a function that has the following C prototype:

int FunctionName(JNIEnv *env, jobject this, int a, int b)

Xamarin.Android 不公开RegisterNatives方法。Xamarin.Android does not expose a RegisterNatives method. 相反,ACW 和 MCW 一起提供的信息需要调用RegisterNatives: ACW 包含方法名称和 JNI 类型签名,现在只缺少挂接的函数指针。Instead, the ACW and the MCW together provide the information necessary to invoke RegisterNatives: the ACW contains the method name and the JNI type signature, the only thing missing is a function pointer to hook up.

这就是连接器方法传入。This is where the connector method comes in. 第三个[Register]自定义特性参数是在已注册的类型或不接受任何参数,并返回的已注册类型的基类中定义的方法名称System.DelegateThe third [Register] custom attribute parameter is the name of a method defined in the registered type or a base class of the registered type that accepts no parameters and returns a System.Delegate. 返回System.Delegate又是指具有正确的 JNI 函数签名的方法。The returned System.Delegate in turn refers to a method that has the correct JNI function signature. 最后,该连接器方法返回的委托必须根路径,使 GC 不会收集它,因为委托提供给 Java。Finally, the delegate that the connector method returns must be rooted so that the GC doesn't collect it, as the delegate is being provided to Java.

#pragma warning disable 0169
static Delegate cb_add;
// This method must match the third parameter of the [Register]
// custom attribute, must be static, must return System.Delegate,
// and must accept no parameters.
static Delegate GetAddHandler ()
{
    if (cb_add == null)
        cb_add = JNINativeWrapper.CreateDelegate ((Func<IntPtr, IntPtr, int, int, int>) n_Add);
    return cb_add;
}
// This method is registered with JNI.
static int n_Add (IntPtr jnienv, IntPtr lrefThis, int a, int b)
{
    Adder __this = Java.Lang.Object.GetObject<Adder>(lrefThis, JniHandleOwnership.DoNotTransfer);
    return __this.Add (a, b);
}
#pragma warning restore 0169

GetAddHandler方法创建Func<IntPtr, IntPtr, int, int, int>委托,是指n_Add方法,然后调用JNINativeWrapper.CreateDelegateThe GetAddHandler method creates a Func<IntPtr, IntPtr, int, int, int> delegate which refers to the n_Add method, then invokes JNINativeWrapper.CreateDelegate. JNINativeWrapper.CreateDelegate 将提供的方法包装在 try/catch 块,以便任何未经处理的异常处理,将导致引发AndroidEvent.UnhandledExceptionRaiser事件。JNINativeWrapper.CreateDelegate wraps the provided method in a try/catch block, so that any unhandled exceptions are handled and will result in raising the AndroidEvent.UnhandledExceptionRaiser event. 结果委托存储在静态cb_add变量,以便 GC 不会释放该委托。The resulting delegate is stored in the static cb_add variable so that the GC will not free the delegate.

最后,n_Add方法负责封送到相应的托管类型的 JNI 参数然后委派方法调用。Finally, the n_Add method is responsible for marshaling the JNI parameters to the corresponding managed types, then delegating the method call.

注意:始终使用JniHandleOwnership.DoNotTransferMCW 获取对使用 Java 实例时。Note: Always use JniHandleOwnership.DoNotTransfer when obtaining an MCW over a Java instance. 把它们当作本地引用 (并因此调用JNIEnv.DeleteLocalRef) 将中断托管-> Java->托管堆栈转换。Treating them as a local reference (and thus calling JNIEnv.DeleteLocalRef) will break managed -> Java -> managed stack transitions.

完成 Adder 绑定Complete Adder Binding

完全托管的绑定mono.android.tests.Adder类型是:The complete managed binding for the mono.android.tests.Adder type is:

[Register ("mono/android/test/Adder", DoNotGenerateAcw=true)]
public class Adder : Java.Lang.Object {

    static IntPtr class_ref = JNIEnv.FindClass ("mono/android/test/Adder");

    public Adder ()
    {
    }

    public Adder (IntPtr handle, JniHandleOwnership transfer)
        : base (handle, transfer)
    {
    }

    protected override Type ThresholdType {
        get {return typeof (Adder);}
    }

    protected override IntPtr ThresholdClass {
        get {return class_ref;}
    }

#region Add
    static IntPtr id_add;

    [Register ("add", "(II)I", "GetAddHandler")]
    public virtual int Add (int a, int b)
    {
        if (id_add == IntPtr.Zero)
            id_add = JNIEnv.GetMethodID (class_ref, "add", "(II)I");
        if (GetType () == ThresholdType)
            return JNIEnv.CallIntMethod (Handle, id_add, new JValue (a), new JValue (b));
        return JNIEnv.CallNonvirtualIntMethod (Handle, ThresholdClass, id_add, new JValue (a), new JValue (b));
    }

#pragma warning disable 0169
    static Delegate cb_add;
    static Delegate GetAddHandler ()
    {
        if (cb_add == null)
            cb_add = JNINativeWrapper.CreateDelegate ((Func<IntPtr, IntPtr, int, int, int>) n_Add);
        return cb_add;
    }

    static int n_Add (IntPtr jnienv, IntPtr lrefThis, int a, int b)
    {
        Adder __this = Java.Lang.Object.GetObject<Adder>(lrefThis, JniHandleOwnership.DoNotTransfer);
        return __this.Add (a, b);
    }
#pragma warning restore 0169
#endregion
}

限制Restrictions

编写一种类型时满足以下条件:When writing a type that matches the following criteria:

  1. 子类 Java.Lang.ObjectSubclasses Java.Lang.Object

  2. 具有[Register]自定义属性Has a [Register] custom attribute

  3. RegisterAttribute.DoNotGenerateAcwtrueRegisterAttribute.DoNotGenerateAcw is true

然后 GC 交互类型对于不得有可能引用的任何字段Java.Lang.ObjectJava.Lang.Object在运行时的子类。Then for GC interaction the type must not have any fields which may refer to a Java.Lang.Object or Java.Lang.Object subclass at runtime. 例如,类型的字段System.Object和不允许使用任何接口类型。For example, fields of type System.Object and any interface type are not permitted. 不能引用的类型Java.Lang.Object允许使用实例,如System.StringList<int>Types which cannot refer to Java.Lang.Object instances are permitted, such as System.String and List<int>. 此限制是为了防止 gc 过早对象集合。This restriction is to prevent premature object collection by the GC.

如果该类型必须包含一个实例字段,它可以指Java.Lang.Object实例,则字段类型必须为System.WeakReferenceGCHandleIf the type must contain an instance field that can refer to a Java.Lang.Object instance, then the field type must be System.WeakReference or GCHandle.

绑定抽象方法Binding Abstract Methods

绑定abstract方法在很大程度上等同于绑定的虚拟方法。Binding abstract methods is largely identical to binding virtual methods. 有只有两个区别:There are only two differences:

  1. 抽象方法是抽象类。The abstract method is abstract. 它仍将保留[Register]属性和关联的方法注册,方法绑定只需移动到Invoker类型。It still retains the [Register] attribute and the associated Method Registration, the Method Binding is just moved to the Invoker type.

  2. abstract``Invoker创建类型的子类的抽象类型。A non- abstract Invoker type is created which subclasses the abstract type. Invoker类型必须重写基类中声明的所有抽象方法和重写的实现是绑定方法的实现,但可以忽略非虚拟调度大小写。The Invoker type must override all abstract methods declared in the base class, and the overridden implementation is the Method Binding implementation, though the non-virtual dispatch case can be ignored.

例如,假设上述mono.android.test.Adder.add方法已abstractFor example, assume that the above mono.android.test.Adder.add method were abstract. C#绑定会更改,以便Adder.Add了抽象,和一个新AdderInvoker将其实现定义的类型Adder.Add:The C# binding would change so that Adder.Add were abstract, and a new AdderInvoker type would be defined which implemented Adder.Add:

partial class Adder {
    [Register ("add", "(II)I", "GetAddHandler")]
    public abstract int Add (int a, int b);

    // The Method Registration machinery is identical to the
    // virtual method case...
}

partial class AdderInvoker : Adder {
    public AdderInvoker (IntPtr handle, JniHandleOwnership transfer)
        : base (handle, transfer)
    {
    }

    static IntPtr id_add;
    public override int Add (int a, int b)
    {
        if (id_add == IntPtr.Zero)
            id_add = JNIEnv.GetMethodID (class_ref, "add", "(II)I");
        return JNIEnv.CallIntMethod (Handle, id_add, new JValue (a), new JValue (b));
    }
}

Invoker时获取对 Java 创建实例的 JNI 引用,类型才是必需。The Invoker type is only necessary when obtaining JNI references to Java-created instances.

绑定接口Binding Interfaces

绑定接口是从概念上讲类似于绑定类,其中包含的虚拟方法,但许多细节在细微 (和小) 方面存在差异。Binding interfaces is conceptually similar to binding classes containing virtual methods, but many of the specifics differ in subtle (and not so subtle) ways. 请考虑以下Java 接口声明:Consider the following Java interface declaration:

public interface Progress {
    void onAdd(int[] values, int currentIndex, int currentSum);
}

接口绑定都具有两个部分:C#接口定义和调用程序定义的接口。Interface bindings have two parts: the C# interface definition, and an Invoker definition for the interface.

接口定义Interface Definition

C#接口定义必须满足以下要求:The C# interface definition must fulfill the following requirements:

  • 接口定义必须具有[Register]自定义属性。The interface definition must have a [Register] custom attribute.

  • 接口定义必须扩展IJavaObject interfaceThe interface definition must extend the IJavaObject interface. 如果不这样做将阻止 ACWs 从 Java 接口继承。Failure to do so will prevent ACWs from inheriting from the Java interface.

  • 每个接口方法必须包含[Register]属性,用于指定相应的 Java 方法名称、 JNI 签名和连接器方法。Each interface method must contain a [Register] attribute specifying the corresponding Java method name, the JNI signature, and the connector method.

  • 连接器方法还必须指定可以位于连接器方法的类型。The connector method must also specify the type that the connector method can be located on.

绑定时abstractvirtual内正在注册的类型的继承层次结构中搜索方法,该连接器方法。When binding abstract and virtual methods, the connector method would be searched within the inheritance hierarchy of the type being registered. 接口可具有包含正文,因此这不起作用,因此要求没有方法的指定类型,该值指示连接器方法所在的位置。Interfaces can have no methods containing bodies, so this doesn't work, thus the requirement that a type be specified indicating where the connector method is located. 在连接器方法字符串中,指定一个冒号之后的类型':',并且必须包含调用程序的类型的程序集限定的类型名称。The type is specified within the connector method string, after a colon ':', and must be the assembly qualified type name of the type containing the invoker.

接口方法声明为相应的 Java 方法使用的翻译兼容类型。Interface method declarations are a translation of the corresponding Java method using compatible types. Java 内置类型兼容的类型是相应C#类型,例如 Javaint是C# intFor Java builtin types, the compatible types are the corresponding C# types, e.g. Java int is C# int. 对于引用类型兼容的类型是可以提供适当的 Java 类型的 JNI 句柄的类型。For reference types, the compatible type is a type that can provide a JNI handle of the appropriate Java type.

接口成员,将不会直接调用由 Java–将通过调用程序类型间接调用–以便允许一定程度的灵活性。The interface members will not be directly invoked by Java – invocation will be mediated through the Invoker type – so some amount of flexibility is permitted.

Java 进度接口可以是中声明C#作为:The Java Progress interface can be declared in C# as:

[Register ("mono/android/test/Adder$Progress", DoNotGenerateAcw=true)]
public interface IAdderProgress : IJavaObject {
    [Register ("onAdd", "([III)V",
            "GetOnAddHandler:Mono.Samples.SanityTests.IAdderProgressInvoker, SanityTests, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
    void OnAdd (JavaArray<int> values, int currentIndex, int currentSum);
}

请注意,在上述我们映射 Javaint[]参数JavaArray<int>Notice in the above that we map the Java int[] parameter to a JavaArray<int>. 没有必要这样做: 我们可能已经绑定到C# int[],或IList<int>,或其他内容完全。This isn't necessary: we could have bound it to a C# int[], or an IList<int>, or something else entirely. 选择的任何类型,则Invoker需要能够将其转换为 Javaint[]调用的类型。Whatever type is chosen, the Invoker needs to be able to translate it into a Java int[] type for invocation.

调用程序定义Invoker Definition

Invoker类型定义必须继承Java.Lang.Object、 实现相应的接口,并提供在接口定义中引用的所有连接方法。The Invoker type definition must inherit Java.Lang.Object, implement the appropriate interface, and provide all connection methods referenced in the interface definition. 还有一个不同于类绑定的更多建议:class_ref字段和方法 Id 应为实例成员,非静态成员。There is one more suggestion that differs from a class binding: the class_ref field and method IDs should be instance members, not static members.

优先使用实例成员的原因都与JNIEnv.GetMethodID中 Android 运行时的行为。The reason for preferring instance members has to do with JNIEnv.GetMethodID behavior in the Android runtime. (这可能是 Java 的行为; 它尚未测试)。JNIEnv.GetMethodID查找来自于实现的接口和未声明的接口的方法时返回 null。(This may be Java behavior as well; it hasn't been tested.) JNIEnv.GetMethodID returns null when looking up a method that comes from an implemented interface and not the declared interface. 请考虑java.util.SortedMap<K,V> Java 接口,实现java.util.Map<K,V> 接口。Consider the java.util.SortedMap<K, V> Java interface, which implements the java.util.Map<K, V> interface. 映射提供清除方法,因此看起来合理InvokerSortedMap 的定义将是:Map provides a clear method, thus a seemingly reasonable Invoker definition for SortedMap would be:

// Fails at runtime. DO NOT FOLLOW
partial class ISortedMapInvoker : Java.Lang.Object, ISortedMap {
    static IntPtr class_ref = JNIEnv.FindClass ("java/util/SortedMap");
    static IntPtr id_clear;
    public void Clear()
    {
        if (id_clear == IntPtr.Zero)
            id_clear = JNIEnv.GetMethodID(class_ref, "clear", "()V");
        JNIEnv.CallVoidMethod(Handle, id_clear);
    }
     // ...
}

上述将失败,因为JNIEnv.GetMethodID将返回null查找时Map.clear方法通过SortedMap类实例。The above will fail because JNIEnv.GetMethodID will return null when looking up the Map.clear method through the SortedMap class instance.

有两个解决方案: 跟踪每个方法的来源,哪个接口,并具有class_ref为每个接口,或保留为实例成员的所有内容,并派生程度最高的类类型,不是接口类型上执行方法查找。There are two solutions to this: track which interface every method comes from, and have a class_ref for each interface, or keep everything as instance members and perform the method lookup on the most-derived class type, not the interface type. 完成后者Mono.Android.dllThe latter is done in Mono.Android.dll.

该调用程序定义了六个部分: 构造函数中,Dispose方法,ThresholdTypeThresholdClass成员,GetObject方法、 接口方法实现和连接器方法实现。The Invoker definition has six sections: the constructor, the Dispose method, the ThresholdType and ThresholdClass members, the GetObject method, interface method implementation, and the connector method implementation.

构造函数Constructor

需要查找正在调用的实例的运行时类和实例中存储的运行时类的构造函数class_ref字段:The constructor needs to lookup the runtime class of the instance being invoked and store the runtime class in the instance class_ref field:

partial class IAdderProgressInvoker {
    IntPtr class_ref;
    public IAdderProgressInvoker (IntPtr handle, JniHandleOwnership transfer)
        : base (handle, transfer)
    {
        IntPtr lref = JNIEnv.GetObjectClass (Handle);
        class_ref   = JNIEnv.NewGlobalRef (lref);
        JNIEnv.DeleteLocalRef (lref);
    }
}

注意:Handle属性必须使用在构造函数体内,而不handle参数,截止时间 Android v4.0handle基构造函数完成执行后参数可能无效。Note: The Handle property must be used within the constructor body, and not the handle parameter, as on Android v4.0 the handle parameter may be invalid after the base constructor finishes executing.

Dispose 方法Dispose Method

Dispose方法需要释放的构造函数中分配的全局引用:The Dispose method needs to free the global reference allocated in the constructor:

partial class IAdderProgressInvoker {
    protected override void Dispose (bool disposing)
    {
        if (this.class_ref != IntPtr.Zero)
            JNIEnv.DeleteGlobalRef (this.class_ref);
        this.class_ref = IntPtr.Zero;
        base.Dispose (disposing);
    }
}

ThresholdType 和 ThresholdClassThresholdType and ThresholdClass

ThresholdTypeThresholdClass成员是相同的类绑定中找到的内容:The ThresholdType and ThresholdClass members are identical to what is found in a class binding:

partial class IAdderProgressInvoker {
    protected override Type ThresholdType {
        get {
            return typeof (IAdderProgressInvoker);
        }
    }
    protected override IntPtr ThresholdClass {
        get {
            return class_ref;
        }
    }
}

GetObject 方法GetObject Method

一个静态GetObject支持所需的方法Extensions.JavaCast<T>():A static GetObject method is required to support Extensions.JavaCast<T>():

partial class IAdderProgressInvoker {
    public static IAdderProgress GetObject (IntPtr handle, JniHandleOwnership transfer)
    {
        return new IAdderProgressInvoker (handle, transfer);
    }
}

接口方法Interface Methods

需要有一个实现,调用相应的 Java 方法通过 JNI 接口的每个方法:Every method of the interface needs to have an implementation, which invokes the corresponding Java method through JNI:

partial class IAdderProgressInvoker {
    IntPtr id_onAdd;
    public void OnAdd (JavaArray<int> values, int currentIndex, int currentSum)
    {
        if (id_onAdd == IntPtr.Zero)
            id_onAdd = JNIEnv.GetMethodID (class_ref, "onAdd", "([III)V");
        JNIEnv.CallVoidMethod (Handle, id_onAdd, new JValue (JNIEnv.ToJniHandle (values)), new JValue (currentIndex), new JValue (currentSum));
    }
}

连接器方法Connector Methods

连接器方法和支持基础结构负责封送处理到相应的 JNI 参数C#类型。The connector methods and supporting infrastructure are responsible for marshaling the JNI parameters to appropriate C# types. Javaint[]参数将传递为 JNI jintArray,即IntPtr中C#。The Java int[] parameter will be passed as a JNI jintArray, which is an IntPtr within C#. IntPtr必须封送到JavaArray<int>为了支持调用C#接口:The IntPtr must be marshaled to a JavaArray<int> in order to support invoking the C# interface:

partial class IAdderProgressInvoker {
    static Delegate cb_onAdd;
    static Delegate GetOnAddHandler ()
    {
        if (cb_onAdd == null)
            cb_onAdd = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr, int, int>) n_OnAdd);
        return cb_onAdd;
    }

    static void n_OnAdd (IntPtr jnienv, IntPtr lrefThis, IntPtr values, int currentIndex, int currentSum)
    {
        IAdderProgress __this = Java.Lang.Object.GetObject<IAdderProgress>(lrefThis, JniHandleOwnership.DoNotTransfer);
        using (var _values = new JavaArray<int>(values, JniHandleOwnership.DoNotTransfer)) {
            __this.OnAdd (_values, currentIndex, currentSum);
        }
    }
}

如果int[]会通过首选JavaList<int>,然后JNIEnv.GetArray()也可改用:If int[] would be preferred over JavaList<int>, then JNIEnv.GetArray() could be used instead:

int[] _values = (int[]) JNIEnv.GetArray(values, JniHandleOwnership.DoNotTransfer, typeof (int));

但请注意,JNIEnv.GetArray将复制整个数组之间的 Vm,因此对于大型数组这可能导致大量增加的 GC 压力。Note, however, that JNIEnv.GetArray copies the entire array between VMs, so for large arrays this could result in lots of added GC pressure.

完成调用程序定义Complete Invoker Definition

完成 IAdderProgressInvoker 定义:The complete IAdderProgressInvoker definition:

class IAdderProgressInvoker : Java.Lang.Object, IAdderProgress {

    IntPtr class_ref;

    public IAdderProgressInvoker (IntPtr handle, JniHandleOwnership transfer)
        : base (handle, transfer)
    {
        IntPtr lref = JNIEnv.GetObjectClass (Handle);
        class_ref = JNIEnv.NewGlobalRef (lref);
        JNIEnv.DeleteLocalRef (lref);
    }

    protected override void Dispose (bool disposing)
    {
        if (this.class_ref != IntPtr.Zero)
            JNIEnv.DeleteGlobalRef (this.class_ref);
        this.class_ref = IntPtr.Zero;
        base.Dispose (disposing);
    }

    protected override Type ThresholdType {
        get {return typeof (IAdderProgressInvoker);}
    }

    protected override IntPtr ThresholdClass {
        get {return class_ref;}
    }

    public static IAdderProgress GetObject (IntPtr handle, JniHandleOwnership transfer)
    {
        return new IAdderProgressInvoker (handle, transfer);
    }

#region OnAdd
    IntPtr id_onAdd;
    public void OnAdd (JavaArray<int> values, int currentIndex, int currentSum)
    {
        if (id_onAdd == IntPtr.Zero)
            id_onAdd = JNIEnv.GetMethodID (class_ref, "onAdd",
                    "([III)V");
        JNIEnv.CallVoidMethod (Handle, id_onAdd,
                new JValue (JNIEnv.ToJniHandle (values)),
                new JValue (currentIndex),
new JValue (currentSum));
    }

#pragma warning disable 0169
    static Delegate cb_onAdd;
    static Delegate GetOnAddHandler ()
    {
        if (cb_onAdd == null)
            cb_onAdd = JNINativeWrapper.CreateDelegate ((Action<IntPtr, IntPtr, IntPtr, int, int>) n_OnAdd);
        return cb_onAdd;
    }

    static void n_OnAdd (IntPtr jnienv, IntPtr lrefThis, IntPtr values, int currentIndex, int currentSum)
    {
        IAdderProgress __this = Java.Lang.Object.GetObject<IAdderProgress>(lrefThis, JniHandleOwnership.DoNotTransfer);
        using (var _values = new JavaArray<int>(values, JniHandleOwnership.DoNotTransfer)) {
            __this.OnAdd (_values, currentIndex, currentSum);
        }
    }
#pragma warning restore 0169
#endregion
}

JNI 对象引用JNI Object References

很多 JNIEnv 方法返回JNI 的对象引用,这是类似于GCHandles。Many JNIEnv methods return JNI object references, which are similar to GCHandles. JNI 提供三种不同类型的对象引用: 本地引用、 全局引用和全局的弱引用。JNI provides three different types of object references: local references, global references, and weak global references. 所有这三个表示为System.IntPtr(根据 JNI 函数类型部分中) 不是所有IntPtrs 从返回JNIEnv方法是引用。All three are represented as System.IntPtr, but (as per the JNI Function Types section) not all IntPtrs returned from JNIEnv methods are references. 例如, JNIEnv.GetMethodID返回IntPtr,但它不会返回一个对象引用,它会返回jmethodIDFor example, JNIEnv.GetMethodID returns an IntPtr, but it doesn't return an object reference, it returns a jmethodID. 请查阅JNI 函数文档有关详细信息。Consult the JNI function documentation for details.

创建本地引用大多数引用创建方法。Local references are created by most reference-creating methods. Android 仅允许有限的数量的本地引用,以在任何给定时间,通常 512 存在。Android only allows a limited number of local references to exist at any given time, usually 512. 可以通过删除本地引用JNIEnv.DeleteLocalRefLocal references can be deleted via JNIEnv.DeleteLocalRef. 与不同的 JNI,并非所有引用 JNIEnv 方法的返回对象的引用返回本地引用;JNIEnv.FindClass返回全局引用。Unlike JNI, not all reference JNIEnv methods which return object references return local references; JNIEnv.FindClass returns a global reference. 强烈建议快速可以可能是通过构造来删除本地引用Java.Lang.Object围绕对象并指定JniHandleOwnership.TransferLocalRefJava.Lang.Object (IntPtr处理,JniHandleOwnership 传输)构造函数。It is strongly recommended that you delete local references as quickly as you can, possibly by constructing a Java.Lang.Object around the object and specifying JniHandleOwnership.TransferLocalRef to the Java.Lang.Object(IntPtr handle, JniHandleOwnership transfer) constructor.

创建全局引用JNIEnv.NewGlobalRefJNIEnv.FindClassGlobal references are created by JNIEnv.NewGlobalRef and JNIEnv.FindClass. 它们可以与销毁JNIEnv.DeleteGlobalRefThey can be destroyed with JNIEnv.DeleteGlobalRef. 仿真程序具有的限制为 2,000 未完成的全局引用,而硬件设备具有的限制为大约 52,000 全局引用。Emulators have a limit of 2,000 outstanding global references, while hardware devices have a limit of around 52,000 global references.

Android v2.2 (Froyo) 及更高版本,弱全局引用才可用。Weak global references are only available on Android v2.2 (Froyo) and later. 可以使用删除全局的弱引用JNIEnv.DeleteWeakGlobalRefWeak global references can be deleted with JNIEnv.DeleteWeakGlobalRef.

处理 JNI 本地引用Dealing With JNI Local References

JNIEnv.GetObjectFieldJNIEnv.GetStaticObjectFieldJNIEnv.CallObjectMethodJNIEnv.CallNonvirtualObjectMethodJNIEnv.CallStaticObjectMethod方法返回IntPtr其中包含 Java 对象的 JNI 本地引用或IntPtr.Zero如果 Java 返回nullThe JNIEnv.GetObjectField, JNIEnv.GetStaticObjectField, JNIEnv.CallObjectMethod, JNIEnv.CallNonvirtualObjectMethod and JNIEnv.CallStaticObjectMethod methods return an IntPtr which contains a JNI local reference to a Java object, or IntPtr.Zero if Java returned null. 由于有限数量的本地 (512 个条目),它是需要确保引用后,可以是在未完成的引用会及时删除。Due to the limited number of local references that can be outstanding at once (512 entries), it is desirable to ensure that the references are deleted in a timely fashion. 有三种本地引用可处理的方法: 显式删除它们,创建Java.Lang.Object实例,用于保存它们,并使用Java.Lang.Object.GetObject<T>()创建针对这些托管的可调用包装器。There are three ways that local references can be dealt with: explicitly deleting them, creating a Java.Lang.Object instance to hold them, and using Java.Lang.Object.GetObject<T>() to create a managed callable wrapper around them.

显式删除本地引用Explicitly Deleting Local References

JNIEnv.DeleteLocalRef用于删除本地引用。JNIEnv.DeleteLocalRef is used to delete local references. 一旦本地引用已被删除,就不能使用它,因此必须格外小心,确保JNIEnv.DeleteLocalRef是通过本地引用的最后一步。Once the local reference has been deleted, it cannot be used anymore, so care must be taken to ensure that JNIEnv.DeleteLocalRef is the last thing done with the local reference.

IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
try {
    // Do something with `lref`
}
finally {
    JNIEnv.DeleteLocalRef (lref);
}

使用 Java.Lang.Object 包装Wrapping with Java.Lang.Object

Java.Lang.Object 提供了Java.Lang.Object (IntPtr 句柄,JniHandleOwnership 传输)构造函数可以用来包装现有的 JNI 引用它。Java.Lang.Object provides a Java.Lang.Object(IntPtr handle, JniHandleOwnership transfer) constructor which can be used to wrap an exiting JNI reference. JniHandleOwnership参数确定如何IntPtr应视为参数:The JniHandleOwnership parameter determines how the IntPtr parameter should be treated:

  • JniHandleOwnership.DoNotTransfer –创建Java.Lang.Object实例中将创建新的全局引用handle参数,和handle保持不变。JniHandleOwnership.DoNotTransfer – The created Java.Lang.Object instance will create a new global reference from the handle parameter, and handle is unchanged. 调用方负责释放到handle,如果有必要。The caller is responsible to freeing handle , if necessary.

  • JniHandleOwnership.TransferLocalRef –创建Java.Lang.Object实例中将创建新的全局引用handle参数,并且handle使用删除JNIEnv.DeleteLocalRef .JniHandleOwnership.TransferLocalRef – The created Java.Lang.Object instance will create a new global reference from the handle parameter, and handle is deleted with JNIEnv.DeleteLocalRef . 调用方必须释放handle,并且必须使用handle构造函数完成执行之后。The caller must not free handle , and must not use handle after the constructor finishes executing.

  • JniHandleOwnership.TransferGlobalRef –创建Java.Lang.Object实例将接管其所有权的handle参数。JniHandleOwnership.TransferGlobalRef – The created Java.Lang.Object instance will take over ownership of the handle parameter. 调用方必须释放handleThe caller must not free handle .

因为 JNI 方法调用方法返回本地 refsJniHandleOwnership.TransferLocalRef通常使用:Since the JNI method invocation methods return local refs, JniHandleOwnership.TransferLocalRef would normally be used:

IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef);

前创建的全局引用将不会释放Java.Lang.Object实例进行垃圾回收。The created global reference will not be freed until the Java.Lang.Object instance is garbage collected. 如果可以,则释放该实例将释放全局引用,加快垃圾回收:If you are able to, disposing of the instance will free up the global reference, speeding up garbage collections:

IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
using (var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef)) {
    // use value ...
}

Using Java.Lang.Object.GetObject<T>()Using Java.Lang.Object.GetObject<T>()

Java.Lang.Object 提供了Java.Lang.Object.GetObject<T>(IntPtr 句柄,JniHandleOwnership 传输)方法,可以用来创建指定类型的托管可调用包装器。Java.Lang.Object provides a Java.Lang.Object.GetObject<T>(IntPtr handle, JniHandleOwnership transfer) method that can be used to create a managed callable wrapper of the specified type.

类型T必须满足以下要求:The type T must fulfill the following requirements:

  1. T 必须是引用类型。T must be a reference type.

  2. T 必须实现IJavaObject接口。T must implement the IJavaObject interface.

  3. 如果T不是抽象类或接口,然后T必须为一个构造函数提供的参数类型(IntPtr, JniHandleOwnership)If T is not an abstract class or interface, then T must provide a constructor with the parameter types (IntPtr, JniHandleOwnership) .

  4. 如果T是一个抽象类或接口,有必须调用程序可用于TIf T is an abstract class or an interface, there must be an invoker available for T . 调用程序是一种非抽象类型,继承T或实现T,并且具有相同的名称作为T与调用程序后缀。An invoker is a non-abstract type that inherits T or implements T , and has the same name as T with an Invoker suffix. 例如,如果 T 是接口Java.Lang.IRunnable,然后键入Java.Lang.IRunnableInvoker必须存在,并且必须包含所需(IntPtr, JniHandleOwnership)构造函数。For example, if T is the interface Java.Lang.IRunnable , then the type Java.Lang.IRunnableInvoker must exist and must contain the required (IntPtr, JniHandleOwnership) constructor.

因为 JNI 方法调用方法返回本地 refsJniHandleOwnership.TransferLocalRef通常使用:Since the JNI method invocation methods return local refs, JniHandleOwnership.TransferLocalRef would normally be used:

IntPtr lrefString = JNIEnv.CallObjectMethod(instance, methodID);
Java.Lang.String value = Java.Lang.Object.GetObject<Java.Lang.String>( lrefString, JniHandleOwnership.TransferLocalRef);

查找 Java 类型Looking up Java Types

若要查找的字段或方法中 JNI,字段或方法的声明类型必须查找第一个。To lookup a field or method in JNI, the declaring type for the field or method must be looked up first. Android.Runtime.JNIEnv.FindClass(string)方法用于查找 Java 类型。The Android.Runtime.JNIEnv.FindClass(string) method is used to lookup Java types. 字符串参数是简化类型引用完整类型引用Java 类型。The string parameter is the simplified type reference or the full type reference for the Java type. 请参阅JNI 类型参考资料部分有关简化和完整的类型引用的详细信息。See the JNI Type References section for details about simplified and full type references.

注意:与其他所有不同JNIEnv方法可返回对象实例FindClass返回全局引用,而不是本地引用。Note: Unlike every other JNIEnv method which returns object instances, FindClass returns a global reference, not a local reference.

实例字段Instance Fields

通过操作时字段字段 IdFields are manipulated through field IDs. 通过获取的字段 Id JNIEnv.GetFieldID,这需要在类的字段定义中的字段的名称和JNI 类型签名的字段。Field IDs are obtained via JNIEnv.GetFieldID, which requires the class that the field is defined in, the name of the field, and the JNI Type Signature of the field.

字段 Id 不需要释放,并且有效,只要加载相应的 Java 类型。Field IDs do not need to be freed, and are valid as long as the corresponding Java type is loaded. (android 不当前不支持卸载类。)(Android does not currently support class unloading.)

有两个组的操作实例字段的方法: 一个用于读取实例字段,另一个用于编写实例字段。There are two sets of methods for manipulating instance fields: one for reading instance fields and one for writing instance fields. 所有组方法都需要读取或写入的字段值的字段 ID。All sets of methods require a field ID to read or write the field value.

读取实例字段值Reading Instance Field Values

组用于读取实例字段值的方法遵循命名模式:The set of methods for reading instance field values follows the naming pattern:

* JNIEnv.Get*Field(IntPtr instance, IntPtr fieldID);

其中*是字段的类型:where * is the type of the field:

正在写入实例字段值Writing Instance Field Values

组用于编写实例字段值的方法遵循命名模式:The set of methods for writing instance field values follows the naming pattern:

JNIEnv.SetField(IntPtr instance, IntPtr fieldID, Type value);

其中类型是字段的类型:where Type is the type of the field:

  • JNIEnv.SetField –编写的并不是内置类型,如任何字段值java.lang.Object,数组和接口类型。JNIEnv.SetField – Write the value of any field that isn't a builtin type, such as java.lang.Object , arrays, and interface types. IntPtr值可能为 JNI 本地引用、 JNI 全局引用、 JNI 弱全局引用,或IntPtr.Zero(对于null)。The IntPtr value may be a JNI local reference, JNI global reference, JNI weak global reference, or IntPtr.Zero (for null ).

  • JNIEnv.SetField –的值写入bool实例字段。JNIEnv.SetField – Write the value of bool instance fields.

  • JNIEnv.SetField –的值写入sbyte实例字段。JNIEnv.SetField – Write the value of sbyte instance fields.

  • JNIEnv.SetField –的值写入char实例字段。JNIEnv.SetField – Write the value of char instance fields.

  • JNIEnv.SetField –的值写入short实例字段。JNIEnv.SetField – Write the value of short instance fields.

  • JNIEnv.SetField –的值写入int实例字段。JNIEnv.SetField – Write the value of int instance fields.

  • JNIEnv.SetField –的值写入long实例字段。JNIEnv.SetField – Write the value of long instance fields.

  • JNIEnv.SetField –的值写入float实例字段。JNIEnv.SetField – Write the value of float instance fields.

  • JNIEnv.SetField –的值写入double实例字段。JNIEnv.SetField – Write the value of double instance fields.

静态字段Static Fields

通过操作时的静态字段字段 IdStatic Fields are manipulated through field IDs. 通过获取的字段 Id JNIEnv.GetStaticFieldID,这需要在类的字段定义中的字段的名称和JNI 类型签名的字段。Field IDs are obtained via JNIEnv.GetStaticFieldID, which requires the class that the field is defined in, the name of the field, and the JNI Type Signature of the field.

字段 Id 不需要释放,并且有效,只要加载相应的 Java 类型。Field IDs do not need to be freed, and are valid as long as the corresponding Java type is loaded. (android 不当前不支持卸载类。)(Android does not currently support class unloading.)

有两个组的操作的静态字段的方法: 一个用于读取实例字段,另一个用于编写实例字段。There are two sets of methods for manipulating static fields: one for reading instance fields and one for writing instance fields. 所有组方法都需要读取或写入的字段值的字段 ID。All sets of methods require a field ID to read or write the field value.

读取静态字段值Reading Static Field Values

组用于读取静态字段值的方法遵循命名模式:The set of methods for reading static field values follows the naming pattern:

* JNIEnv.GetStatic*Field(IntPtr class, IntPtr fieldID);

其中*是字段的类型:where * is the type of the field:

正在写入静态字段值Writing Static Field Values

组用于写入静态字段值的方法遵循命名模式:The set of methods for writing static field values follows the naming pattern:

JNIEnv.SetStaticField(IntPtr class, IntPtr fieldID, Type value);

其中类型是字段的类型:where Type is the type of the field:

实例方法Instance Methods

通过调用实例方法方法 IdInstance Methods are invoked through method IDs. 通过获取的方法 Id JNIEnv.GetMethodID,其需要的类型,该方法定义的方法名称中和JNI 类型签名的方法。Method IDs are obtained via JNIEnv.GetMethodID, which requires the type that the method is defined in, the name of the method, and the JNI Type Signature of the method.

方法 Id 不需要释放,并且有效,只要加载相应的 Java 类型。Method IDs do not need to be freed, and are valid as long as the corresponding Java type is loaded. (android 不当前不支持卸载类。)(Android does not currently support class unloading.)

有两组用于调用方法的方法: 一个用于几乎,调用方法,一个用于非虚拟调用方法。There are two sets of methods for invoking methods: one for invoking methods virtually, and one for invoking methods non-virtually. 这两个组方法需要一个方法 ID 来调用的方法和非虚拟调用还要求您指定应调用哪个类实现。Both sets of methods require a method ID to invoke the method, and non-virtual invocation also requires that you specify which class implementation should be invoked.

接口方法内的声明类型; 仅按键查找不能查找来自扩展/继承接口的方法。Interface methods can only be looked up within the declaring type; methods that come from extended/inherited interfaces cannot be looked up. 请参阅后续绑定接口 / 调用程序实现部分,了解更多详细信息。See the later Binding Interfaces / Invoker Implementation section for more details.

在类中声明的任何方法或可以查找任何基类或实现的接口。Any method declared in the class or any base class or implemented interface can be looked up.

虚拟方法调用Virtual Method Invocation

组用于调用方法的方法几乎遵循命名模式:The set of methods for invoking methods virtually follows the naming pattern:

* JNIEnv.Call*Method( IntPtr instance, IntPtr methodID, params JValue[] args );

其中*是该方法的返回类型。where * is the return type of the method.

非虚拟方法调用Non-virtual Method Invocation

方法用于调用方法的一非几乎遵循命名模式:The set of methods for invoking methods non-virtually follows the naming pattern:

* JNIEnv.CallNonvirtual*Method( IntPtr instance, IntPtr class, IntPtr methodID, params JValue[] args );

其中*是该方法的返回类型。where * is the return type of the method. 非虚拟方法调用通常用于调用虚方法的基方法。Non-virtual method invocation is usually used to invoke the base method of a virtual method.

静态方法Static Methods

通过调用静态方法方法 IdStatic Methods are invoked through method IDs. 通过获取的方法 Id JNIEnv.GetStaticMethodID,其需要的类型,该方法定义的方法名称中和JNI 类型签名的方法。Method IDs are obtained via JNIEnv.GetStaticMethodID, which requires the type that the method is defined in, the name of the method, and the JNI Type Signature of the method.

方法 Id 不需要释放,并且有效,只要加载相应的 Java 类型。Method IDs do not need to be freed, and are valid as long as the corresponding Java type is loaded. (android 不当前不支持卸载类。)(Android does not currently support class unloading.)

静态方法调用Static Method Invocation

组用于调用方法的方法几乎遵循命名模式:The set of methods for invoking methods virtually follows the naming pattern:

* JNIEnv.CallStatic*Method( IntPtr class, IntPtr methodID, params JValue[] args );

其中*是该方法的返回类型。where * is the return type of the method.

JNI 类型签名JNI Type Signatures

JNI 类型签名JNI 类型引用(但不简化的类型引用) 的方法除外。JNI Type Signatures are JNI Type References (though not simplified type references), except for methods. 使用方法时,JNI 类型签名是左括号'('后, 跟所有类型连接在一起 (不带不分隔开来将以逗号分隔或任何其他内容) 后, 跟右括号的参数的类型引用')',后跟方法返回类型的 JNI 类型引用。With methods, the JNI Type Signature is an open parenthesis '(', followed by the type references for all of the parameter types concatenated together (with no separating commas or anything else), followed by a closing parenthesis ')', followed by the JNI type reference of the method return type.

例如,对于给定的 Java 方法:For example, given the Java method:

long f(int n, String s, int[] array);

将 JNI 类型签名:The JNI type signature would be:

(ILjava/lang/String;[I)J

一般情况下,它是建议使用javap命令,以确定 JNI 签名。In general, it is strongly recommended to use the javap command to determine JNI signatures. 例如,JNI 类型的签名java.lang.Thread.State.valueOf(String)方法是"(lang/Ljava/字符串;) Ljava/lang/线程$ 状态;",而 JNI 键入的签名java.lang.Thread.State.values方法是"() [Ljava/lang/线程$ 状态;"。For example, the JNI Type Signature of the java.lang.Thread.State.valueOf(String) method is "(Ljava/lang/String;)Ljava/lang/Thread$State;", while the JNI Type Signature of the java.lang.Thread.State.values method is "()[Ljava/lang/Thread$State;". 尾随分号; 请注意这些JNI 类型签名的一部分。Watch out for the trailing semicolons; those are part of the JNI type signature.

JNI 类型引用JNI Type References

从 Java 类型引用不同 JNI 类型引用。JNI type references are different from Java type references. 不能使用完全限定的 Java 类型名称,例如java.lang.String使用 JNI,必须改为使用 JNI 变体"java/lang/String""Ljava/lang/String;",具体取决于上下文; 请参阅下面有关详细信息。You cannot use fully qualified Java type names such as java.lang.String with JNI, you must instead use the JNI variations "java/lang/String" or "Ljava/lang/String;", depending on context; see below for details. 有四种类型的 JNI 类型引用:There are four types of JNI type references:

  • built-inbuilt-in
  • simplifiedsimplified
  • typetype
  • arrayarray

内置类型的引用Built-in Type References

内置类型的引用是用来引用内置值类型的单个字符。Built-in type references are a single character, used to reference built-in value types. 映射为按如下所示:The mapping is as follows:

  • "B" 有关sbyte"B" for sbyte .
  • "S" 有关short"S" for short .
  • "I" 有关int"I" for int .
  • "J" 有关long"J" for long .
  • "F" 有关float"F" for float .
  • "D" 有关double"D" for double .
  • "C" 有关char"C" for char .
  • "Z" 有关bool"Z" for bool .
  • "V" 有关void方法返回类型。"V" for void method return types.

简化的类型引用Simplified Type References

简化的类型引用仅可在JNIEnv.FindClass(string)Simplified type references can only be used in JNIEnv.FindClass(string). 有两种方法来派生简单的类型引用:There are two ways to derive a simplified type reference:

  1. 从完全限定的 Java 名称,将为每个'.'中的包名称和类型名与之前'/',和每个'.'内类型名与'$'From a fully-qualified Java name, replace every '.' within the package name and before the type name with '/' , and every '.' within a type name with '$' .

  2. 读取输出的'unzip -l android.jar | grep JavaName'Read the output of 'unzip -l android.jar | grep JavaName' .

二者会导致 Java 类型java.lang.Thread.State映射到的简化的类型引用java/lang/Thread$StateEither of the two will result in the Java type java.lang.Thread.State being mapped to the simplified type reference java/lang/Thread$State.

类型引用Type References

类型引用为内置类型引用或使用简化的类型引用'L'前缀和一个';'后缀。A type reference is a built-in type reference or a simplified type reference with an 'L' prefix and a ';' suffix. Java 类型java.lang.String,则简化的类型引用的是"java/lang/String",而类型引用为"Ljava/lang/String;"For the Java type java.lang.String, the simplified type reference is "java/lang/String", while the type reference is "Ljava/lang/String;".

与数组类型引用和使用 JNI 签名使用类型引用。Type references are used with Array type references and with JNI Signatures.

若要获取的类型引用另一种方式是通过读取的输出'javap -s -classpath android.jar fully.qualified.Java.Name'An additional way to obtain a type reference is by reading the output of 'javap -s -classpath android.jar fully.qualified.Java.Name'. 具体取决于类型涉及到,您可以使用构造函数声明或方法返回类型,以确定 JNI 名称。Depending on the type involved, you can use a constructor declaration or method return type to determine the JNI name. 例如:For example:

$ javap -classpath android.jar -s java.lang.Thread.State
Compiled from "Thread.java"
public final class java.lang.Thread$State extends java.lang.Enum{
public static final java.lang.Thread$State NEW;
  Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State RUNNABLE;
  Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State BLOCKED;
  Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State WAITING;
  Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State TIMED_WAITING;
  Signature: Ljava/lang/Thread$State;
public static final java.lang.Thread$State TERMINATED;
  Signature: Ljava/lang/Thread$State;
public static java.lang.Thread$State[] values();
  Signature: ()[Ljava/lang/Thread$State;
public static java.lang.Thread$State valueOf(java.lang.String);
  Signature: (Ljava/lang/String;)Ljava/lang/Thread$State;
static {};
  Signature: ()V
}

Thread.State 因此,我们可以使用的签名是 Java 枚举类型,valueOf方法确定的类型引用为 Ljava/lang/线程$ 状态;。Thread.State is a Java enum type, so we can use the Signature of the valueOf method to determine that the type reference is Ljava/lang/Thread$State;.

数组类型引用Array Type References

数组类型引用'['JNI 类型引用的前缀。Array type references are '[' prefixed to a JNI type reference. 指定数组时,不能使用简化的类型引用。Simplified type references cannot be used when specifying arrays.

例如,int[]"[I"int[][]"[[I",并java.lang.Object[]"[Ljava/lang/Object;"For example, int[] is "[I", int[][] is "[[I", and java.lang.Object[] is "[Ljava/lang/Object;".

Java 泛型和类型擦除Java Generics and Type Erasure

大多数情况下,通过 JNI,Java 泛型所示不存在Most of the time, as seen through JNI, Java generics do not exist. 有一些"褶皱,",但这些褶皱正在 Java 如何与泛型,不使用 JNI 如何查找和调用泛型成员进行交互。There are some "wrinkles," but those wrinkles are in how Java interacts with generics, not with how JNI looks up and invokes generic members.

通过 JNI 进行交互时的泛型类型或成员和非泛型类型或成员之间没有差异。There is no difference between a generic type or member and a non-generic type or member when interacting through JNI. 例如,泛型类型java.lang.Class<T> 也是"原始"的泛型类型java.lang.Class,这两个具有相同的简化的类型引用, "java/lang/Class"For example, the generic type java.lang.Class<T> is also the "raw" generic type java.lang.Class, both of which have the same simplified type reference, "java/lang/Class".

Java 本机接口支持Java Native Interface Support

Android.Runtime.JNIEnv是 Jave 本机接口 (JNI) 有关的托管的包装。Android.Runtime.JNIEnv is managed wrapper for the Jave Native Interface (JNI). 内声明的 JNI 函数Java 本机接口规范中使用,但方法已更改,以删除的显式JNIEnv*参数和IntPtr而不是使用jobjectjclassjmethodID,等等。例如,考虑JNI NewObject 函数:JNI Functions are declared within the Java Native Interface Specification, though the methods have been changed to remove the explicit JNIEnv* parameter and IntPtr is used instead of jobject, jclass, jmethodID, etc. For example, consider the JNI NewObject function:

jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);

这作为公开JNIEnv.NewObject方法:This is exposed as the JNIEnv.NewObject method:

public static IntPtr NewObject(IntPtr clazz, IntPtr jmethod, params JValue[] parms);

将两个调用之间的转换是相当简单。Translating between the two calls is reasonably straightforward. 在 C 中您必须:In C you would have:

jobject CreateMapActivity(JNIEnv *env)
{
    jclass    Map_Class   = (*env)->FindClass(env, "mono/samples/googlemaps/MyMapActivity");
    jmethodID Map_defCtor = (*env)->GetMethodID (env, Map_Class, "<init>", "()V");
    jobject   instance    = (*env)->NewObject (env, Map_Class, Map_defCtor);

    return instance;
}

C#等效将是:The C# equivalent would be:

IntPtr CreateMapActivity()
{
    IntPtr Map_Class   = JNIEnv.FindClass ("mono/samples/googlemaps/MyMapActivity");
    IntPtr Map_defCtor = JNIEnv.GetMethodID (Map_Class, "<init>", "()V");
    IntPtr instance    = JNIEnv.NewObject (Map_Class, Map_defCtor);

    return instance;
}

保存在一个 IntPtr 的 Java 对象实例后,可能需要对其执行操作。Once you have a Java Object instance held in an IntPtr, you'll probably want to do something with it. 您可以使用 JNIEnv 方法,如 JNIEnv.CallVoidMethod() 若要执行此操作,但如果已存在相似之处C#包装器,则你将想要通过 JNI 引用构造一个包装器。You can use JNIEnv methods such as JNIEnv.CallVoidMethod() to do so, but if there is already an analogue C# wrapper then you'll want to construct a wrapper over the JNI reference. 可以通过执行Extensions.JavaCast () 扩展方法:You can do so through the Extensions.JavaCast () extension method:

IntPtr lrefActivity = CreateMapActivity();

// imagine that Activity were instead an interface or abstract type...
Activity mapActivity = new Java.Lang.Object(lrefActivity, JniHandleOwnership.TransferLocalRef)
    .JavaCast<Activity>();

此外可以使用Java.Lang.Object.GetObject () 方法:You can also use the Java.Lang.Object.GetObject () method:

IntPtr lrefActivity = CreateMapActivity();

// imagine that Activity were instead an interface or abstract type...
Activity mapActivity = Java.Lang.Object.GetObject<Activity>(lrefActivity, JniHandleOwnership.TransferLocalRef);

此外,所有的 JNI 函数已被修改的删除JNIEnv*参数中的每个 JNI 函数存在。Furthermore, all of the JNI functions have been modified by removing the JNIEnv* parameter present in every JNI function.

总结Summary

直接使用 JNI 处理是一种可怕的体验,应不惜一切代价避免使用。Dealing directly with JNI is a terrible experience that should be avoided at all costs. 遗憾的是,它并不总是能够避免;希望本指南适用于 Android 命中与 Mono 的未绑定的 Java 用例时将提供一些帮助。Unfortunately, it's not always avoidable; hopefully this guide will provide some assistance when you hit the unbound Java cases with Mono for Android.