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.
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:
java.util.Set<E> maps to system type ICollection<T>, pomocné třídy Android.Runtime.JavaSet<T>.
java.util.List<E> mapuje na typ systému IList<T>, pomocné třídy Android.Runtime.JavaList<T>.
java.util.Map K,V maps to system type IDictionary<TKey,TValue>, helper class Android.Runtime.JavaDictionary<K,V>.><
java.util.Collection E> mapuje na typ systému ICollection<T>, pomocná třída Android.Runtime.JavaCollection<T>.<
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 avoid setFoo(T)
jsou transformovány naFoo
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:
Má předponu
set
, například nastavitOnClickListener.Má návratový
void
typ.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:
Implicitní odkaz na obsahující typ musí být zadán explicitně jako parametr konstruktoru.
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#:
Metody
Typy
Pole
Rozhraní Java se překládají do dvou typů:
(volitelné) rozhraní obsahující deklarace metody. Toto rozhraní má stejný název jako rozhraní Java, kromě toho má také předponu I .
(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:
Typ se stejným názvem jako rozhraní Java se vygeneruje, aby obsahoval konstanty.
Typy obsahující konstanty rozhraní obsahují také všechny konstanty, které pocházejí z implementovaných rozhraní Java.
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í.
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.xml
slovní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.