Principy návrhu rozhraní API Xamarin.Android

Kromě základních knihoven základních tříd, které jsou součástí Mono, se Xamarin.Android dodává s vazbami pro různá rozhraní API pro Android, aby vývojáři mohli vytvářet nativní aplikace pro Android pomocí Mono.

Jádrem Xamarin.Androidu je interop engine, který přemostivá svět C# se světem Java a poskytuje vývojářům přístup k rozhraním Java API z C# nebo jiných jazyků .NET.

Principy návrhu

Toto jsou některé z našich principů návrhu pro vazbu Xamarin.Android.

  • Vyhovuje pokynům pro návrh rozhraní .NET Framework.

  • Umožňuje vývojářům podtřídět třídy Java.

  • Podtřída by měla fungovat se standardními konstrukty jazyka C#.

  • Odvození z existující třídy.

  • Volání základního konstruktoru pro řetězení

  • Přepsání metod by se mělo provádět pomocí systému přepsání jazyka C#.

  • Usnadnit běžné úlohy v Javě a obtížné úlohy v Javě je možné.

  • Zpřístupňte vlastnosti JavaBean jako vlastnosti jazyka C#.

  • Zveřejnění rozhraní API silného typu:

    • Zvyšte bezpečnost typů.

    • Minimalizujte chyby za běhu.

    • Získejte intellisense integrovaného vývojového prostředí (IDE) pro návratové typy.

    • Umožňuje místní dokumentaci integrovaného vývojového prostředí (IDE).

  • Podpora zkoumání rozhraní API v integrovaném vývojovém prostředí (IDE):

    • Použití alternativ architektury k minimalizaci ohrožení třídy Java.

    • Vystavit delegáty jazyka C# (lambda, anonymní metody a System.Delegate) místo rozhraní s jednou metodou, pokud je to vhodné a možné.

    • Zadejte mechanismus pro volání libovolných knihoven Java ( Android.Runtime.JNIEnv).

Sestavení

Xamarin.Android obsahuje řadu sestavení, která tvoří profil MonoMobile. Stránka Sestavení obsahuje další informace.

Vazby na platformu Mono.Android.dll Android jsou obsaženy v sestavení. Toto sestavení obsahuje celou vazbu pro využívání rozhraní ANDROID API a komunikaci s virtuálním počítačem s Modulem Android Runtime.

Návrh vazby

Kolekce

Rozhraní API pro Android využívají kolekce java.util k rozsáhlému poskytování seznamů, sad a map. Tyto prvky zveřejňujeme pomocí rozhraní System.Collections.Generic v naší vazbě. Základní mapování jsou:

Poskytli jsme pomocné třídy, které usnadňují rychlejší zařazování těchto typů bez kopírování. Pokud je to možné, doporučujeme použít tyto poskytnuté kolekce místo implementace poskytované architekturou, například List<T> nebo Dictionary<TKey, TValue>. Implementace Android.Runtime využívají nativní kolekci Java interně, a proto při předávání členu rozhraní ANDROID API nevyžadují kopírování do a z nativní kolekce.

Do metody Androidu, která přijímá toto rozhraní, můžete předat jakoukoli implementaci rozhraní, například předat List<int>konstruktoru ArrayAdapter<int>(Context, int, IList<int).> U všech implementací kromě implementací Android.Runtime to ale zahrnuje zkopírování seznamu z mono virtuálního počítače do virtuálního počítače s Androidem Runtime. Pokud se seznam později změní v modulu Runtime Androidu (např. vyvoláním ArrayAdapter<T>. Metoda Add(T), tyto změny se ve spravovaném kódu nezobrazí . JavaList<int> Pokud by byl použit, tyto změny by se zobrazily.

Rephrased, kolekce rozhraní implementace, které nejsou jedním z výše uvedených pomocných třídes pouze 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.");

Vlastnosti

Metody Java se v případě potřeby transformují na vlastnosti:

  • Dvojice T getFoo() metod Java a void setFoo(T) jsou transformovány na Foo vlastnost. Příklad: Activity.Intent.

  • Metoda getFoo() Java je transformována na vlastnost Foo jen pro čtení. Příklad: Context.PackageName.

  • Vlastnosti pouze sady nejsou generovány.

  • Vlastnosti nejsou generovány, pokud by typ vlastnosti byl pole.

Události a naslouchací procesy

Rozhraní API pro Android jsou založená na Javě a její komponenty se řídí vzorem Javy pro připojení naslouchacích procesů událostí. Tento vzor je obvykle těžkopádný, protože vyžaduje, aby uživatel vytvořil anonymní třídu a deklaroval metody, které se mají přepsat, například takto by se věci prováděly v Androidu s Javou:

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

Ekvivalentní kód v jazyce C# pomocí událostí by byl:

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

Všimněte si, že oba výše uvedené mechanismy jsou k dispozici v Xamarin.Androidu. Můžete implementovat rozhraní naslouchacího procesu a připojit ho pomocí View.SetOnClickListener, nebo můžete připojit delegáta vytvořený prostřednictvím libovolného z obvyklých paradigmat jazyka C# k události Click.

Pokud má metoda zpětného volání naslouchacího procesu návrat void, vytvoříme prvky rozhraní API založené na delegátu TEventArgs obslužné rutiny<> událostí. Pro tyto typy naslouchacího procesu vygenerujeme událost jako v předchozím příkladu. Pokud však zpětné volání naslouchacího procesu vrátí hodnotu, která není void a není logická hodnota, události a EventHandlers se nepoužívají. Místo toho vygenerujeme konkrétního delegáta pro podpis zpětného volání a místo událostí přidáme vlastnosti. Důvodem je zabývat se pořadím vyvolání delegáta a vrácením. Tento přístup odpovídá tomu, co se provádí s rozhraním API Xamarin.iOS.

Události nebo vlastnosti jazyka C# se generují automaticky pouze v případě, že metoda registrace události Androidu:

  1. Má předponu set , například nastavitOnClickListener.

  2. Má návratový void typ.

  3. Přijímá pouze jeden parametr, typ parametru je rozhraní, rozhraní má pouze jednu metodu a název rozhraní končí Listener , například View.OnClick Listener.

Kromě toho, pokud naslouchací proces rozhraní metoda má návratový typ logickémísto void, vygenerovaná eventArgs podtřída bude obsahovat Handled vlastnost. Hodnota Handled vlastnost je použita jako návratová hodnota pro Naslouchací proces metoda, a to je výchozí true.

Například Metoda Android View.setOnKeyListener() přijímá View.OnKeyListener rozhraní a View.OnKeyListener.onKeyListener.onKey(View, int, KeyEvent) má logický návratový typ. Xamarin.Android vygeneruje odpovídající událost View.KeyPress, což je EventHandler<View.KeyEventArgs>. Třída KeyEventArgs má zase View.KeyEventArgs.Handled vlastnost, která se používá jako návratová hodnota pro View.OnKeyListener.onKeyKey() metoda.

Máme v úmyslu přidat přetížení pro jiné metody a ctory k zveřejnění připojení založeného na delegátu. Naslouchací procesy s více zpětnými voláními navíc vyžadují další kontrolu, aby bylo možné určit, jestli je implementace jednotlivých zpětných volání rozumná, takže je převádíme podle toho, jak jsou identifikovány. Pokud neexistuje žádná odpovídající událost, musí být naslouchací procesy použity v jazyce C#, ale přineste všechny, které by podle vás mohly delegovat na naši pozornost. Také jsme provedli některé převody rozhraní bez přípony "Naslouchací proces", když bylo jasné, že by mohly těžit z alternativy delegáta.

Všechna rozhraní naslouchacích procesů implementují Android.Runtime.IJavaObject rozhraní, protože implementace podrobnosti vazby, takže naslouchací procesy třídy musí implementovat toto rozhraní. To lze provést implementací rozhraní naslouchacího procesu na podtřídě Java.Lang.Object nebo jakéhokoli jiného zabaleného objektu Java, jako je aktivita Androidu.

Spustitelné

Java využívá rozhraní java.lang.Runnable k zajištění mechanismu delegování. Třída java.lang.Thread je velmi srozumitelným příjemcem tohoto rozhraní. Android používá také rozhraní v rozhraní API. Příklady aktivit.runOnUiThread() a View.post().

Rozhraní Runnable obsahuje jednu metodu void, run(). Proto se v jazyce C# považuje za delegáta System.Action . V vazbě jsme poskytli přetížení, která přijímají Action parametr pro všechny členy rozhraní API, které využívají Runnable nativní rozhraní API, například Activity.RunOnUiThread() a View.Post().

Nechali jsme na místě přetížení IRunnable místo jejich nahrazení, protože několik typů implementuje rozhraní, a proto lze předat jako runnables přímo.

Vnitřní třídy

Java má dva různé typy vnořených tříd: statické vnořené třídy a nestatické třídy.

Statické vnořené třídy Java jsou shodné s vnořenými typy jazyka C#.

Nestatické vnořené třídy, označované také jako vnitřní třídy, se výrazně liší. Obsahují implicitní odkaz na instanci jejich nadřazeného typu a nemohou obsahovat statické členy (mimo jiné rozdíly mimo rozsah tohoto přehledu).

Pokud jde o vazbu a použití jazyka C#, statické vnořené třídy se považují za normální vnořené typy. Vnitřní třídy mezitím mají dva významné rozdíly:

  1. Implicitní odkaz na obsahující typ musí být zadán explicitně jako parametr konstruktoru.

  2. Při dědění z vnitřní třídy musí být vnitřní třída vnořená do typu, který dědí z typu obsahujícího základní vnitřní třídu, a odvozený typ musí poskytovat konstruktor stejného typu jako typ obsahující jazyk C#.

Představte si například vnitřní třídu Android.Service.Wallpaper.WallpaperService.Engine . Vzhledem k tomu, že se jedná o vnitřní třídu, konstruktor WallpaperService.Engine() přebírá odkaz na instanci WallpaperService (porovnání a kontrast s konstruktorem Java WallpaperService.Engine(), který nepřijímá žádné parametry).

Příkladem odvození vnitřní třídy je CubeWallpaper.CubeEngine:

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

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

Všimněte si, jak CubeWallpaper.CubeEngine je vnořeno do CubeWallpaper, CubeWallpaper dědí z obsahující třídy WallpaperService.Engine, a CubeWallpaper.CubeEngine má konstruktor, který přebírá deklarující typ -- CubeWallpaper v tomto případě - vše, jak je uvedeno výše.

Rozhraní

Rozhraní Java můžou obsahovat tři sady členů, z nichž dvě způsobují problémy z jazyka C#:

  1. Metody

  2. Typy

  3. Pole

Rozhraní Java se překládají do dvou typů:

  1. (volitelné) rozhraní obsahující deklarace metody. Toto rozhraní má stejný název jako rozhraní Java, kromě toho má také předponu I .

  2. (volitelná) statická třída obsahující všechna pole deklarovaná v rozhraní Java.

Vnořené typy se "přemísťují" tak, aby byly na stejné úrovni jako předpona uzavíracího rozhraní místo vnořených typů.

Představte si například rozhraní android.os.Parcelable . Rozhraní Balíkable obsahuje metody, vnořené typy a konstanty. Metody rozhraní Balíkable jsou umístěny do rozhraní Android.OS.IParcelable . Konstanty rozhraní Balíkable jsou umístěny do typu Android.OS.ParcelableConsts . Vnořené typy android.os.Parcelable.ClassLoaderCreator<T> a android.os.Parcelable.Creator<T> nejsou v současné době vázány z důvodu omezení v naší obecné podpoře. Pokud by byly podporovány, byly by přítomné jako rozhraní Android.OS.IParcelableClassLoaderCreator a Android.OS.IParcelableCreator . Například vnořené android.os.IBinder.DeathRecipient rozhraní je vázáno jako Android.OS.IBinderDeathRecipient rozhraní.

Poznámka:

Počínaje Xamarin.Android 1.9 se konstanty rozhraní Java duplikují ve snaze zjednodušit přenos kódu Java. To pomáhá zlepšit portování kódu Java, který spoléhá na konstanty rozhraní zprostředkovatele androidu.

Kromě výše uvedených typů existují čtyři další změny:

  1. Typ se stejným názvem jako rozhraní Java se vygeneruje, aby obsahoval konstanty.

  2. Typy obsahující konstanty rozhraní obsahují také všechny konstanty, které pocházejí z implementovaných rozhraní Java.

  3. Všechny třídy, které implementují rozhraní Java obsahující konstanty, získávají nový vnořený typ InterfaceConsts, který obsahuje konstanty ze všech implementovaných rozhraní.

  4. Typ Consts je nyní zastaralý.

Pro rozhraní android.os.Parcelable to znamená, že nyní bude typ Android.OS.Parcelable obsahovat konstanty. Například Parcelable.CONTENTS_FILE_DESCRIPTOR konstanta bude vázána jako parcelable.ContentsFileDescriptor konstanta místo jako ParcelableConsts.ContentsFileDescriptor konstanta.

U rozhraní obsahujících konstanty, které implementují další rozhraní obsahující ještě více konstant, se teď vygeneruje sjednocení všech konstant. Například rozhraní android.provider.MediaStore.Video.VideoColumns implementuje rozhraní android.provider.MediaStore.MediaColumns . Před verzí 1.9 však typ Android.Provider.MediaStore.Video.VideoColumnsConsts nemá žádný způsob přístupu k konstantám deklarovaným v android.Provider.MediaStore.MediaColumnsConsts. V důsledku toho musí být výraz Java MediaStore.Video.VideoColumns.TITLE vázán na výraz C# MediaStore.Video.MediaColumnsConsts.Title , který je obtížné zjistit, aniž byste museli číst spoustu dokumentace Java. V 1.9 bude ekvivalentní výraz C# MediaStore.Video.VideoColumns.Title.

Zvažte také typ android.os.Bundle , který implementuje rozhraní Java Parcelable . Vzhledem k tomu, že implementuje rozhraní, jsou všechny konstanty v rozhraní přístupné "prostřednictvím" typu Bundle, například Bundle.CONTENTS_FILE_DESCRIPTOR je dokonale platný výraz Java. Pokud chcete tento výraz přenést do jazyka C#, museli byste se podívat na všechna rozhraní, která jsou implementovaná, abyste viděli, z jakého typu CONTENTS_FILE_DESCRIPTOR pochází. Počínaje Xamarin.Android 1.9 třídy implementují rozhraní Java, která obsahují konstanty, budou mít vnořený typ InterfaceConsts , který bude obsahovat všechny zděděné konstanty rozhraní. To umožní přeložit Bundle.CONTENTS_FILE_DESCRIPTOR na Bundle.InterfaceConsts.ContentsFileDescriptor.

Typy s příponou Consts , jako je Android.OS.ParcelableConsts , jsou nyní zastaralé, kromě nově zavedených vnořených typů InterfaceConsts. Odeberou se v Xamarin.Android 3.0.

Zdroje informací

Obrázky, popisy rozložení, binární objekty blob a slovníky řetězců můžou být součástí vaší aplikace jako soubory prostředků. Různá rozhraní API pro Android jsou navržená tak, aby fungovala na ID prostředků místo přímého zpracování obrázků, řetězců nebo binárních objektů blob.

Například ukázková aplikace pro Android, která obsahuje rozložení uživatelského rozhraní ( main.axml), řetězec tabulky internacionalizace ( strings.xml) a některé ikony ( drawable-*/icon.png) by zachovaly své prostředky v adresáři "Resources" aplikace:

Resources/
    drawable-hdpi/
        icon.png

    drawable-ldpi/
        icon.png

    drawable-mdpi/
        icon.png

    layout/
        main.axml

    values/
        strings.xml

Nativní rozhraní API androidu nepracují přímo s názvy souborů, ale pracují s ID prostředků. Při kompilaci aplikace pro Android, která používá prostředky, systém sestavení zabalí prostředky pro distribuci a vygeneruje třídu s názvem Resource , která obsahuje tokeny pro každý z zahrnutých prostředků. Například pro výše uvedené rozložení Resources je to, co by třída R zpřístupnil:

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

Pak byste použili Resource.Drawable.icon odkaz na drawable/icon.png soubor, nebo Resource.Layout.main na layout/main.xml soubor odkazovat nebo Resource.String.first_string odkazovat na první řetězec v souboru values/strings.xmlslovníku .

Konstanty a výčty

Nativní rozhraní API pro Android mají mnoho metod, které přebírají nebo vracejí int, které musí být mapovány na konstantní pole, aby bylo možné určit, co int znamená. Aby uživatel tyto metody používal, musí se podívat do dokumentace a zjistit, které konstanty jsou vhodné hodnoty, což je menší než ideální.

Představte si například Activity.requestWindowFeature(int featureID).

V těchto případech se snažíme seskupit související konstanty do výčtu .NET a přemapovat metodu tak, aby se místo toho výčet bral. Díky tomu můžeme technologii IntelliSense nabídnout výběr potenciálních hodnot.

Výše uvedený příklad se stane: Activity.RequestWindowFeature(WindowFeatures featureId).

Všimněte si, že se jedná o velmi ruční proces, který zjistí, které konstanty patří dohromady a která rozhraní API tyto konstanty spotřebovávají. Zapište chyby pro všechny konstanty použité v rozhraní API, které by byly lépe vyjádřeny jako výčet.