Bagikan melalui


Bekerja dengan JNI dan Xamarin.Android

Xamarin.Android mengizinkan penulisan aplikasi Android dengan C# alih-alih Java. Beberapa rakitan disediakan dengan Xamarin.Android yang menyediakan pengikatan untuk pustaka Java, termasuk Mono.Android.dll dan Mono.Android.Google Peta.dll. Namun, pengikatan tidak disediakan untuk setiap kemungkinan pustaka Java, dan pengikatan yang disediakan mungkin tidak mengikat setiap jenis dan anggota Java. Untuk menggunakan jenis dan anggota Java yang tidak terikat, Java Native Interface (JNI) dapat digunakan. Artikel ini menggambarkan cara menggunakan JNI untuk berinteraksi dengan jenis Java dan anggota dari aplikasi Xamarin.Android.

Gambaran Umum

Tidak selalu diperlukan atau dimungkinkan untuk membuat Managed Callable Wrapper (MCW) untuk memanggil kode Java. Dalam banyak kasus, JNI "sebaris" sangat dapat diterima dan berguna untuk penggunaan satu kali anggota Java yang tidak terikat. Seringkali lebih mudah menggunakan JNI untuk memanggil satu metode pada kelas Java daripada menghasilkan pengikatan seluruh .jar.

Xamarin.Android menyediakan assembly Mono.Android.dll , yang menyediakan pengikatan untuk pustaka Android android.jar . Jenis dan anggota yang tidak ada di dalam Mono.Android.dll dan jenis yang tidak ada android.jar dapat digunakan dengan mengikatnya secara manual. Untuk mengikat jenis dan anggota Java, Anda menggunakan Java Native Interface (JNI) untuk mencari jenis, membaca dan menulis bidang, dan memanggil metode.

JNI API di Xamarin.Android secara konseptual sangat mirip dengan System.Reflection API di .NET: memungkinkan Anda untuk mencari jenis dan anggota berdasarkan nama, membaca dan menulis nilai bidang, memanggil metode, dan banyak lagi. Anda dapat menggunakan JNI dan Android.Runtime.RegisterAttribute atribut kustom untuk mendeklarasikan metode virtual yang dapat terikat untuk mendukung penimpaan. Anda dapat mengikat antarmuka sehingga dapat diimplementasikan di C#.

Dokumen ini menjelaskan:

  • Bagaimana JNI mengacu pada jenis.
  • Cara mencari, membaca, dan menulis bidang.
  • Cara mencari dan memanggil metode.
  • Cara mengekspos metode virtual untuk memungkinkan penimpaan dari kode terkelola.
  • Cara mengekspos antarmuka.

Persyaratan

JNI, seperti yang diekspos melalui namespace Android.Runtime.JNIEnv, tersedia di setiap versi Xamarin.Android. Untuk mengikat jenis dan antarmuka Java, Anda harus menggunakan Xamarin.Android 4.0 atau yang lebih baru.

Pembungkus yang Dapat Dipanggil Terkelola

Managed Callable Wrapper (MCW) adalah pengikatan untuk kelas atau antarmuka Java yang membungkus semua mesin JNI sehingga kode C# klien tidak perlu khawatir tentang kompleksitas JNI yang mendasar. Mono.Android.dll Sebagian besar terdiri dari pembungkus yang dapat dipanggil terkelola.

Pembungkus yang dapat dipanggil terkelola melayani dua tujuan:

  1. Merangkum penggunaan JNI sehingga kode klien tidak perlu tahu tentang kompleksitas yang mendasarinya.
  2. Memungkinkan untuk sub-kelas jenis Java dan mengimplementasikan antarmuka Java.

Tujuan pertama adalah murni untuk kenyamanan dan enkapsulasi kompleksitas sehingga konsumen memiliki serangkaian kelas yang sederhana dan dikelola untuk digunakan. Ini memerlukan penggunaan berbagai anggota JNIEnv seperti yang dijelaskan nanti dalam artikel ini. Perlu diingat bahwa pembungkus yang dapat dipanggil terkelola tidak benar-benar diperlukan - penggunaan JNI "sebaris" dapat diterima dengan sempurna dan berguna untuk penggunaan satu kali dari anggota Java yang tidak terikat. Implementasi sub-kelas dan antarmuka memerlukan penggunaan pembungkus yang dapat dipanggil terkelola.

Pembungkus Yang Dapat Dipanggil Android

Pembungkus yang dapat dipanggil Android (ACW) diperlukan setiap kali runtime Android (ART) perlu memanggil kode terkelola; pembungkus ini diperlukan karena tidak ada cara untuk mendaftarkan kelas dengan ART saat runtime. (Khususnya, Fungsi DefineClass JNI tidak didukung oleh runtime Android. Pembungkus yang dapat dipanggil Android sehingga menebus kurangnya dukungan pendaftaran jenis runtime.)

Setiap kali kode Android perlu menjalankan metode virtual atau antarmuka yang ditimpa atau diimplementasikan dalam kode terkelola, Xamarin.Android harus menyediakan proksi Java sehingga metode ini dikirim ke jenis terkelola yang sesuai. Jenis proksi Java ini adalah kode Java yang memiliki kelas dasar "sama" dan daftar antarmuka Java sebagai jenis terkelola, menerapkan konstruktor yang sama dan mendeklarasikan kelas dasar dan metode antarmuka yang ditimpa.

Pembungkus yang dapat dipanggil Android dihasilkan oleh program monodroid.exe selama proses build, dan dihasilkan untuk semua jenis yang (secara langsung atau tidak langsung) mewarisi Java.Lang.Object.

Menerapkan Antarmuka

Ada kalanya Anda mungkin perlu menerapkan antarmuka Android, (seperti Android.Content.IComponentCallbacks).

Semua kelas dan antarmuka Android memperluas antarmuka Android.Runtime.IJavaObject ; oleh karena itu, semua jenis Android harus menerapkan IJavaObject. Xamarin.Android memanfaatkan fakta ini - menggunakan untuk menyediakan proksi Java kepada Android (pembungkus IJavaObject yang dapat dipanggil Android) untuk jenis terkelola yang diberikan. Karena monodroid.exe hanya mencari Java.Lang.Object subkelas (yang harus mengimplementasikan IJavaObject), subkelas Java.Lang.Object memberi kami cara untuk menerapkan antarmuka dalam kode terkelola. Contohnya:

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

Detail Implementasi

Sisa artikel ini memberikan detail implementasi yang dapat berubah tanpa pemberitahuan (dan disajikan di sini hanya karena pengembang mungkin ingin tahu tentang apa yang terjadi di bawah tenda).

Misalnya, mengingat sumber C# berikut:

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

Program mandroid.exe akan menghasilkan Android Callable Wrapper berikut:

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

Perhatikan bahwa kelas dasar dipertahankan, dan deklarasi metode asli disediakan untuk setiap metode yang ditimpa dalam kode terkelola.

ExportAttribute dan ExportFieldAttribute

Biasanya, Xamarin.Android secara otomatis menghasilkan kode Java yang terdiri dari ACW; generasi ini didasarkan pada kelas dan nama metode ketika kelas berasal dari kelas Java dan mengambil alih metode Java yang ada. Namun, dalam beberapa skenario, pembuatan kode tidak memadai, seperti yang diuraikan di bawah ini:

  • Android mendukung nama tindakan dalam atribut XML tata letak, misalnya atribut XML android:onClick . Ketika ditentukan, instans Tampilan yang ditingkatkan mencoba mencari metode Java.

  • Antarmuka java.io.Serializable memerlukan readObject metode dan writeObject . Karena mereka bukan anggota antarmuka ini, implementasi terkelola kami yang sesuai tidak mengekspos metode ini ke kode Java.

  • Antarmuka android.os.Parcelable mengharapkan bahwa kelas implementasi harus memiliki bidang CREATOR statis jenis Parcelable.Creator. Kode Java yang dihasilkan memerlukan beberapa bidang eksplisit. Dengan skenario standar kami, tidak ada cara untuk menghasilkan bidang dalam kode Java dari kode terkelola.

Karena pembuatan kode tidak memberikan solusi untuk menghasilkan metode Java arbitrer dengan nama arbitrer, dimulai dengan Xamarin.Android 4.2, ExportAttribute dan ExportFieldAttribute diperkenalkan untuk menawarkan solusi untuk skenario di atas. Kedua atribut berada di Java.Interop namespace:

  • ExportAttribute – menentukan nama metode dan jenis pengecualian yang diharapkan (untuk memberikan "lemparan" eksplisit di Java). Ketika digunakan pada metode , metode akan "mengekspor" metode Java yang menghasilkan kode pengiriman ke pemanggilan JNI yang sesuai ke metode terkelola. Ini dapat digunakan dengan android:onClick dan java.io.Serializable.

  • ExportFieldAttribute – menentukan nama bidang. Ini berada pada metode yang berfungsi sebagai penginisialisasi bidang. Ini dapat digunakan dengan android.os.Parcelable.

Pemecahan Masalah ExportAttribute dan ExportFieldAttribute

  • Pengemasan gagal karena Mono.Android.Export.dll hilang – jika Anda menggunakan ExportAttribute atau ExportFieldAttribute pada beberapa metode dalam kode atau pustaka dependen, Anda harus menambahkan Mono.Android.Export.dll. Rakitan ini diisolasi untuk mendukung kode panggilan balik dari Java. Ini terpisah dari Mono.Android.dll karena menambahkan ukuran tambahan ke aplikasi.

  • Dalam Build rilis, MissingMethodException terjadi untuk metode Ekspor – Dalam build Rilis, MissingMethodException terjadi untuk metode Ekspor. (Masalah ini diperbaiki dalam versi terbaru Xamarin.Android.)

ExportParameterAttribute

ExportAttribute dan ExportFieldAttribute menyediakan fungsionalitas yang dapat digunakan kode run-time Java. Kode run-time ini mengakses kode terkelola melalui metode JNI yang dihasilkan yang didorong oleh atribut tersebut. Akibatnya, tidak ada metode Java yang mengikat metode terkelola; oleh karena itu, metode Java dihasilkan dari tanda tangan metode terkelola.

Namun, kasus ini tidak sepenuhnya menentukan. Terutama, ini berlaku dalam beberapa pemetaan tingkat lanjut antara jenis terkelola dan jenis Java seperti:

  • InputStream
  • OutputStream
  • XmlPullParser
  • XmlResourceParser

Ketika jenis seperti ini diperlukan untuk metode yang diekspor, ExportParameterAttribute harus digunakan untuk secara eksplisit memberikan parameter yang sesuai atau nilai pengembalian jenis.

Atribut Anotasi

Di Xamarin.Android 4.2, kami mengonversi IAnnotation jenis implementasi menjadi atribut (System.Attribute), dan menambahkan dukungan untuk pembuatan anotasi dalam pembungkus Java.

Ini berarti perubahan arah berikut:

  • Generator pengikatan dihasilkan Java.Lang.DeprecatedAttribute dari java.Lang.Deprecated (sementara harus [Obsolete] dalam kode terkelola).

  • Ini tidak berarti bahwa kelas yang ada Java.Lang.Deprecated akan lenyap. Objek berbasis Java ini masih dapat digunakan sebagai objek Java biasa (jika penggunaan tersebut ada). Akan Deprecated ada kelas dan DeprecatedAttribute .

  • Kelas Java.Lang.DeprecatedAttribute ditandai sebagai [Annotation] . Ketika ada atribut kustom yang diwarisi dari atribut ini [Annotation] , tugas msbuild akan menghasilkan anotasi Java untuk atribut kustom tersebut (@Deprecated) di Android Callable Wrapper (ACW).

  • Anotasi dapat dihasilkan ke kelas, metode, dan bidang yang diekspor (yang merupakan metode dalam kode terkelola).

Jika kelas yang berisi (kelas anotasi itu sendiri, atau kelas yang berisi anggota anotasi) tidak terdaftar, seluruh sumber kelas Java tidak dihasilkan sama sekali, termasuk anotasi. Untuk metode, Anda dapat menentukan ExportAttribute untuk mendapatkan metode yang dihasilkan dan dianotasikan secara eksplisit. Selain itu, ini bukan fitur untuk "menghasilkan" definisi kelas anotasi Java. Dengan kata lain, jika Anda menentukan atribut terkelola kustom untuk anotasi tertentu, Anda harus menambahkan pustaka .jar lain yang berisi kelas anotasi Java yang sesuai. Menambahkan file sumber Java yang menentukan jenis anotasi tidak cukup. Pengkompilasi Java tidak berfungsi dengan cara yang sama seperti apt.

Selain itu, batasan berikut berlaku:

  • Proses konversi ini tidak mempertimbangkan @Target anotasi pada jenis anotasi sejauh ini.

  • Atribut ke properti tidak berfungsi. Gunakan atribut untuk properti getter atau setter sebagai gantinya.

Pengikatan Kelas

Mengikat kelas berarti menulis pembungkus yang dapat dipanggil terkelola untuk menyederhanakan pemanggilan jenis Java yang mendasar.

Mengikat metode virtual dan abstrak untuk mengizinkan penimpaan dari C# memerlukan Xamarin.Android 4.0. Namun, versi Xamarin.Android apa pun dapat mengikat metode non-virtual, metode statis, atau metode virtual tanpa mendukung penimpaan.

Pengikatan biasanya berisi item berikut:

Mendeklarasikan Handel Tipe

Metode pencarian bidang dan metode memerlukan referensi objek yang mengacu pada jenis deklarasinya. Menurut konvensi, ini diadakan class_ref di bidang:

static IntPtr class_ref = JNIEnv.FindClass(CLASS);

Lihat bagian Referensi Jenis JNI untuk detail tentang CLASS token.

Bidang Pengikatan

Bidang Java diekspos sebagai properti C#, misalnya bidang Java java.lang.System.in terikat sebagai properti C# Java.Lang.JavaSystem.In. Selain itu, karena JNI membedakan antara bidang statis dan bidang instans, metode yang berbeda digunakan saat mengimplementasikan properti.

Pengikatan bidang melibatkan tiga set metode:

  1. Metode dapatkan id bidang. Metode dapatkan id bidang bertanggung jawab untuk mengembalikan handel bidang yang akan digunakan nilai bidang get dan atur metode nilai bidang. Mendapatkan id bidang mengharuskan mengetahui jenis deklarasikan, nama bidang, dan tanda tangan jenis JNI bidang.

  2. Metode dapatkan nilai bidang. Metode ini memerlukan handel bidang dan bertanggung jawab untuk membaca nilai bidang dari Java. Metode yang digunakan tergantung pada jenis bidang.

  3. Metode nilai bidang yang ditetapkan. Metode ini memerlukan handel bidang dan bertanggung jawab untuk menulis nilai bidang dalam Java. Metode yang digunakan tergantung pada jenis bidang.

Bidang statis menggunakan metode JNIEnv.GetStaticFieldID, JNIEnv.GetStatic*Field, dan JNIEnv.SetStaticField .

Bidang instans menggunakan metode JNIEnv.GetFieldID, JNIEnv.Get*Field, dan JNIEnv.SetField .

Misalnya, properti JavaSystem.In statis dapat diimplementasikan sebagai:

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

Catatan: Kami menggunakan InputStreamInvoker.FromJniHandle untuk mengonversi referensi JNI menjadi System.IO.Stream instans, dan kami menggunakan JniHandleOwnership.TransferLocalRef karena JNIEnv.GetStaticObjectField mengembalikan referensi lokal.

Banyak dari jenis Android.Runtime memiliki FromJniHandle metode yang akan mengonversi referensi JNI menjadi jenis yang diinginkan.

Pengikatan Metode

Metode Java diekspos sebagai metode C# dan sebagai properti C#. Misalnya, metode Java java.lang.Runtime.runFinalizersOnExit terikat sebagai metode Java.Lang.Runtime.RunFinalizersOnExit, dan metode java.lang.Object.getClass terikat sebagai properti Java.Lang.Object.Class.

Pemanggilan metode adalah proses dua langkah:

  1. Id metode get untuk metode yang akan dipanggil. Metode id metode get bertanggung jawab untuk mengembalikan handel metode yang akan digunakan metode pemanggilan metode. Mendapatkan id metode mengharuskan mengetahui jenis deklarasikan, nama metode, dan tanda tangan jenis JNI dari metode .

  2. Panggil metodenya.

Sama seperti bidang, metode yang digunakan untuk mendapatkan id metode dan memanggil metode berbeda antara metode statis dan metode instans.

Metode statis menggunakan JNIEnv.GetStaticMethodID() untuk mencari id metode, dan menggunakan JNIEnv.CallStatic*Method keluarga metode untuk pemanggilan.

Metode instans menggunakan JNIEnv.GetMethodID untuk mencari id metode, dan menggunakan JNIEnv.Call*MethodJNIEnv.CallNonvirtual*Method dan keluarga metode untuk pemanggilan.

Pengikatan metode berpotensi lebih dari sekadar pemanggilan metode. Pengikatan metode juga termasuk memungkinkan metode untuk ditimpa (untuk metode abstrak dan non-final) atau diimplementasikan (untuk metode antarmuka). Bagian Warisan Pendukung, Antarmuka mencakup kompleksitas mendukung metode virtual dan metode antarmuka.

Metode Statis

Mengikat metode statis melibatkan penggunaan JNIEnv.GetStaticMethodID untuk mendapatkan handel metode, lalu menggunakan metode yang sesuai JNIEnv.CallStatic*Method , tergantung pada jenis pengembalian metode. Berikut ini adalah contoh pengikatan untuk metode Runtime.getRuntime :

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

Perhatikan bahwa kami menyimpan handel metode di bidang statis, id_getRuntime. Ini adalah pengoptimalan performa, sehingga handel metode tidak perlu dicari pada setiap pemanggilan. Tidak perlu menyimpan cache handel metode dengan cara ini. Setelah handel metode diperoleh, JNIEnv.CallStaticObjectMethod digunakan untuk memanggil metode . JNIEnv.CallStaticObjectMethod mengembalikan yang IntPtr berisi handel instans Java yang dikembalikan. Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) digunakan untuk mengonversi handel Java menjadi instans objek yang sangat ditik.

Pengikatan Metode Instans Non-Virtual

Mengikat final metode instans, atau metode instans yang tidak memerlukan penimpaan, melibatkan penggunaan JNIEnv.GetMethodID untuk mendapatkan handel metode, lalu menggunakan metode yang sesuai JNIEnv.Call*Method , tergantung pada jenis pengembalian metode. Berikut ini adalah contoh pengikatan untuk Object.Class properti:

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

Perhatikan bahwa kami menyimpan handel metode di bidang statis, id_getClass. Ini adalah pengoptimalan performa, sehingga handel metode tidak perlu dicari pada setiap pemanggilan. Tidak perlu menyimpan cache handel metode dengan cara ini. Setelah handel metode diperoleh, JNIEnv.CallStaticObjectMethod digunakan untuk memanggil metode . JNIEnv.CallStaticObjectMethod mengembalikan yang IntPtr berisi handel instans Java yang dikembalikan. Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) digunakan untuk mengonversi handel Java menjadi instans objek yang sangat ditik.

Konstruktor Pengikatan

Konstruktor adalah metode Java dengan nama "<init>". Sama seperti metode instans Java, JNIEnv.GetMethodID digunakan untuk mencari handel konstruktor. Tidak seperti metode Java, metode JNIEnv.NewObject digunakan untuk memanggil handel metode konstruktor. Nilai JNIEnv.NewObject pengembalian adalah referensi lokal JNI:

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…

Biasanya pengikatan kelas akan subkelas Java.Lang.Object. Saat subkelas Java.Lang.Object, semantik tambahan mulai dimainkan: Java.Lang.Object instans mempertahankan referensi global ke instans Java melalui Java.Lang.Object.Handle properti .

  1. Java.Lang.Object Konstruktor default akan mengalokasikan instans Java.

  2. Jika jenis memiliki RegisterAttribute , dan RegisterAttribute.DoNotGenerateAcw adalah true , maka instans jenis RegisterAttribute.Name dibuat melalui konstruktor defaultnya.

  3. Jika tidak, Android Callable Wrapper (ACW) yang sesuai this.GetType dengan dibuat melalui konstruktor defaultnya. Android Callable Wrappers dihasilkan selama pembuatan paket untuk setiap Java.Lang.Object subkelas yang RegisterAttribute.DoNotGenerateAcw tidak diatur ke true.

Untuk jenis yang bukan pengikatan kelas, ini adalah semantik yang diharapkan: membuat instans Mono.Samples.HelloWorld.HelloAndroid C# harus membuat instans Java mono.samples.helloworld.HelloAndroid yang merupakan Pembungkus Yang Dapat Dipanggil Android yang dihasilkan.

Untuk pengikatan kelas, ini mungkin perilaku yang benar jika jenis Java berisi konstruktor default dan/atau tidak ada konstruktor lain yang perlu dipanggil. Jika tidak, konstruktor harus disediakan yang melakukan tindakan berikut:

  1. Memanggil Java.Lang.Object(IntPtr, JniHandleOwnership) alih-alih konstruktor default Java.Lang.Object . Ini diperlukan untuk menghindari pembuatan instans Java baru.

  2. Periksa nilai Java.Lang.Object.Handle sebelum membuat instans Java apa pun. Properti Object.Handle akan memiliki nilai selain IntPtr.Zero jika Android Callable Wrapper dibangun dalam kode Java, dan pengikatan kelas sedang dibangun untuk berisi instans Android Callable Wrapper yang dibuat. Misalnya, ketika Android membuat instans mono.samples.helloworld.HelloAndroid , Android Callable Wrapper akan dibuat terlebih dahulu , dan konstruktor Java HelloAndroid akan membuat instans dari jenis yang Mono.Samples.HelloWorld.HelloAndroid sesuai, dengan Object.Handle properti diatur ke instans Java sebelum eksekusi konstruktor.

  3. Jika jenis runtime saat ini tidak sama dengan jenis deklarasikan, maka instans Android Callable Wrapper yang sesuai harus dibuat, dan gunakan Object.SetHandle untuk menyimpan handel yang dikembalikan oleh JNIEnv.CreateInstance.

  4. Jika jenis runtime saat ini sama dengan jenis deklarasikan, panggil konstruktor Java dan gunakan Object.SetHandle untuk menyimpan handel yang dikembalikan oleh JNIEnv.NewInstance .

Misalnya, pertimbangkan konstruktor java.lang.Integer(int ). Ini terikat sebagai:

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

Metode JNIEnv.CreateInstance adalah pembantu untuk melakukan JNIEnv.FindClass, , JNIEnv.GetMethodIDJNIEnv.NewObject, dan JNIEnv.DeleteGlobalReference pada nilai yang dikembalikan dari JNIEnv.FindClass. Lihat bagian berikutnya untuk detailnya.

Mendukung Warisan, Antarmuka

Subkelas jenis Java atau menerapkan antarmuka Java memerlukan pembuatan Android Callable Wrappers (ACW) yang dihasilkan untuk setiap Java.Lang.Object subkelas selama proses pengemasan. Pembuatan ACW dikontrol melalui atribut kustom Android.Runtime.RegisterAttribute .

Untuk jenis C#, [Register] konstruktor atribut kustom memerlukan satu argumen: referensi jenis JNI yang disederhanakan untuk jenis Java yang sesuai. Ini memungkinkan penyediaan nama yang berbeda antara Java dan C#.

Sebelum Xamarin.Android 4.0, [Register] atribut kustom tidak tersedia untuk "alias" jenis Java yang ada. Ini karena proses pembuatan ACW akan menghasilkan ACW untuk setiap Java.Lang.Object subkelas yang ditemui.

Xamarin.Android 4.0 memperkenalkan properti RegisterAttribute.DoNotGenerateAcw . Properti ini menginstruksikan proses pembuatan ACW untuk melewati jenis anotasi, memungkinkan deklarasi Pembungkus Yang Dapat Dipanggil Terkelola baru yang tidak akan mengakibatkan ACW dihasilkan pada waktu pembuatan paket. Ini memungkinkan pengikatan jenis Java yang ada. Misalnya, pertimbangkan kelas Java sederhana berikut, Adder, yang berisi satu metode, add, yang menambahkan ke bilangan bulat dan mengembalikan hasilnya:

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

Jenisnya Adder dapat diikat sebagai:

[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 {
}

Di sini, Adder jenis C# aliasAdder jenis Java. Atribut [Register] digunakan untuk menentukan nama JNI dari mono.android.test.Adder jenis Java, dan DoNotGenerateAcw properti digunakan untuk menghambat pembuatan ACW. Ini akan menghasilkan pembuatan ACW untuk ManagedAdder jenis , yang subkelas dengan benar jenisnya mono.android.test.Adder . RegisterAttribute.DoNotGenerateAcw Jika properti belum digunakan, maka proses build Xamarin.Android akan menghasilkan jenis Java barumono.android.test.Adder. Ini akan mengakibatkan kesalahan kompilasi, karena mono.android.test.Adder jenisnya akan ada dua kali, dalam dua file terpisah.

Mengikat Metode Virtual

ManagedAdder subkelas jenis Java Adder , tetapi tidak terlalu menarik: jenis C# Adder tidak menentukan metode virtual apa pun, sehingga ManagedAdder tidak dapat mengambil alih apa pun.

Metode pengikatan untuk mengizinkan pengesampingan virtual oleh subkelas memerlukan beberapa hal yang perlu dilakukan yang termasuk dalam dua kategori berikut:

  1. Pengikatan Metode

  2. Pendaftaran Metode

Pengikatan Metode

Pengikatan metode memerlukan penambahan dua anggota dukungan ke definisi C Adder #: ThresholdType, dan ThresholdClass.

ThresholdType

Properti ThresholdType mengembalikan jenis pengikatan saat ini:

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

ThresholdType digunakan dalam Pengikatan Metode untuk menentukan kapan metode harus melakukan pengiriman metode virtual vs. non-virtual. Ini harus selalu mengembalikan System.Type instans yang sesuai dengan jenis C# yang mendeklarasikan.

ThresholdClass

Properti ThresholdClass mengembalikan referensi kelas JNI untuk jenis terikat:

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

ThresholdClass digunakan dalam Pengikatan Metode saat memanggil metode non-virtual.

Implementasi Pengikatan

Implementasi pengikatan metode bertanggung jawab atas pemanggilan runtime metode Java. Ini juga berisi [Register] deklarasi atribut kustom yang merupakan bagian dari pendaftaran metode, dan akan dibahas di bagian Pendaftaran Metode:

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

Bidang id_add berisi ID metode untuk dipanggil oleh metode Java. Nilai id_add diperoleh dari JNIEnv.GetMethodID, yang memerlukan kelas deklarasikan (class_ref), nama metode Java ("add"), dan tanda tangan JNI dari metode ("(II)I").

Setelah ID metode diperoleh, GetType dibandingkan ThresholdType dengan menentukan apakah pengiriman virtual atau non-virtual diperlukan. Pengiriman virtual diperlukan ketika GetType cocok ThresholdType, seperti Handle yang dapat merujuk ke subkelas yang dialokasikan Java yang mengambil alih metode .

Ketika GetType tidak cocok ThresholdType, Adder telah disubkelas (misalnya oleh ManagedAdder), dan Adder.Add implementasi hanya akan dipanggil jika subkelas dipanggil base.Add. Ini adalah kasus pengiriman non-virtual, yang merupakan tempat ThresholdClass masuknya. ThresholdClass menentukan kelas Java mana yang akan menyediakan implementasi metode yang akan dipanggil.

Pendaftaran Metode

Asumsikan kita memiliki definisi yang diperbarui ManagedAdder yang mengambil Adder.Add alih metode:

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

Ingat bahwa Adder.Add memiliki [Register] atribut kustom:

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

[Register] Konstruktor atribut kustom menerima tiga nilai:

  1. Nama metode Java, "add" dalam hal ini.

  2. Tanda Tangan Jenis JNI dari metode , "(II)I" dalam hal ini.

  3. Metode konektor , GetAddHandler dalam hal ini. metode Koneksi atau akan dibahas nanti.

Dua parameter pertama memungkinkan proses pembuatan ACW menghasilkan deklarasi metode untuk mengambil alih metode. ACW yang dihasilkan akan berisi beberapa kode berikut:

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

Perhatikan bahwa @Override metode dideklarasikan, yang mendelegasikan ke n_metode -awalan dengan nama yang sama. Ini memastikan bahwa ketika kode Java memanggil ManagedAdder.add, ManagedAdder.n_add akan dipanggil, yang akan memungkinkan metode penimpaan C# ManagedAdder.Add dijalankan.

Dengan demikian, pertanyaan terpenting: bagaimana dikaitkan ManagedAdder.n_add dengan ManagedAdder.Add?

Metode Java native terdaftar dengan runtime Java (runtime Android) melalui fungsi JNI RegisterNatives. RegisterNatives mengambil array struktur yang berisi nama metode Java, Tanda Tangan Jenis JNI, dan penunjuk fungsi untuk dipanggil yang mengikuti konvensi panggilan JNI. Penunjuk fungsi harus berupa fungsi yang mengambil dua argumen penunjuk diikuti oleh parameter metode. Metode Java ManagedAdder.n_add harus diimplementasikan melalui fungsi yang memiliki prototipe C berikut:

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

Xamarin.Android tidak mengekspos RegisterNatives metode . Sebaliknya, ACW dan MCW bersama-sama memberikan informasi yang diperlukan untuk dipanggil RegisterNatives: ACW berisi nama metode dan tanda tangan jenis JNI, satu-satunya hal yang hilang adalah penunjuk fungsi untuk menghubungkan.

Di sinilah metode konektor masuk. Parameter atribut kustom ketiga [Register] adalah nama metode yang ditentukan dalam jenis terdaftar atau kelas dasar dari jenis terdaftar yang tidak menerima parameter dan mengembalikan System.Delegate. Yang dikembalikan System.Delegate pada gilirannya mengacu pada metode yang memiliki tanda tangan fungsi JNI yang benar. Terakhir, delegasi yang dikembalikan metode konektor harus di-rooting sehingga GC tidak mengumpulkannya, karena delegasi disediakan ke 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

Metode ini GetAddHandler membuat Func<IntPtr, IntPtr, int, int, int> delegasi yang mengacu pada n_Add metode , lalu memanggil JNINativeWrapper.CreateDelegate. JNINativeWrapper.CreateDelegate membungkus metode yang disediakan dalam blok try/catch, sehingga setiap pengecualian yang tidak tertangani ditangani dan akan mengakibatkan peningkatan peristiwa AndroidEvent.UnhandledExceptionRaiser . Delegasi yang dihasilkan disimpan dalam variabel statis cb_add sehingga GC tidak akan membebaskan delegasi.

Terakhir, n_Add metode ini bertanggung jawab untuk melakukan marsekal parameter JNI ke jenis terkelola yang sesuai, lalu mendelegasikan panggilan metode.

Catatan: Selalu gunakan JniHandleOwnership.DoNotTransfer saat mendapatkan MCW melalui instans Java. Memperlakukannya sebagai referensi lokal (dan dengan demikian memanggil JNIEnv.DeleteLocalRef) akan rusak dikelola -> Java -> transisi tumpukan terkelola.

Pengikatan Adder Lengkap

Pengikatan terkelola lengkap untuk jenis ini mono.android.tests.Adder adalah:

[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
}

Batasan

Saat menulis jenis yang cocok dengan kriteria berikut:

  1. Subkelas Java.Lang.Object

  2. Memiliki [Register] atribut kustom

  3. RegisterAttribute.DoNotGenerateAcw is true

Kemudian untuk interaksi GC, jenis tidak boleh memiliki bidang apa pun yang dapat merujuk ke Java.Lang.Object atau Java.Lang.Object subkelas saat runtime. Misalnya, bidang jenis System.Object dan jenis antarmuka apa pun tidak diizinkan. Jenis yang tidak dapat merujuk ke Java.Lang.Object instans diizinkan, seperti System.String dan List<int>. Pembatasan ini untuk mencegah pengumpulan objek prematur oleh GC.

Jika jenis harus berisi bidang instans yang dapat merujuk ke Java.Lang.Object instans, maka jenis bidang harus System.WeakReference atau GCHandle.

Mengikat Metode Abstrak

Metode pengikatan abstract sebagian besar identik dengan metode virtual yang mengikat. Hanya ada dua perbedaan:

  1. Metode abstrak bersifat abstrak. Ini masih mempertahankan [Register] atribut dan Pendaftaran Metode terkait, Pengikatan Metode baru saja dipindahkan ke jenis .Invoker

  2. Non-jenis abstractInvoker dibuat yang mensubkelas jenis abstrak. Jenis harus Invoker mengambil alih semua metode abstrak yang dideklarasikan dalam kelas dasar, dan implementasi yang ditimpa adalah implementasi Pengikatan Metode, meskipun kasus pengiriman non-virtual dapat diabaikan.

Misalnya, asumsikan bahwa metode di atas mono.android.test.Adder.add adalah abstract. Pengikatan C# akan berubah sehingga Adder.Add abstrak, dan jenis baru AdderInvoker akan didefinisikan yang diimplementasikan 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));
    }
}

Jenis Invoker ini hanya diperlukan saat mendapatkan referensi JNI ke instans yang dibuat Java.

Antarmuka Pengikatan

Antarmuka pengikatan secara konseptual mirip dengan kelas pengikatan yang berisi metode virtual, tetapi banyak dari spesifiknya berbeda dalam cara yang halus (dan tidak begitu halus). Pertimbangkan deklarasi antarmuka Java berikut:

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

Pengikatan antarmuka memiliki dua bagian: definisi antarmuka C#, dan definisi Pemanggil untuk antarmuka.

Definisi Antarmuka

Definisi antarmuka C# harus memenuhi persyaratan berikut:

  • Definisi antarmuka harus memiliki [Register] atribut kustom.

  • Definisi antarmuka harus memperluas IJavaObject interface. Kegagalan untuk melakukannya akan mencegah ACW mewarisi dari antarmuka Java.

  • Setiap metode antarmuka harus berisi atribut yang [Register] menentukan nama metode Java yang sesuai, tanda tangan JNI, dan metode konektor.

  • Metode konektor juga harus menentukan jenis tempat metode konektor dapat ditemukan.

Saat pengikatan abstract dan virtual metode, metode konektor akan dicari dalam hierarki pewarisan jenis yang didaftarkan. Antarmuka tidak dapat memiliki metode yang berisi badan, sehingga ini tidak berfungsi, sehingga persyaratan bahwa jenis ditentukan yang menunjukkan di mana metode konektor berada. Jenis ditentukan dalam string metode konektor, setelah titik dua ':', dan harus merupakan nama jenis yang memenuhi syarat perakitan dari jenis yang berisi pemanggil.

Deklarasi metode antarmuka adalah terjemahan dari metode Java yang sesuai menggunakan jenis yang kompatibel . Untuk jenis bawaan Java, jenis yang kompatibel adalah jenis C# yang sesuai, misalnya Java int adalah C# int. Untuk jenis referensi, jenis yang kompatibel adalah jenis yang dapat menyediakan handel JNI dari jenis Java yang sesuai.

Anggota antarmuka tidak akan langsung dipanggil oleh Java - pemanggilan akan dimediasi melalui jenis Invoker - sehingga beberapa jumlah fleksibilitas diizinkan.

Antarmuka Java Progress dapat dideklarasikan dalam C# sebagai:

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

Perhatikan di atas bahwa kami memetakan parameter Java int[] ke int> JavaArray<. Ini tidak diperlukan: kita bisa mengikatnya ke C# int[], atau IList<int>, atau sesuatu yang lain sepenuhnya. Jenis apa pun yang dipilih, Invoker kebutuhan untuk dapat menerjemahkannya ke dalam jenis Java int[] untuk pemanggilan.

Definisi Pemanggil

Definisi Invoker jenis harus mewarisi Java.Lang.Object, menerapkan antarmuka yang sesuai, dan menyediakan semua metode koneksi yang direferensikan dalam definisi antarmuka. Ada satu saran lagi yang berbeda dari pengikatan kelas: class_ref ID bidang dan metode harus menjadi anggota instans, bukan anggota statis.

Alasan untuk memilih anggota instans ada hubungannya dengan JNIEnv.GetMethodID perilaku dalam runtime Android. (Ini mungkin perilaku Java juga; ini belum diuji.) JNIEnv.GetMethodID mengembalikan null saat mencari metode yang berasal dari antarmuka yang diimplementasikan dan bukan antarmuka yang dideklarasikan. Pertimbangkan java.util.SortedMap<K, antarmuka V> Java, yang mengimplementasikan antarmuka java.util.Map<K, V>. Peta menyediakan metode yang jelas , sehingga definisi yang tampaknya masuk akal Invoker untuk SortedMap adalah:

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

Hal di atas akan gagal karena JNIEnv.GetMethodID akan kembali null saat mencari Map.clear metode melalui SortedMap instans kelas.

Ada dua solusi untuk ini: lacak antarmuka mana setiap metode berasal, dan memiliki class_ref untuk setiap antarmuka, atau pertahankan semuanya sebagai anggota instans dan lakukan pencarian metode pada jenis kelas yang paling turunan, bukan jenis antarmuka. Yang terakhir dilakukan dalam Mono.Android.dll.

Definisi Invoker memiliki enam bagian: konstruktor, Dispose metode, ThresholdType dan ThresholdClass anggota, GetObject metode, implementasi metode antarmuka, dan implementasi metode konektor.

Konstruktor

Konstruktor perlu mencari kelas runtime instans yang dipanggil dan menyimpan kelas runtime di bidang instans class_ref :

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

Catatan: Handle Properti harus digunakan dalam isi konstruktor, dan bukan handle parameter, karena pada Android v4.0 handle parameter mungkin tidak valid setelah konstruktor dasar selesai dieksekusi.

Metode Dispose

Metode Dispose ini perlu membebaskan referensi global yang dialokasikan dalam konstruktor:

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 dan ThresholdClass

Anggota ThresholdType dan ThresholdClass identik dengan apa yang ditemukan dalam pengikatan kelas:

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

Metode GetObject

Metode statis GetObject diperlukan untuk mendukung Extensions.JavaCast<T>():

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

Metode Antarmuka

Setiap metode antarmuka harus memiliki implementasi, yang memanggil metode Java yang sesuai melalui 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));
    }
}

Metode Koneksi or

Metode konektor dan infrastruktur pendukung bertanggung jawab untuk marshaling parameter JNI ke jenis C# yang sesuai. Parameter Java int[] akan diteruskan sebagai JNI jintArray, yang merupakan IntPtr dalam C#. IntPtr harus dinaungi ke JavaArray<int> untuk mendukung pemanggilan antarmuka C#:

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

Jika int[] akan lebih disukai daripada JavaList<int>, maka JNIEnv.GetArray() dapat digunakan sebagai gantinya:

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

Namun, perhatikan bahwa JNIEnv.GetArray menyalin seluruh array antara VM, jadi untuk array besar ini dapat mengakibatkan banyak tekanan GC tambahan.

Definisi Pemanggil Lengkap

Definisi IAdderProgressInvoker lengkap:

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
}

Referensi Objek JNI

Banyak metode JNIEnv mengembalikan referensi objek JNI, yang mirip GCHandledengan s. JNI menyediakan tiga jenis referensi objek yang berbeda: referensi lokal, referensi global, dan referensi global yang lemah. Ketiganya direpresentasikan sebagai System.IntPtr, tetapi (sesuai bagian Jenis Fungsi JNI) tidak semua IntPtryang dikembalikan dari JNIEnv metode adalah referensi. Misalnya, JNIEnv.GetMethodID mengembalikan , tetapi tidak mengembalikan referensi objek, JNIEnv.GetMethodIDjmethodIDmengembalikan IntPtr. Lihat dokumentasi fungsi JNI untuk detailnya.

Referensi lokal dibuat oleh sebagian besar metode pembuatan referensi. Android hanya memungkinkan sejumlah referensi lokal terbatas untuk ada pada waktu tertentu, biasanya 512. Referensi lokal dapat dihapus melalui JNIEnv.DeleteLocalRef. Tidak seperti JNI, tidak semua metode referensi JNIEnv yang mengembalikan referensi objek mengembalikan referensi lokal; JNIEnv.FindClass mengembalikan referensi global . Sangat disarankan agar Anda menghapus referensi lokal secepat mungkin dengan membuat konstruktor Java.Lang.Object di sekitar objek dan menentukan JniHandleOwnership.TransferLocalRef ke konstruktor Java.Lang.Object(IntPtr handle, transfer JniHandleOwnership).

Referensi global dibuat oleh JNIEnv.NewGlobalRef dan JNIEnv.FindClass. Mereka dapat dihancurkan dengan JNIEnv.DeleteGlobalRef. Emulator memiliki batas 2.000 referensi global yang luar biasa, sementara perangkat keras memiliki batas sekitar 52.000 referensi global.

Referensi global yang lemah hanya tersedia di Android v2.2 (Froyo) dan yang lebih baru. Referensi global yang lemah dapat dihapus dengan JNIEnv.DeleteWeakGlobalRef.

Berurusan dengan Referensi Lokal JNI

Metode JNIEnv.GetObjectField, JNIEnv.GetStaticObjectField, JNIEnv.CallObjectMethod, JNIEnv.CallNonvirtualObjectMethod dan JNIEnv.CallStaticObjectMethod mengembalikan IntPtr yang berisi referensi lokal JNI ke objek Java, atau IntPtr.Zero jika Java mengembalikan null. Karena jumlah referensi lokal terbatas yang dapat luar biasa sekaligus (512 entri), diinginkan untuk memastikan bahwa referensi dihapus tepat waktu. Ada tiga cara agar referensi lokal dapat ditangani: secara eksplisit menghapusnya, membuat Java.Lang.Object instans untuk menahannya, dan menggunakan Java.Lang.Object.GetObject<T>() untuk membuat pembungkus yang dapat dipanggil terkelola di sekitarnya.

Menghapus Referensi Lokal Secara Eksplisit

JNIEnv.DeleteLocalRef digunakan untuk menghapus referensi lokal. Setelah referensi lokal dihapus, referensi tidak dapat digunakan lagi, jadi perawatan harus dilakukan untuk memastikan bahwa adalah hal terakhir yang JNIEnv.DeleteLocalRef dilakukan dengan referensi lokal.

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

Membungkus dengan Java.Lang.Object

Java.Lang.Objectmenyediakan konstruktor Java.Lang.Object(IntPtr handle, JniHandleOwnership transfer) yang dapat digunakan untuk membungkus referensi JNI yang keluar. Parameter JniHandleOwnership menentukan bagaimana IntPtr parameter harus diperlakukan:

  • JniHandleOwnership.DoNotTransfer – Instans yang dibuat Java.Lang.Object akan membuat referensi global baru dari handle parameter , dan handle tidak berubah. Pemanggil bertanggung jawab untuk membebaskan handle , jika perlu.

  • JniHandleOwnership.TransferLocalRef – Instans yang dibuat Java.Lang.Object akan membuat referensi global baru dari handle parameter , dan handle dihapus dengan JNIEnv.DeleteLocalRef . Pemanggil tidak boleh bebas handle , dan tidak boleh digunakan handle setelah konstruktor selesai dieksekusi.

  • JniHandleOwnership.TransferGlobalRef – Instans yang dibuat Java.Lang.Object akan mengambil alih kepemilikan handle parameter. Pemanggil tidak boleh gratis handle .

Karena metode pemanggilan metode JNI mengembalikan ref lokal, JniHandleOwnership.TransferLocalRef biasanya akan digunakan:

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

Referensi global yang dibuat tidak akan dibebaskan sampai Java.Lang.Object instans dikumpulkan sampah. Jika Anda mampu, membuang instans akan membebaskan referensi global, mempercepat pengumpulan sampah:

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

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

Java.Lang.Objectmenyediakan metode Java.Lang.Object.GetObject<T>(Handel IntPtr, transfer JniHandleOwnership) yang dapat digunakan untuk membuat pembungkus yang dapat dipanggil terkelola dari jenis yang ditentukan.

Jenis T harus memenuhi persyaratan berikut:

  1. T harus merupakan jenis referensi.

  2. T harus mengimplementasikan IJavaObject antarmuka.

  3. Jika T bukan kelas atau antarmuka abstrak, maka T harus menyediakan konstruktor dengan jenis (IntPtr, JniHandleOwnership) parameter .

  4. Jika T adalah kelas abstrak atau antarmuka, harus ada pemanggil yang tersedia untuk T . Pemanggil adalah jenis non-abstrak yang mewarisi T atau mengimplementasikan T , dan memiliki nama yang sama seperti T dengan akhiran Invoker. Misalnya, jika T adalah antarmuka Java.Lang.IRunnable , maka jenisnya Java.Lang.IRunnableInvoker harus ada dan harus berisi konstruktor yang diperlukan (IntPtr, JniHandleOwnership) .

Karena metode pemanggilan metode JNI mengembalikan ref lokal, JniHandleOwnership.TransferLocalRef biasanya akan digunakan:

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

Mencari Tipe Java

Untuk mencari bidang atau metode di JNI, jenis deklarasikan untuk bidang atau metode harus dicari terlebih dahulu. Metode Android.Runtime.JNIEnv.FindClass(string)) digunakan untuk mencari jenis Java. Parameter string adalah referensi jenis yang disederhanakan atau referensi jenis lengkap untuk jenis Java. Lihat bagian Referensi Jenis JNI untuk detail tentang referensi jenis yang disederhanakan dan lengkap.

Catatan: Tidak seperti setiap metode lain JNIEnv yang mengembalikan instans objek, FindClass mengembalikan referensi global, bukan referensi lokal.

Bidang Instans

Bidang dimanipulasi melalui ID bidang. ID Bidang diperoleh melalui JNIEnv.GetFieldID, yang memerlukan kelas tempat bidang didefinisikan, nama bidang, dan Tanda Tangan Jenis JNI dari bidang .

ID Bidang tidak perlu dibebaskan, dan valid selama jenis Java yang sesuai dimuat. (Android saat ini tidak mendukung pembongkaran kelas.)

Ada dua set metode untuk memanipulasi bidang instans: satu untuk membaca bidang instans dan satu untuk menulis bidang instans. Semua set metode memerlukan ID bidang untuk membaca atau menulis nilai bidang.

Membaca Nilai Bidang Instans

Set metode untuk membaca nilai bidang instans mengikuti pola penamaan:

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

di mana * adalah jenis bidang :

Menulis Nilai Bidang Instans

Kumpulan metode untuk menulis nilai bidang instans mengikuti pola penamaan:

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

di mana Jenis adalah jenis bidang:

  • JNIEnv.SetField) – Tulis nilai bidang apa pun yang bukan jenis bawaan, seperti java.lang.Object , array, dan jenis antarmuka. Nilainya IntPtr mungkin referensi lokal JNI, referensi global JNI, referensi global JNI lemah, atau IntPtr.Zero (untuk null ).

  • JNIEnv.SetField) – Tulis nilai bool bidang instans.

  • JNIEnv.SetField) – Tulis nilai sbyte bidang instans.

  • JNIEnv.SetField) – Tulis nilai char bidang instans.

  • JNIEnv.SetField) – Tulis nilai short bidang instans.

  • JNIEnv.SetField) – Tulis nilai int bidang instans.

  • JNIEnv.SetField) – Tulis nilai long bidang instans.

  • JNIEnv.SetField) – Tulis nilai float bidang instans.

  • JNIEnv.SetField) – Tulis nilai double bidang instans.

Bidang Statis

Bidang Statis dimanipulasi melalui ID bidang. ID Bidang diperoleh melalui JNIEnv.GetStaticFieldID, yang memerlukan kelas tempat bidang ditentukan, nama bidang, dan Tanda Tangan Jenis JNI dari bidang .

ID Bidang tidak perlu dibebaskan, dan valid selama jenis Java yang sesuai dimuat. (Android saat ini tidak mendukung pembongkaran kelas.)

Ada dua set metode untuk memanipulasi bidang statis: satu untuk bidang instans baca dan satu untuk menulis bidang instans. Semua set metode memerlukan ID bidang untuk membaca atau menulis nilai bidang.

Membaca Nilai Bidang Statis

Sekumpulan metode untuk membaca nilai bidang statis mengikuti pola penamaan:

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

di mana * adalah jenis bidang :

Menulis Nilai Bidang Statis

Set metode untuk menulis nilai bidang statis mengikuti pola penamaan:

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

di mana Jenis adalah jenis bidang:

Metode Instans

Metode Instans dipanggil melalui ID metode. ID metode diperoleh melalui JNIEnv.GetMethodID, yang memerlukan jenis metode yang ditentukan, nama metode, dan Tanda Tangan Jenis JNI dari metode .

ID metode tidak perlu dibebaskan, dan valid selama jenis Java yang sesuai dimuat. (Android saat ini tidak mendukung pembongkaran kelas.)

Ada dua set metode untuk memanggil metode: satu untuk memanggil metode secara virtual, dan satu untuk memanggil metode secara non-virtual. Kedua set metode memerlukan ID metode untuk memanggil metode, dan pemanggilan non-virtual juga mengharuskan Anda menentukan implementasi kelas mana yang harus dipanggil.

Metode antarmuka hanya dapat dicari dalam jenis deklarasikan; metode yang berasal dari antarmuka yang diperluas/diwariskan tidak dapat dicari. Lihat bagian Mengikat Antarmuka /Implementasi Invoker yang lebih baru untuk detail selengkapnya.

Metode apa pun yang dideklarasikan di kelas atau kelas dasar atau antarmuka yang diimplementasikan dapat dicari.

Pemanggilan Metode Virtual

Set metode untuk memanggil metode hampir mengikuti pola penamaan:

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

di mana * adalah jenis pengembalian metode.

Pemanggilan Metode Non-Virtual

Set metode untuk memanggil metode secara non-virtual mengikuti pola penamaan:

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

di mana * adalah jenis pengembalian metode. Pemanggilan metode non-virtual biasanya digunakan untuk memanggil metode dasar metode virtual.

Metode Statis

Metode Statis dipanggil melalui ID metode. ID metode diperoleh melalui JNIEnv.GetStaticMethodID, yang memerlukan jenis metode yang ditentukan, nama metode, dan Tanda Tangan Jenis JNI dari metode .

ID metode tidak perlu dibebaskan, dan valid selama jenis Java yang sesuai dimuat. (Android saat ini tidak mendukung pembongkaran kelas.)

Pemanggilan Metode Statis

Set metode untuk memanggil metode hampir mengikuti pola penamaan:

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

di mana * adalah jenis pengembalian metode.

Tanda Tangan Jenis JNI

Tanda Tangan Jenis JNI adalah Referensi Jenis JNI (meskipun bukan referensi jenis yang disederhanakan), kecuali untuk metode. Dengan metode, JNI Type Signature adalah tanda kurung '('terbuka , diikuti dengan referensi jenis untuk semua jenis parameter yang digabungkan bersama-sama (tanpa memisahkan koma atau apa pun), diikuti oleh tanda kurung ')'penutup , diikuti dengan referensi jenis JNI dari jenis pengembalian metode.

Misalnya, mengingat metode Java:

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

Tanda tangan jenis JNI adalah:

(ILjava/lang/String;[I)J

Secara umum, sangat disarankan untuk menggunakan javap perintah untuk menentukan tanda tangan JNI. Misalnya, JNI Type Signature dari metode java.lang.Thread.State.valueOf(String) adalah "(Ljava/lang/String;) Ljava/lang/Thread$State;", sedangkan JNI Type Signature dari metode java.lang.Thread.State.values adalah "()[Ljava/lang/Thread$State;". Perhatikan titik koma berikutnya; itu adalah bagian dari tanda tangan jenis JNI.

Referensi Jenis JNI

Referensi jenis JNI berbeda dari referensi jenis Java. Anda tidak dapat menggunakan nama jenis Java yang sepenuhnya memenuhi syarat seperti java.lang.String dengan JNI, Anda harus menggunakan variasi "java/lang/String" JNI atau "Ljava/lang/String;", tergantung pada konteks; lihat di bawah ini untuk detailnya. Ada empat jenis referensi jenis JNI:

  • Built-in
  • Disederhanakan
  • jenis
  • array

Referensi Jenis Bawaan

Referensi jenis bawaan adalah karakter tunggal, digunakan untuk mereferensikan jenis nilai bawaan. Pemetaannya adalah sebagai berikut:

  • "B" untuk sbyte .
  • "S" untuk short .
  • "I" untuk int .
  • "J" untuk long .
  • "F" untuk float .
  • "D" untuk double .
  • "C" untuk char .
  • "Z" untuk bool .
  • "V" untuk void jenis pengembalian metode.

Referensi Tipe Yang Disederhanakan

Referensi jenis yang disederhanakan hanya dapat digunakan dalam JNIEnv.FindClass(string)). Ada dua cara untuk mendapatkan referensi jenis yang disederhanakan:

  1. Dari nama Java yang sepenuhnya memenuhi syarat, ganti setiap '.' dalam nama paket dan sebelum nama jenis dengan '/' , dan setiap '.' dalam nama jenis dengan '$' .

  2. Baca output dari 'unzip -l android.jar | grep JavaName' .

Salah satu dari keduanya akan menghasilkan jenis Java java.lang.Thread.State yang dipetakan ke referensi java/lang/Thread$Statejenis yang disederhanakan .

Referensi Jenis

Referensi jenis adalah referensi jenis bawaan atau referensi jenis yang disederhanakan dengan 'L' awalan dan ';' akhiran. Untuk jenis Java java.lang.String, referensi jenis yang disederhanakan adalah "java/lang/String", sementara referensi jenisnya adalah "Ljava/lang/String;".

Referensi jenis digunakan dengan referensi jenis Array dan dengan Tanda Tangan JNI.

Cara tambahan untuk mendapatkan referensi jenis adalah dengan membaca output .'javap -s -classpath android.jar fully.qualified.Java.Name' Bergantung pada jenis yang terlibat, Anda dapat menggunakan deklarasi konstruktor atau jenis pengembalian metode untuk menentukan nama JNI. Contohnya:

$ 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 adalah jenis enum Java, sehingga kita dapat menggunakan Tanda Tangan valueOf metode untuk menentukan bahwa referensi jenis adalah Ljava/lang/Thread$State;.

Referensi Tipe Array

Referensi jenis array diawali '[' dengan referensi jenis JNI. Referensi jenis yang disederhanakan tidak dapat digunakan saat menentukan array.

Misalnya, int[] adalah "[I", int[][] adalah "[[I", dan java.lang.Object[] adalah "[Ljava/lang/Object;".

Java Generics dan Type Erasure

Sebagian besar waktu, seperti yang terlihat melalui JNI, generik Java tidak ada. Ada beberapa "keriput," tetapi kerutan tersebut ada di bagaimana Java berinteraksi dengan generik, bukan dengan bagaimana JNI mencari dan memanggil anggota generik.

Tidak ada perbedaan antara jenis atau anggota generik dan jenis atau anggota non-generik saat berinteraksi melalui JNI. Misalnya, jenis generik java.lang.Class<T> juga merupakan jenis java.lang.Classgenerik "mentah", yang keduanya memiliki referensi jenis yang disederhanakan yang sama, "java/lang/Class".

Dukungan Antarmuka Asli Java

Android.Runtime.JNIEnv adalah pembungkus terkelola untuk Jave Native Interface (JNI). Fungsi JNI dideklarasikan dalam Spesifikasi Antarmuka Asli Java, meskipun metode telah diubah untuk menghapus parameter eksplisit JNIEnv* dan IntPtr digunakan alih-alih jobject, jclass, jmethodID, dll. Misalnya, pertimbangkan fungsi JNI NewObject:

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

Ini diekspos sebagai metode JNIEnv.NewObject :

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

Menerjemahkan antara dua panggilan cukup mudah. Di C Anda akan memiliki:

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

Yang setara dengan C# adalah:

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

Setelah Anda memiliki instans Objek Java yang disimpan di IntPtr, Anda mungkin ingin melakukan sesuatu dengannya. Anda dapat menggunakan metode JNIEnv seperti JNIEnv.CallVoidMethod() untuk melakukannya, tetapi jika sudah ada pembungkus C# analog maka Anda harus membuat pembungkus atas referensi JNI. Anda dapat melakukannya melalui metode ekstensi Extensions.JavaCast<T> :

IntPtr lrefActivity = CreateMapActivity();

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

Anda juga dapat menggunakan metode Java.Lang.Object.GetObject<T> :

IntPtr lrefActivity = CreateMapActivity();

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

Selain itu, semua fungsi JNI telah dimodifikasi dengan menghapus parameter yang JNIEnv* ada di setiap fungsi JNI.

Ringkasan

Berurusan langsung dengan JNI adalah pengalaman mengerikan yang harus dihindari dengan segala biaya. Sayangnya, itu tidak selalu dapat dihindari; mudah-mudahan panduan ini akan memberikan bantuan ketika Anda mencapai kasus Java yang tidak terikat dengan Mono untuk Android.