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:
- Merangkum penggunaan JNI sehingga kode klien tidak perlu tahu tentang kompleksitas yang mendasarinya.
- 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 danwriteObject
. 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 jenisParcelable.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 denganandroid:onClick
danjava.io.Serializable
.ExportFieldAttribute
– menentukan nama bidang. Ini berada pada metode yang berfungsi sebagai penginisialisasi bidang. Ini dapat digunakan denganandroid.os.Parcelable
.
Pemecahan Masalah ExportAttribute dan ExportFieldAttribute
Pengemasan gagal karena Mono.Android.Export.dll hilang – jika Anda menggunakan
ExportAttribute
atauExportFieldAttribute
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
darijava.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). AkanDeprecated
ada kelas danDeprecatedAttribute
.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:
Handel JNI ke jenis Java yang terikat.
Jika sub-kelas diperlukan, jenis harus memiliki atribut kustom RegisterAttribute pada deklarasi jenis dengan RegisterAttribute.DoNotGenerateAcw diatur ke
true
.
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:
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.
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.
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:
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 .
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*Method
JNIEnv.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 .
Java.Lang.Object
Konstruktor default akan mengalokasikan instans Java.Jika jenis memiliki
RegisterAttribute
, danRegisterAttribute.DoNotGenerateAcw
adalahtrue
, maka instans jenisRegisterAttribute.Name
dibuat melalui konstruktor defaultnya.Jika tidak, Android Callable Wrapper (ACW) yang sesuai
this.GetType
dengan dibuat melalui konstruktor defaultnya. Android Callable Wrappers dihasilkan selama pembuatan paket untuk setiapJava.Lang.Object
subkelas yangRegisterAttribute.DoNotGenerateAcw
tidak diatur ketrue
.
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:
Memanggil Java.Lang.Object(IntPtr, JniHandleOwnership) alih-alih konstruktor default
Java.Lang.Object
. Ini diperlukan untuk menghindari pembuatan instans Java baru.Periksa nilai Java.Lang.Object.Handle sebelum membuat instans Java apa pun. Properti
Object.Handle
akan memiliki nilai selainIntPtr.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 instansmono.samples.helloworld.HelloAndroid
, Android Callable Wrapper akan dibuat terlebih dahulu , dan konstruktor JavaHelloAndroid
akan membuat instans dari jenis yangMono.Samples.HelloWorld.HelloAndroid
sesuai, denganObject.Handle
properti diatur ke instans Java sebelum eksekusi konstruktor.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.
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 & 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.GetMethodID
JNIEnv.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:
Pengikatan Metode
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:
Nama metode Java,
"add"
dalam hal ini.Tanda Tangan Jenis JNI dari metode ,
"(II)I"
dalam hal ini.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:
Subkelas
Java.Lang.Object
Memiliki
[Register]
atribut kustomRegisterAttribute.DoNotGenerateAcw
istrue
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:
Metode abstrak bersifat abstrak. Ini masih mempertahankan
[Register]
atribut dan Pendaftaran Metode terkait, Pengikatan Metode baru saja dipindahkan ke jenis .Invoker
Non-jenis
abstract
Invoker
dibuat yang mensubkelas jenis abstrak. Jenis harusInvoker
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 GCHandle
dengan 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 IntPtr
yang dikembalikan dari JNIEnv
metode adalah referensi. Misalnya, JNIEnv.GetMethodID mengembalikan , tetapi tidak mengembalikan referensi objek, JNIEnv.GetMethodIDjmethodID
mengembalikan 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.Object
menyediakan 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 darihandle
parameter , danhandle
tidak berubah. Pemanggil bertanggung jawab untuk membebaskanhandle
, jika perlu.JniHandleOwnership.TransferLocalRef – Instans yang dibuat
Java.Lang.Object
akan membuat referensi global baru darihandle
parameter , danhandle
dihapus dengan JNIEnv.DeleteLocalRef . Pemanggil tidak boleh bebashandle
, dan tidak boleh digunakanhandle
setelah konstruktor selesai dieksekusi.JniHandleOwnership.TransferGlobalRef – Instans yang dibuat
Java.Lang.Object
akan mengambil alih kepemilikanhandle
parameter. Pemanggil tidak boleh gratishandle
.
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.Object
menyediakan 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:
T
harus merupakan jenis referensi.T
harus mengimplementasikanIJavaObject
antarmuka.Jika
T
bukan kelas atau antarmuka abstrak, makaT
harus menyediakan konstruktor dengan jenis(IntPtr, JniHandleOwnership)
parameter .Jika
T
adalah kelas abstrak atau antarmuka, harus ada pemanggil yang tersedia untukT
. Pemanggil adalah jenis non-abstrak yang mewarisiT
atau mengimplementasikanT
, dan memiliki nama yang sama sepertiT
dengan akhiran Invoker. Misalnya, jika T adalah antarmukaJava.Lang.IRunnable
, maka jenisnyaJava.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 :
JNIEnv.GetObjectField – Baca nilai bidang instans apa pun yang bukan jenis bawaan, seperti
java.lang.Object
, array, dan jenis antarmuka. Nilai yang dikembalikan adalah referensi lokal JNI.JNIEnv.GetBooleanField – Baca nilai
bool
bidang instans.JNIEnv.GetByteField – Baca nilai
sbyte
bidang instans.JNIEnv.GetCharField – Baca nilai
char
bidang instans.JNIEnv.GetShortField – Baca nilai
short
bidang instans.JNIEnv.GetIntField – Baca nilai
int
bidang instans.JNIEnv.GetLongField – Baca nilai
long
bidang instans.JNIEnv.GetFloatField – Baca nilai
float
bidang instans.JNIEnv.GetDoubleField – Baca nilai
double
bidang instans.
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. NilainyaIntPtr
mungkin referensi lokal JNI, referensi global JNI, referensi global JNI lemah, atauIntPtr.Zero
(untuknull
).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 :
JNIEnv.GetStaticObjectField – Baca nilai bidang statis apa pun yang bukan jenis bawaan, seperti
java.lang.Object
, array, dan jenis antarmuka. Nilai yang dikembalikan adalah referensi lokal JNI.JNIEnv.GetStaticBooleanField – Baca nilai
bool
bidang statis.JNIEnv.GetStaticByteField – Baca nilai
sbyte
bidang statis.JNIEnv.GetStaticCharField – Baca nilai
char
bidang statis.JNIEnv.GetStaticShortField – Baca nilai
short
bidang statis.JNIEnv.GetStaticLongField – Baca nilai
long
bidang statis.JNIEnv.GetStaticFloatField – Baca nilai
float
bidang statis.JNIEnv.GetStaticDoubleField – Baca nilai
double
bidang statis.
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:
JNIEnv.SetStaticField) – Tulis nilai bidang statis apa pun yang bukan jenis bawaan, seperti
java.lang.Object
, array, dan jenis antarmuka. NilainyaIntPtr
mungkin referensi lokal JNI, referensi global JNI, referensi global JNI lemah, atauIntPtr.Zero
(untuknull
).JNIEnv.SetStaticField) – Tulis nilai
bool
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
sbyte
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
char
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
short
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
int
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
long
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
float
bidang statis.JNIEnv.SetStaticField) – Tulis nilai
double
bidang statis.
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.
JNIEnv.CallObjectMethod – Memanggil metode yang mengembalikan jenis non-bawaan, seperti
java.lang.Object
, array, dan antarmuka. Nilai yang dikembalikan adalah referensi lokal JNI.JNIEnv.CallBooleanMethod – Memanggil metode yang mengembalikan
bool
nilai.JNIEnv.CallByteMethod – Memanggil metode yang mengembalikan
sbyte
nilai.JNIEnv.CallCharMethod – Memanggil metode yang mengembalikan
char
nilai.JNIEnv.CallShortMethod – Memanggil metode yang mengembalikan
short
nilai.JNIEnv.CallLongMethod – Memanggil metode yang mengembalikan
long
nilai.JNIEnv.CallFloatMethod – Memanggil metode yang mengembalikan
float
nilai.JNIEnv.CallDoubleMethod – Memanggil metode yang mengembalikan
double
nilai.
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.
JNIEnv.CallNonvirtualObjectMethod – Tidak secara virtual memanggil metode yang mengembalikan jenis non-bawaan, seperti
java.lang.Object
array, dan antarmuka. Nilai yang dikembalikan adalah referensi lokal JNI.JNIEnv.CallNonvirtualBooleanMethod – Tidak secara virtual memanggil metode yang mengembalikan
bool
nilai.JNIEnv.CallNonvirtualByteMethod – Tidak secara virtual memanggil metode yang mengembalikan
sbyte
nilai.JNIEnv.CallNonvirtualCharMethod – Tidak secara virtual memanggil metode yang mengembalikan
char
nilai.JNIEnv.CallNonvirtualShortMethod – Tidak secara virtual memanggil metode yang mengembalikan
short
nilai.JNIEnv.CallNonvirtualLongMethod – Tidak secara virtual memanggil metode yang mengembalikan
long
nilai.JNIEnv.CallNonvirtualFloatMethod – Tidak secara virtual memanggil metode yang mengembalikan
float
nilai.JNIEnv.CallNonvirtualDoubleMethod – Tidak secara virtual memanggil metode yang mengembalikan
double
nilai.
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.
JNIEnv.CallStaticObjectMethod – Memanggil metode statis yang mengembalikan jenis non-bawaan, seperti
java.lang.Object
array, dan antarmuka. Nilai yang dikembalikan adalah referensi lokal JNI.JNIEnv.CallStaticBooleanMethod – Memanggil metode statis yang mengembalikan
bool
nilai.JNIEnv.CallStaticByteMethod – Memanggil metode statis yang mengembalikan
sbyte
nilai.JNIEnv.CallStaticCharMethod – Memanggil metode statis yang mengembalikan
char
nilai.JNIEnv.CallStaticShortMethod – Memanggil metode statis yang mengembalikan
short
nilai.JNIEnv.CallStaticLongMethod – Memanggil metode statis yang mengembalikan
long
nilai.JNIEnv.CallStaticFloatMethod – Memanggil metode statis yang mengembalikan
float
nilai.JNIEnv.CallStaticDoubleMethod – Memanggil metode statis yang mengembalikan
double
nilai.
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"
untuksbyte
."S"
untukshort
."I"
untukint
."J"
untuklong
."F"
untukfloat
."D"
untukdouble
."C"
untukchar
."Z"
untukbool
."V"
untukvoid
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:
Dari nama Java yang sepenuhnya memenuhi syarat, ganti setiap
'.'
dalam nama paket dan sebelum nama jenis dengan'/'
, dan setiap'.'
dalam nama jenis dengan'$'
.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$State
jenis 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.Class
generik "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.