Arsitektur
Aplikasi Xamarin.Android berjalan dalam lingkungan eksekusi Mono. Lingkungan eksekusi ini berjalan berdampingan dengan komputer virtual Android Runtime (ART). Kedua lingkungan runtime berjalan di atas kernel Linux dan mengekspos berbagai API ke kode pengguna yang memungkinkan pengembang mengakses sistem yang mendasarinya. Runtime Mono ditulis dalam bahasa C.
Anda dapat menggunakan Sistem, System.IO, System.Net , dan pustaka kelas .NET lainnya untuk mengakses fasilitas sistem operasi Linux yang mendasarinya.
Di Android, sebagian besar fasilitas sistem seperti Audio, Grafis, OpenGL, dan Telepon tidak tersedia langsung untuk aplikasi asli, mereka hanya diekspos melalui API Java Runtime Android yang berada di salah satu namespace Java.* atau namespace Android.*. Arsitekturnya kira-kira seperti ini:
Pengembang Xamarin.Android mengakses berbagai fitur dalam sistem operasi baik dengan memanggil ke API .NET yang mereka ketahui (untuk akses tingkat rendah) atau menggunakan kelas yang diekspos di namespace Android yang menyediakan jembatan ke API Java yang diekspos oleh Android Runtime.
Untuk informasi selengkapnya tentang cara kelas Android berkomunikasi dengan kelas Android Runtime, lihat dokumen Desain API.
Paket Aplikasi
Paket aplikasi Android adalah kontainer ZIP dengan ekstensi file .apk . Paket aplikasi Xamarin.Android memiliki struktur dan tata letak yang sama dengan paket Android normal, dengan penambahan berikut:
Rakitan aplikasi (berisi IL) disimpan tidak dikompresi dalam folder rakitan. Selama startup proses dalam Rilis , build .apk adalah mmap() yang dimasukkan ke dalam proses dan rakitan dimuat dari memori. Ini memungkinkan startup aplikasi yang lebih cepat, karena rakitan tidak perlu diekstraksi sebelum eksekusi.
Catatan: Informasi lokasi rakitan seperti Assembly.Location dan Assembly.CodeBasetidak dapat diandalkan dalam Build rilis. Mereka tidak ada sebagai entri sistem file yang berbeda, dan tidak memiliki lokasi yang dapat digunakan.
Pustaka asli yang berisi runtime Mono ada dalam .apk . Aplikasi Xamarin.Android harus berisi pustaka asli untuk arsitektur Android yang diinginkan/ditargetkan, misalnya armeabi , armeabi-v7a , x86 . Aplikasi Xamarin.Android tidak dapat berjalan pada platform kecuali berisi pustaka runtime yang sesuai.
Aplikasi Xamarin.Android juga berisi Android Callable Wrappers untuk memungkinkan Android memanggil ke kode terkelola.
Pembungkus Yang Dapat Dipanggil Android
- Pembungkus yang dapat dipanggil Android adalah jembatan JNI yang digunakan kapan saja runtime Android perlu memanggil kode terkelola. Pembungkus yang dapat dipanggil Android adalah bagaimana metode virtual dapat ditimpa dan antarmuka Java dapat diimplementasikan. Lihat dokumen Gambaran Umum Integrasi Java untuk informasi selengkapnya.
Pembungkus yang Dapat Dipanggil Terkelola
Pembungkus yang dapat dipanggil terkelola adalah jembatan JNI yang digunakan setiap kali kode terkelola perlu memanggil kode Android dan memberikan dukungan untuk mengambil alih metode virtual dan menerapkan antarmuka Java. Seluruh Android.* dan namespace terkait adalah pembungkus yang dapat dipanggil terkelola yang dihasilkan melalui pengikatan .jar. Pembungkus yang dapat dipanggil terkelola bertanggung jawab untuk mengonversi antara jenis terkelola dan Android dan memanggil metode platform Android yang mendasar melalui JNI.
Setiap pembungkus yang dapat dipanggil terkelola yang dibuat menyimpan referensi global Java, yang dapat diakses melalui properti Android.Runtime.IJavaObject.Handle . Referensi global digunakan untuk menyediakan pemetaan antara instans Java dan instans terkelola. Referensi global adalah sumber daya terbatas: emulator hanya memungkinkan 2000 referensi global ada pada satu waktu, sementara sebagian besar perangkat keras memungkinkan lebih dari 52.000 referensi global ada pada satu waktu.
Untuk melacak kapan referensi global dibuat dan dihancurkan, Anda dapat mengatur properti sistem debug.mono.log untuk berisi gref.
Referensi global dapat dibebaskan secara eksplisit dengan memanggil Java.Lang.Object.Dispose() pada pembungkus yang dapat dipanggil terkelola. Ini akan menghapus pemetaan antara instans Java dan instans terkelola dan memungkinkan instans Java dikumpulkan. Jika instans Java diakses kembali dari kode terkelola, pembungkus baru yang dapat dipanggil akan dibuat untuknya.
Perawatan harus dilakukan saat membuang Pembungkus Yang Dapat Dipanggil Terkelola jika instans dapat dibagikan secara tidak sengaja di antara utas, karena membuang instans akan memengaruhi referensi dari utas lain. Untuk keamanan maksimum, hanya Dispose()
instans yang telah dialokasikan melaluinew
atau dari metode yang Anda ketahui selalu mengalokasikan instans baru dan bukan instans cache yang dapat menyebabkan berbagi instans yang tidak disengaja antara utas.
Subkelas Pembungkus yang Dapat Dipanggil Terkelola
Subkelas pembungkus yang dapat dipanggil terkelola adalah tempat semua logika khusus aplikasi "menarik" mungkin hidup. Ini termasuk subkelas Android.App.Activity kustom (seperti jenis Activity1 di templat proyek default). (Secara khusus, ini adalah Subkelas Java.Lang.Object yang tidakberisi atribut kustom RegisterAttribute atau RegisterAttribute.DoNotGenerateAcw adalah false, yang merupakan default.)
Seperti pembungkus yang dapat dipanggil terkelola, subkelas pembungkus yang dapat dipanggil terkelola juga berisi referensi global, dapat diakses melalui properti Java.Lang.Object.Handle . Sama seperti pembungkus yang dapat dipanggil terkelola, referensi global dapat dibebaskan secara eksplisit dengan memanggil Java.Lang.Object.Dispose(). Tidak seperti pembungkus yang dapat dipanggil terkelola, sangat hati-hati harus diambil sebelum membuang instans tersebut, karena Dispose()-ing instans akan memecah pemetaan antara instans Java (instans Android Callable Wrapper) dan instans terkelola.
Aktivasi Java
Ketika Android Callable Wrapper (ACW) dibuat dari Java, konstruktor ACW akan menyebabkan konstruktor C# yang sesuai dipanggil. Misalnya, ACW untuk MainActivity akan berisi konstruktor default yang akan memanggil konstruktor default MainActivity. (Ini dilakukan melalui TypeManager.Activate() panggilan dalam konstruktor ACW.)
Ada satu tanda tangan konstruktor lainnya dari konstruktor: konstruktor (IntPtr, JniHandleOwnership). Konstruktor (IntPtr, JniHandleOwnership) dipanggil setiap kali objek Java diekspos ke kode terkelola dan Pembungkus Yang Dapat Dipanggil Terkelola perlu dibangun untuk mengelola handel JNI. Ini biasanya dilakukan secara otomatis.
Ada dua skenario di mana konstruktor (IntPtr, JniHandleOwnership) harus disediakan secara manual pada subkelas Pembungkus Yang Dapat Dipanggil Terkelola:
Android.App.Application disubkelas. Aplikasi istimewa; konstruktor Applicaton default tidak akan pernah dipanggil, dan konstruktor (IntPtr, JniHandleOwnership) harus disediakan sebagai gantinya.
Pemanggilan metode virtual dari konstruktor kelas dasar.
Perhatikan bahwa (2) adalah abstraksi bocor. Di Java, seperti dalam C#, panggilan ke metode virtual dari konstruktor selalu memanggil implementasi metode yang paling turunan. Misalnya, konstruktor TextView(Context, AttributeSet, int) memanggil metode virtual TextView.getDefaultMovementMethod(), yang terikat sebagai properti TextView.DefaultMovementMethod. Dengan demikian, jika jenis LogTextBox adalah ke (1) subkelas TextView, (2) menimpa TextView.DefaultMovementMethod, dan (3) mengaktifkan instans kelas tersebut melalui XML, properti DefaultMovementMethod yang ditimpa akan dipanggil sebelum konstruktor ACW memiliki kesempatan untuk mengeksekusi, dan itu akan terjadi sebelum konstruktor C# memiliki kesempatan untuk mengeksekusi.
Ini didukung dengan membuat instans LogTextBox melalui konstruktor LogTextView(IntPtr, JniHandleOwnership) saat instans ACW LogTextBox pertama kali memasukkan kode terkelola, lalu memanggil konstruktor LogTextBox(Context, IAttributeSet, int) pada instans yang sama saat konstruktor ACW dijalankan.
Urutan peristiwa:
XML Tata Letak dimuat ke dalam ContentView.
Android membuat instans grafik objek Tata Letak, dan membuat instans monodroid.apidemo.LogTextBox , ACW untuk LogTextBox .
Konstruktor monodroid.apidemo.LogTextBox menjalankan konstruktor android.widget.TextView .
Konstruktor TextView memanggil monodroid.apidemo.LogTextBox.getDefaultMovementMethod() .
monodroid.apidemo.LogTextBox.getDefaultMovementMethod() memanggil LogTextBox.n_getDefaultMovementMethod() , yang memanggil TextView.n_GetDefaultMovementMethod() , yang memanggil Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer) .
Java.Lang.Object.GetObject<TextView>() memeriksa untuk melihat apakah sudah ada instans C# yang sesuai untuk menangani . Jika ada, itu dikembalikan. Dalam skenario ini, tidak ada, sehingga Object.GetObject<T>() harus membuatnya.
Konstruktor Object.GetObject<T>() mencari konstruktor LogTextBox(IntPtr, JniHandleOwneship), memanggilnya, membuat pemetaan antara handel dan instans yang dibuat, dan mengembalikan instans yang dibuat.
TextView.n_GetDefaultMovementMethod() memanggil getter properti LogTextBox.DefaultMovementMethod .
Kontrol kembali ke konstruktor android.widget.TextView , yang menyelesaikan eksekusi.
Konstruktor monodroid.apidemo.LogTextBox dijalankan, memanggil TypeManager.Activate() .
Konstruktor LogTextBox(Context, IAttributeSet, int) dijalankan pada instans yang sama yang dibuat di (7) .
Jika konstruktor (IntPtr, JniHandleOwnership) tidak dapat ditemukan, maka System.MissingMethodException](xref:System.MissingMethodException) akan dilemparkan.
Panggilan Dispose() Prematur
Ada pemetaan antara handel JNI dan instans C# yang sesuai. Java.Lang.Object.Dispose() memutus pemetaan ini. Jika handel JNI memasukkan kode terkelola setelah pemetaan rusak, sepertinya Java Activation, dan konstruktor (IntPtr, JniHandleOwnership) akan diperiksa dan dipanggil. Jika konstruktor tidak ada, maka pengecualian akan dilemparkan.
Misalnya, mengingat subkelas Wraper Yang Dapat Dipanggil Terkelola berikut:
class ManagedValue : Java.Lang.Object {
public string Value {get; private set;}
public ManagedValue (string value)
{
Value = value;
}
public override string ToString ()
{
return string.Format ("[Managed: Value={0}]", Value);
}
}
Jika kita membuat instans, Buang() instans, dan menyebabkan Pembungkus Yang Dapat Dipanggil Terkelola dibuat ulang:
var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());
Program akan mati:
E/mono ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono ( 2906): at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono ( 2906): at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono ( 2906): --- End of inner exception stack trace ---
E/mono ( 2906): at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono ( 2906): at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono ( 2906): at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000
Jika subkelas memang berisi konstruktor (IntPtr, JniHandleOwnership), maka instans baru dari jenis tersebut akan dibuat. Akibatnya, instans akan tampak "kehilangan" semua data instans, karena ini adalah instans baru. (Perhatikan bahwa Nilai null.)
I/mono-stdout( 2993): [Managed: Value=]
Hanya Buang() subkelas pembungkus yang dapat dipanggil terkelola ketika Anda tahu bahwa objek Java tidak akan digunakan lagi, atau subkelas tidak berisi data instans dan konstruktor (IntPtr, JniHandleOwnership) telah disediakan.
Startup Aplikasi
Ketika aktivitas, layanan, dll diluncurkan, Android akan terlebih dahulu memeriksa untuk melihat apakah sudah ada proses yang berjalan untuk menghosting aktivitas/layanan/dll. Jika tidak ada proses seperti itu, maka proses baru akan dibuat, AndroidManifest.xml dibaca, dan jenis yang ditentukan dalam atribut /manifest/application/@android:name dimuat dan dibuat. Selanjutnya, semua jenis yang ditentukan oleh nilai atribut /manifest/application/provider/@android:name dibuat dan meminta metode ContentProvider.attachInfo%28) mereka dipanggil. Xamarin.Android terhubung ke dalam ini dengan menambahkan mono. MonoRuntimeProvider ContentProvider untuk AndroidManifest.xml selama proses build. Mono. Metode MonoRuntimeProvider.attachInfo() bertanggung jawab untuk memuat runtime Mono ke dalam proses. Setiap upaya untuk menggunakan Mono sebelum titik ini akan gagal. ( Catatan: Inilah sebabnya mengapa jenis subkelas Android.App.Application mana yang perlu menyediakan konstruktor (IntPtr, JniHandleOwnership), karena instans Aplikasi dibuat sebelum Mono dapat diinisialisasi.)
Setelah inisialisasi proses selesai, AndroidManifest.xml
dikonsultasikan untuk menemukan nama kelas aktivitas/layanan/dll untuk diluncurkan. Misalnya, atribut /manifest/application/activity/@android:name digunakan untuk menentukan nama Aktivitas yang akan dimuat. Untuk Aktivitas, jenis ini harus mewarisi android.app.Activity.
Jenis yang ditentukan dimuat melalui Class.forName() (yang mengharuskan jenisnya menjadi jenis Java, oleh karena itu Android Callable Wrappers), kemudian dibuat. Pembuatan instans Android Callable Wrapper akan memicu pembuatan instans jenis C# yang sesuai. Android kemudian akan memanggil Activity.onCreate(Bundle) , yang akan menyebabkan Activity.OnCreate(Bundle) yang sesuai dipanggil, dan Anda akan mengikuti balapan.