Bagikan melalui


Prinsip Desain API Xamarin.Android

Selain Core Base Class Libraries yang merupakan bagian dari Mono, Xamarin.Android dikirim dengan pengikatan untuk berbagai API Android untuk memungkinkan pengembang membuat aplikasi Android asli dengan Mono.

Inti dari Xamarin.Android ada mesin interop yang menjenjang dunia C# dengan dunia Java dan memberi pengembang akses ke API Java dari C# atau bahasa .NET lainnya.

Prinsip Desain

Ini adalah beberapa prinsip desain kami untuk pengikatan Xamarin.Android

  • Sesuai dengan Pedoman Desain .NET Framework.

  • Izinkan pengembang untuk mensubkelas kelas Java.

  • Subkelas harus bekerja dengan konstruksi standar C#.

  • Berasal dari kelas yang ada.

  • Panggil konstruktor dasar untuk menautkan.

  • Mengambil alih metode harus dilakukan dengan sistem penggantian C#.

  • Buat tugas Java umum menjadi mudah, dan tugas Java keras dimungkinkan.

  • Mengekspos properti JavaBean sebagai properti C#.

  • Mengekspos API yang sangat ditik:

    • Tingkatkan keamanan tipe.

    • Meminimalkan kesalahan runtime.

    • Dapatkan intellisense IDE pada jenis pengembalian.

    • Memungkinkan dokumentasi popup IDE.

  • Dorong eksplorasi in-IDE API:

    • Gunakan Alternatif Kerangka Kerja untuk Meminimalkan paparan Java Classlib.

    • Mengekspos delegasi C# (lambda, metode anonim, dan System.Delegate) alih-alih antarmuka metode tunggal jika sesuai dan berlaku.

    • Berikan mekanisme untuk memanggil pustaka Java arbitrer ( Android.Runtime.JNIEnv).

Rakitan

Xamarin.Android mencakup sejumlah rakitan yang merupakan Profil MonoMobile. Halaman Rakitan memiliki informasi lebih lanjut.

Pengikatan ke platform Android terkandung dalam Mono.Android.dll rakitan. Rakitan ini berisi seluruh pengikatan untuk menggunakan API Android dan berkomunikasi dengan VM runtime Android.

Desain Pengikatan

Koleksi

API Android menggunakan koleksi java.util secara ekstensif untuk menyediakan daftar, set, dan peta. Kami mengekspos elemen-elemen ini menggunakan antarmuka System.Collections.Generic dalam pengikatan kami. Pemetaan mendasar adalah:

Kami telah menyediakan kelas pembantu untuk memfasilitasi marshaling tanpa salinan yang lebih cepat dari jenis ini. Jika memungkinkan, sebaiknya gunakan koleksi yang disediakan ini alih-alih implementasi yang disediakan kerangka kerja, seperti List<T> atau Dictionary<TKey, TValue>. Implementasi Android.Runtime menggunakan koleksi Java asli secara internal dan karenanya tidak memerlukan penyalinan ke dan dari koleksi asli saat meneruskan ke anggota Android API.

Anda dapat meneruskan implementasi antarmuka apa pun ke metode Android yang menerima antarmuka tersebut, misalnya meneruskan List<int> ke konstruktor ArrayAdapter<int>(Context, int, IList<int>). Namun, untuk semua implementasi kecuali untuk implementasi Android.Runtime, ini melibatkan penyalinan daftar dari Mono VM ke dalam VM runtime Android. Jika daftar kemudian diubah dalam runtime Android (misalnya dengan memanggil ArrayAdapter<T>. Metode Add(T ), perubahan tersebut tidak akan terlihat dalam kode terkelola. JavaList<int> Jika digunakan, perubahan tersebut akan terlihat.

Diurai ulang, pengumpulan implementasi antarmuka yang bukan salah satu kelasPembantu yang tercantum di atas hanya marshal [In]:

// This fails:
var badSource  = new List<int> { 1, 2, 3 };
var badAdapter = new ArrayAdapter<int>(context, textViewResourceId, badSource);
badAdapter.Add (4);
if (badSource.Count != 4) // true
    throw new InvalidOperationException ("this is thrown");

// this works:
var goodSource  = new JavaList<int> { 1, 2, 3 };
var goodAdapter = new ArrayAdapter<int> (context, textViewResourceId, goodSource);
goodAdapter.Add (4);
if (goodSource.Count != 4) // false
    throw new InvalidOperationException ("should not be reached.");

Properti

Metode Java diubah menjadi properti, jika sesuai:

  • Pasangan T getFoo() metode Java dan void setFoo(T) diubah menjadi Foo properti . Contoh: Activity.Intent.

  • Metode Java getFoo() diubah menjadi properti Foo baca-saja. Contoh: Context.PackageName.

  • Properti set-only tidak dihasilkan.

  • Properti tidak dihasilkan jika jenis properti akan menjadi array.

Peristiwa dan Pendengar

API Android dibangun di atas Java dan komponennya mengikuti pola Java untuk menghubungkan pendengar peristiwa. Pola ini cenderung rumit karena mengharuskan pengguna untuk membuat kelas anonim dan menyatakan metode yang akan diambil alih, misalnya, ini adalah bagaimana hal-hal akan dilakukan di Android dengan Java:

final android.widget.Button button = new android.widget.Button(context);

button.setText(this.count + " clicks!");
button.setOnClickListener (new View.OnClickListener() {
    public void onClick (View v) {
        button.setText(++this.count + " clicks!");
    }
});

Kode yang setara dalam C# menggunakan peristiwa adalah:

var button = new Android.Widget.Button (context) {
    Text = string.Format ("{0} clicks!", this.count),
};
button.Click += (sender, e) => {
    button.Text = string.Format ("{0} clicks!", ++this.count);
};

Perhatikan bahwa kedua mekanisme di atas tersedia dengan Xamarin.Android. Anda dapat menerapkan antarmuka pendengar dan melampirkannya dengan View.SetOnClickListener, atau Anda dapat melampirkan delegasi yang dibuat melalui salah satu paradigma C# biasa ke peristiwa Klik.

Ketika metode panggilan balik pendengar memiliki pengembalian yang batal, kami membuat elemen API berdasarkan delegasi EventHandler<TEventArgs> . Kami menghasilkan peristiwa seperti contoh di atas untuk jenis pendengar ini. Namun, jika panggilan balik pendengar mengembalikan nilai non-batal dan non-boolean, peristiwa dan EventHandlers tidak digunakan. Sebagai gantinya, kami menghasilkan delegasi tertentu untuk tanda tangan panggilan balik dan menambahkan properti alih-alih peristiwa. Alasannya adalah untuk menangani urutan pemanggilan delegasi dan penanganan pengembalian. Pendekatan ini mencerminkan apa yang dilakukan dengan API Xamarin.iOS.

Peristiwa atau properti C# hanya dibuat secara otomatis jika metode pendaftaran peristiwa Android:

  1. Memiliki set awalan, misalnya , aturOnClickListener.

  2. Memiliki void jenis pengembalian.

  3. Hanya menerima satu parameter, jenis parameter adalah antarmuka, antarmuka hanya memiliki satu metode, dan nama antarmuka berakhiran Listener , misalnya View.OnClick Listener.

Selain itu, jika metode antarmuka Listener memiliki jenis pengembalian boolean alih-alih batal, maka subkelas EventArgs yang dihasilkan akan berisi properti Ditangani. Nilai properti Ditangani digunakan sebagai nilai pengembalian untuk metode Listener , dan defaultnya ke true.

Misalnya, metode Android View.setOnKeyListener() menerima antarmuka View.OnKeyListener, dan metode View.OnKeyListener.onKey(View, int, KeyEvent) memiliki jenis pengembalian boolean. Xamarin.Android menghasilkan peristiwa View.KeyPress yang sesuai, yang merupakan EventHandler<View.KeyEventArgs>. Kelas KeyEventArgs pada gilirannya memiliki properti View.KeyEventArgs.Handled, yang digunakan sebagai nilai pengembalian untuk metode View.OnKeyListener.onKey().

Kami ingin menambahkan kelebihan beban untuk metode dan ctor lain untuk mengekspos koneksi berbasis delegasi. Selain itu, pendengar dengan beberapa panggilan balik memerlukan beberapa inspeksi tambahan untuk menentukan apakah menerapkan panggilan balik individu wajar, jadi kami mengonversinya saat diidentifikasi. Jika tidak ada peristiwa yang sesuai, pendengar harus digunakan dalam C#, tetapi harap bawa apa pun yang menurut Anda dapat mendelegasikan penggunaan untuk perhatian kami. Kami juga telah melakukan beberapa konversi antarmuka tanpa akhiran "Listener" ketika jelas mereka akan mendapat manfaat dari alternatif delegasi.

Semua antarmuka pendengar mengimplementasikan Android.Runtime.IJavaObject antarmuka, karena detail implementasi pengikatan, sehingga kelas pendengar harus mengimplementasikan antarmuka ini. Ini dapat dilakukan dengan mengimplementasikan antarmuka pendengar pada subkelas Java.Lang.Object atau objek Java yang dibungkus lainnya, seperti aktivitas Android.

Runnables

Java menggunakan antarmuka java.lang.Runnable untuk menyediakan mekanisme delegasi. Kelas java.lang.Thread adalah konsumen terkemuka dari antarmuka ini. Android juga telah menggunakan antarmuka di API. Activity.runOnUiThread() dan View.post() adalah contoh penting.

Antarmuka Runnable berisi metode void tunggal, run(). Oleh karena itu meminjamkan dirinya untuk mengikat dalam C# sebagai delegasi System.Action . Kami telah menyediakan kelebihan beban dalam pengikatan yang menerima Action parameter untuk semua anggota API yang menggunakan Runnable di API asli, misalnya Activity.RunOnUiThread() dan View.Post().

Kami membiarkan kelebihan beban IRunnable di tempat alih-alih menggantinya karena beberapa jenis mengimplementasikan antarmuka dan oleh karena itu dapat diteruskan sebagai runnable secara langsung.

Kelas Dalam

Java memiliki dua jenis kelas berlapis yang berbeda: kelas berlapis statis dan kelas non-statis.

Kelas berlapis statis Java identik dengan jenis berlapis C#.

Kelas berlapis non-statis, juga disebut kelas dalam, sangat berbeda. Mereka berisi referensi implisit ke instans jenis penutupnya dan tidak dapat berisi anggota statis (di antara perbedaan lain di luar cakupan gambaran umum ini).

Dalam hal pengikatan dan penggunaan C#, kelas berlapis statis diperlakukan sebagai jenis berlapis normal. Kelas dalam, sementara itu, memiliki dua perbedaan signifikan:

  1. Referensi implisit ke jenis yang berisi harus disediakan secara eksplisit sebagai parameter konstruktor.

  2. Saat mewarisi dari kelas dalam, kelas dalam harus ditumpuk dalam jenis yang mewarisi dari jenis yang berisi kelas dalam dasar, dan jenis turunan harus menyediakan konstruktor dengan jenis yang sama dengan C# yang berisi jenis.

Misalnya, pertimbangkan kelas dalam Android.Service.Wallpaper.WallpaperService.Engine . Karena ini adalah kelas dalam, konstruktor WallpaperService.Engine() mengambil referensi ke instans WallpaperService (bandingkan dan kontras dengan konstruktor Java WallpaperService.Engine(), yang tidak mengambil parameter).

Contoh turunan dari kelas dalam adalah CubeWallpaper.CubeEngine:

class CubeWallpaper : WallpaperService {
    public override WallpaperService.Engine OnCreateEngine ()
    {
        return new CubeEngine (this);
    }

    class CubeEngine : WallpaperService.Engine {
        public CubeEngine (CubeWallpaper s)
                : base (s)
        {
        }
    }
}

Perhatikan bagaimana CubeWallpaper.CubeEngine ditumpuk dalam CubeWallpaper, CubeWallpaper mewarisi dari kelas WallpaperService.Engineyang berisi , dan CubeWallpaper.CubeEngine memiliki konstruktor yang mengambil jenis deklarasikan -- CubeWallpaper dalam hal ini -- semua seperti yang ditentukan di atas.

Antarmuka

Antarmuka Java dapat berisi tiga set anggota, dua di antaranya menyebabkan masalah dari C#:

  1. Metode

  2. Jenis

  3. Bidang

Antarmuka Java diterjemahkan ke dalam dua jenis:

  1. Antarmuka (opsional) yang berisi deklarasi metode. Antarmuka ini memiliki nama yang sama dengan antarmuka Java, kecuali juga memiliki awalan ' I '.

  2. Kelas statis (opsional) yang berisi bidang apa pun yang dideklarasikan dalam antarmuka Java.

Jenis berlapis "direlokasi" menjadi saudara kandung dari antarmuka penutup alih-alih jenis berlapis, dengan nama antarmuka penutup sebagai awalan.

Misalnya, pertimbangkan antarmuka android.os.Parcelable . Antarmuka Parcelable berisi metode, jenis berlapis, dan konstanta. Metode antarmuka Parcelable ditempatkan ke antarmuka Android.OS.IParcelable . Konstanta antarmuka Parcelable ditempatkan ke dalam jenis Android.OS.ParcelableConsts. Jenis android.os.Parcelable.ClassLoaderCreator<T> dan android.os.Parcelable.Creator<T> saat ini tidak terikat karena keterbatasan dalam dukungan generik kami; jika didukung, mereka akan hadir sebagai antarmuka Android.OS.IParcelableClassLoaderCreator dan Android.OS.IParcelableCreator. Misalnya, antarmuka android.os.IBinder.DeathRecipient berlapis terikat sebagai antarmuka Android.OS.IBinderDeathRecipient.

Catatan

Dimulai dengan Xamarin.Android 1.9, konstanta antarmuka Java diduplikasi dalam upaya untuk menyederhanakan porting kode Java. Ini membantu meningkatkan porting kode Java yang bergantung pada konstanta antarmuka penyedia android.

Selain jenis di atas, ada empat perubahan lebih lanjut:

  1. Jenis dengan nama yang sama dengan antarmuka Java dihasilkan untuk berisi konstanta.

  2. Jenis yang berisi konstanta antarmuka juga berisi semua konstanta yang berasal dari antarmuka Java yang diimplementasikan.

  3. Semua kelas yang mengimplementasikan antarmuka Java yang berisi konstanta mendapatkan jenis InterfaceConsts berlapis baru yang berisi konstanta dari semua antarmuka yang diimplementasikan.

  4. Jenis Consts sekarang usang.

Untuk antarmuka android.os.Parcelable, ini berarti bahwa sekarang akan ada jenis Android.OS.Parcelable untuk berisi konstanta. Misalnya, konstanta Parcelable.CONTENTS_FILE_DESCRIPTOR akan terikat sebagai konstanta Parcelable.ContentsFileDescriptor, alih-alih sebagai konstanta ParcelableConsts.ContentsFileDescriptor.

Untuk antarmuka yang berisi konstanta yang mengimplementasikan antarmuka lain yang berisi lebih banyak konstanta, gabungan semua konstanta sekarang dihasilkan. Misalnya, antarmuka android.provider.MediaStore.Video.VideoColumns mengimplementasikan antarmuka android.provider.MediaStore.MediaColumns . Namun, sebelum 1.9, jenis Android.Provider.MediaStore.Video.VideoColumnsConsts tidak memiliki cara untuk mengakses konstanta yang dideklarasikan di Android.Provider.MediaStore.MediaColumnsConsts. Akibatnya, ekspresi Java MediaStore.Video.VideoColumns.TITLE perlu terikat ke ekspresi C# MediaStore.Video.MediaColumnsConsts.Title yang sulit ditemukan tanpa membaca banyak dokumentasi Java. Dalam 1.9, ekspresi C# yang setara akan menjadi MediaStore.Video.VideoColumns.Title.

Selain itu, pertimbangkan jenis android.os.Bundle , yang mengimplementasikan antarmuka Java Parcelable . Karena mengimplementasikan antarmuka, semua konstanta pada antarmuka tersebut dapat diakses "melalui" jenis Bundel, misalnya Bundle.CONTENTS_FILE_DESCRIPTOR adalah ekspresi Java yang valid dengan sempurna. Sebelumnya, untuk memindahkan ekspresi ini ke C# Anda harus melihat semua antarmuka yang diimplementasikan untuk melihat dari jenis mana CONTENTS_FILE_DESCRIPTOR berasal. Mulai Xamarin.Android 1.9, kelas yang menerapkan antarmuka Java yang berisi konstanta akan memiliki jenis InterfaceConsts berlapis, yang akan berisi semua konstanta antarmuka yang diwariskan. Ini akan memungkinkan penerjemahan Bundle.CONTENTS_FILE_DESCRIPTOR ke Bundle.InterfaceConsts.ContentsFileDescriptor.

Terakhir, jenis dengan akhiran Consts seperti Android.OS.ParcelableConsts sekarang usang, selain jenis berlapis InterfaceConsts yang baru diperkenalkan. Mereka akan dihapus di Xamarin.Android 3.0.

Sumber

Gambar, deskripsi tata letak, blob biner, dan kamus string dapat disertakan dalam aplikasi Anda sebagai file sumber daya. Berbagai API Android dirancang untuk beroperasi pada ID sumber daya alih-alih berurusan dengan gambar, string, atau blob biner secara langsung.

Misalnya, contoh aplikasi Android yang berisi tata letak antarmuka pengguna ( main.axml), string tabel internasionalisasi ( strings.xml) dan beberapa ikon ( drawable-*/icon.png) akan menyimpan sumber dayanya di direktori "Sumber Daya" aplikasi:

Resources/
    drawable-hdpi/
        icon.png

    drawable-ldpi/
        icon.png

    drawable-mdpi/
        icon.png

    layout/
        main.axml

    values/
        strings.xml

API Android asli tidak beroperasi langsung dengan nama file, tetapi sebaliknya beroperasi pada ID sumber daya. Saat Anda mengkompilasi aplikasi Android yang menggunakan sumber daya, sistem build akan mengemas sumber daya untuk distribusi dan menghasilkan kelas yang disebut Resource yang berisi token untuk masing-masing sumber daya yang disertakan. Misalnya, untuk tata letak Sumber Daya di atas, inilah yang akan diekspos oleh kelas R:

public class Resource {
    public class Drawable {
        public const int icon = 0x123;
    }

    public class Layout {
        public const int main = 0x456;
    }

    public class String {
        public const int first_string = 0xabc;
        public const int second_string = 0xbcd;
    }
}

Anda kemudian akan menggunakan Resource.Drawable.icon untuk mereferensikan drawable/icon.png file, atau Resource.Layout.main untuk mereferensikan layout/main.xml file, atau Resource.String.first_string untuk mereferensikan string pertama dalam file values/strings.xmlkamus .

Konstanta dan Enumerasi

API Android asli memiliki banyak metode yang mengambil atau mengembalikan int yang harus dipetakan ke bidang konstan untuk menentukan apa arti int. Untuk menggunakan metode ini, pengguna diharuskan untuk berkonsultasi dengan dokumentasi untuk melihat konstanta mana yang sesuai nilai, yang kurang dari ideal.

Misalnya, pertimbangkan Activity.requestWindowFeature(int featureID).

Dalam kasus ini, kami berusaha untuk mengelompokkan konstanta terkait bersama-sama ke dalam enumerasi .NET, dan memulihkan metode untuk mengambil enumerasi sebagai gantinya. Dengan melakukan ini, kita dapat menawarkan pemilihan IntelliSense dari nilai potensial.

Contoh di atas menjadi: Activity.RequestWindowFeature(WindowFeatures featureId).

Perhatikan bahwa ini adalah proses yang sangat manual untuk mencari tahu konstanta mana yang dimiliki bersama-sama, dan API mana yang menggunakan konstanta ini. Harap file bug untuk setiap konstanta yang digunakan dalam API yang akan lebih baik dinyatakan sebagai enumerasi.