Práce s JNI a Xamarin.Androidem
Xamarin.Android umožňuje psát aplikace pro Android pomocí jazyka C# místo Javy. V Xamarin.Androidu je k dispozici několik sestavení, která poskytují vazby pro knihovny Java, včetně Mono.Android.dll a Mono.Android.GoogleMaps.dll. Vazby však nejsou k dispozici pro každou možnou knihovnu Java a zadané vazby nemusí svázat všechny typy a členy Javy. Chcete-li použít nevázané typy a členy Javy, je možné použít rozhraní JNI (Java Native Interface). Tento článek ukazuje, jak pomocí JNI pracovat s typy a členy Javy z aplikací Xamarin.Android.
Přehled
K vyvolání kódu Java není vždy nutné ani není možné vytvořit spravovaný obálkový obálka s možností volání (MCW). V mnoha případech je "vložené" JNI dokonale přijatelné a užitečné pro jednorázové použití nevázaných členů Javy. Často je jednodušší použít JNI k vyvolání jedné metody ve třídě Java, než vygenerovat celou .jar vazbu.
Xamarin.Android poskytuje Mono.Android.dll
sestavení, které poskytuje vazbu pro knihovnu Androidu android.jar
. Typy a členy, které nejsou přítomny uvnitř Mono.Android.dll
, a typy, které android.jar
nejsou přítomny, mohou být použity ruční vazbou. K vytvoření vazby typů a členů Jazyka Java použijete JNI (Java Native Interface) k vyhledávání, čtení a zápisu polí a vyvolání metod.
Rozhraní JNI API v Xamarin.Androidu je koncepčně velmi podobné System.Reflection
rozhraní API v .NET: umožňuje vyhledávat typy a členy podle názvu, číst a zapisovat hodnoty polí, vyvolat metody a další. Pomocí JNI a vlastního atributu Android.Runtime.RegisterAttribute
můžete deklarovat virtuální metody, které lze svázat s podporou přepsání. Rozhraní můžete svázat, aby bylo možné je implementovat v jazyce C#.
Tento dokument vysvětluje:
- Jak JNI odkazuje na typy.
- Jak vyhledávat, číst a zapisovat pole
- Jak vyhledat a vyvolat metody
- Jak zveřejnit virtuální metody umožňující přepsání ze spravovaného kódu
- Jak vystavit rozhraní
Požadavky
JNI, jak je vystaveno prostřednictvím oboru názvů Android.Runtime.JNIEnv, je k dispozici ve všech verzích Xamarin.Android. K vytvoření vazby typů a rozhraní Java musíte použít Xamarin.Android 4.0 nebo novější.
Spravované obálky s možností volání
Managed Callable Wrapper (MCW) je vazba pro třídu nebo rozhraní Java, která zabalí všechny stroje JNI tak, aby se klientský kód C# nemusel starat o základní složitost JNI. Většina se skládá ze spravovaných Mono.Android.dll
obálkových volání.
Spravované obálky s možností volání slouží ke dvěma účelům:
- Zapouzdřte JNI, aby klientský kód nemusel znát základní složitost.
- Umožňuje podtříděné typy Javy a implementovat rozhraní Java.
Prvním účelem je čistě pohodlí a zapouzdření složitosti, aby spotřebitelé měli jednoduchou spravovanou sadu tříd, které se mají použít. To vyžaduje použití různých členů JNIEnv , jak je popsáno dále v tomto článku. Mějte na paměti, že spravované obálky s možností volání nejsou nezbytně nutné – použití JNI "vložené" JNI je naprosto přijatelné a je užitečné pro jednorázové použití nevázaných členů Javy. Implementace podtříd a rozhraní vyžaduje použití spravovaných obálkových volání.
Obálky Androidu s možností volání
Obálky volatelné pro Android (ACW) jsou vyžadovány vždy, když modul runtime Androidu (ART) potřebuje vyvolat spravovaný kód; tyto obálky jsou vyžadovány, protože neexistuje způsob, jak registrovat třídy v ART za běhu. (Konkrétně se jedná o Modul runtime Androidu nepodporuje funkci DefineClass JNI. Obálky s možností volání pro Android se tak shodí s nedostatkem podpory registrace typu modulu runtime.)
Pokaždé, když kód Androidu potřebuje spustit virtuální metodu nebo metodu rozhraní, která se přepíše nebo implementuje ve spravovaném kódu, musí Xamarin.Android poskytnout proxy jazyka Java, aby se tato metoda odeslala do příslušného spravovaného typu. Tyto typy proxy serverů v Javě jsou kód Java, který má "stejnou" základní třídu a seznam rozhraní Java jako spravovaný typ, implementuje stejné konstruktory a deklaruje všechny přepsané základní třídy a metody rozhraní.
Obálky volatelné pro Android jsou generovány programem monodroid.exe během procesu sestavení a jsou generovány pro všechny typy, které (přímo nebo nepřímo) dědí Java.Lang.Object.
Implementace rozhraní
Někdy může být potřeba implementovat rozhraní Androidu (například Android.Content.IComponentCallbacks).
Všechny třídy a rozhraní androidu rozšiřují rozhraní Android.Runtime.IJavaObject ; proto musí implementovat IJavaObject
všechny typy Androidu .
Xamarin.Android využívá tuto skutečnost – poskytuje IJavaObject
Androidu proxy server Java (obálku s možností volání pro Android) pro daný spravovaný typ. Protože monodroid.exe hledá Java.Lang.Object
pouze podtřídy (které musí implementovat IJavaObject
), poskytuje podtřídy Java.Lang.Object
způsob, jak implementovat rozhraní ve spravovaném kódu. Příklad:
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...
}
}
Podrobnosti implementace
Zbývající část tohoto článku obsahuje podrobnosti implementace, které se můžou změnit bez předchozího upozornění (a jsou zde uvedeny pouze proto, že vývojáři můžou být zvědaví na to, co se děje pod kapotou).
Například vzhledem k následujícímu zdroji jazyka C#:
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 vygeneruje následující obálku pro Android Callable Wrapper:
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);
}
Všimněte si, že základní třída je zachována a deklarace nativní metody jsou k dispozici pro každou metodu, která je přepsána v rámci spravovaného kódu.
ExportAttribute a ExportFieldAttribute
Xamarin.Android obvykle automaticky vygeneruje kód Java, který zahrnuje ACW; tato generace je založena na názvech tříd a metod, pokud třída je odvozena z třídy Java a přepíše existující metody Java. V některých scénářích ale generování kódu není adekvátní, jak je uvedeno níže:
Android podporuje názvy akcí v atributech XML rozložení, například atribut XML android:onClick . Při zadání se nafoukaná instance View pokusí vyhledat metodu Java.
Rozhraní java.io.Serializable vyžaduje
readObject
awriteObject
metody. Vzhledem k tomu, že nejsou členy tohoto rozhraní, naše odpovídající spravovaná implementace nezpřístupňuje tyto metody kódu Java.Rozhraní android.os.Parcelable očekává, že třída implementace musí mít statické pole
CREATOR
typuParcelable.Creator
. Vygenerovaný kód Java vyžaduje určité explicitní pole. V našem standardním scénáři neexistuje způsob, jak výstupní pole v kódu Java ze spravovaného kódu vytvořit.
Vzhledem k tomu, že generování kódu neposkytuje řešení pro generování libovolných metod Java s libovolnými názvy, počínaje Xamarin.Android 4.2, exportAttribute a ExportFieldAttribute byly zavedeny, aby nabídly řešení výše uvedeným scénářům. Oba atributy se nacházejí v Java.Interop
oboru názvů:
ExportAttribute
– určuje název metody a jeho očekávané typy výjimek (pro explicitní vyvolání v Javě). Když se použije u metody, metoda "exportuje" metodu Java, která vygeneruje kód dispečera do odpovídajícího volání JNI do spravované metody. To lze použít sandroid:onClick
ajava.io.Serializable
.ExportFieldAttribute
– určuje název pole. Nachází se v metodě, která funguje jako inicializátor pole. To lze použít sandroid.os.Parcelable
.
Řešení potíží s ExportAttribute a ExportFieldAttribute
Balení selže kvůli chybějícímu Mono.Android.Export.dll – pokud jste použili
ExportAttribute
neboExportFieldAttribute
použili některé metody v kódu nebo závislých knihovnách, musíte přidat Mono.Android.Export.dll. Toto sestavení je izolované pro podporu kódu zpětného volání z Javy. Je oddělená od Mono.Android.dll , protože přidává do aplikace další velikost.V buildu
MissingMethodException
vydané verze dochází u metod exportu – v sestaveníMissingMethodException
vydané verze dochází pro metody exportu. (Tento problém je opravený v nejnovější verzi Xamarin.Android.)
ExportParameterAttribute
ExportAttribute
a ExportFieldAttribute
poskytují funkce, které může kód za běhu v Javě používat. Tento kód za běhu přistupuje ke spravovanému kódu prostřednictvím vygenerovaných metod JNI řízených těmito atributy. V důsledku toho neexistuje žádná existující metoda Java, kterou spravovaná metoda vytvoří vazbu; proto se metoda Java vygeneruje z podpisu spravované metody.
Tento případ však není zcela determinantní. To platí zejména v některých pokročilých mapováních mezi spravovanými typy a typy Javy, například:
- InputStream
- Výstupní stream
- XmlPullParser
- XmlResourceParser
Pokud jsou pro exportované metody potřeba takové typy, ExportParameterAttribute
musí být použity k explicitnímu zadání odpovídajícího parametru nebo návratové hodnoty typu.
Atribut poznámky
V Xamarin.Android 4.2 jsme převedli IAnnotation
typy implementace na atributy (System.Attribute) a přidali jsme podporu generování poznámek v obálkách Java.
To znamená následující směrové změny:
Generátor vazeb se generuje
Java.Lang.DeprecatedAttribute
zejava.Lang.Deprecated
spravovaného kódu (zatímco by měl být[Obsolete]
ve spravovaném kódu).To neznamená, že existující
Java.Lang.Deprecated
třída zmizí. Tyto objekty založené na Javě lze stále používat jako obvyklé objekty Java (pokud takové použití existuje). Tam budouDeprecated
aDeprecatedAttribute
třídy.Třída
Java.Lang.DeprecatedAttribute
je označena jako[Annotation]
. Pokud existuje vlastní atribut, který je zděděn z tohoto[Annotation]
atributu, úloha msbuild vygeneruje poznámku Java pro tento vlastní atribut (@Deprecated) v volatelné obálky Androidu (ACW).Poznámky lze generovat do tříd, metod a exportovaných polí (což je metoda ve spravovaném kódu).
Pokud není zaregistrovaná obsahující třída (samotná anotovaná třída nebo třída obsahující členy s poznámkami), negeneruje se vůbec celý zdroj třídy Java včetně poznámek. U metod můžete určit ExportAttribute
, jak získat metodu explicitně vygenerovanou a anotovanou. Není to také funkce k "vygenerování" definice třídy poznámek Java. Jinými slovy, pokud pro určitou poznámku definujete vlastní spravovaný atribut, budete muset přidat další knihovnu .jar, která obsahuje odpovídající třídu poznámek Java. Přidání zdrojového souboru Java, který definuje typ poznámky, není dostačující. Kompilátor Java nefunguje stejným způsobem jako apt.
Navíc platí následující omezení:
Tento proces převodu zatím nebere v úvahu
@Target
poznámky k typu poznámky.Atributy na vlastnost nefungují. Místo toho použijte atributy pro getter nebo setter.
Vazba třídy
Vazba třídy znamená zápis spravovaného obálky s možností volání, aby se zjednodušilo vyvolání základního typu Java.
Vytvoření vazby virtuálních a abstraktních metod pro povolení přepsání z jazyka C# vyžaduje Xamarin.Android 4.0. Jakákoli verze Xamarin.Android však může svázat ne virtuální metody, statické metody nebo virtuální metody bez podpory přepsání.
Vazba obvykle obsahuje následující položky:
Pokud je požadováno podtřídění, typ musí mít u deklarace typu vlastní atribut RegisterAttribute.DoNotGenerateAcw nastavený na .
true
Deklarování popisovače typu
Vyhledávací metody pole a metody vyžadují odkaz na objekt odkazující na jejich deklarující typ. Podle konvence se to koná v class_ref
poli:
static IntPtr class_ref = JNIEnv.FindClass(CLASS);
Podrobnosti o tokenu CLASS
najdete v části Odkazy na typy JNI.
Pole vazby
Pole Java jsou vystavena jako vlastnosti jazyka C#, například pole Java java.lang.System.in je vázáno jako vlastnost jazyka C# Java.Lang.JavaSystem.In. Vzhledem k tomu, že JNI rozlišuje mezi statickými poli a poli instance, se při implementaci vlastností používají různé metody.
Vazba pole zahrnuje tři sady metod:
Metoda get field id . Metoda get field ID je zodpovědná za vrácení popisovače pole, který bude používat hodnota pole get a nastavit metody hodnoty pole. Získání ID pole vyžaduje znalost deklarujícího typu, názvu pole a podpisu typu JNI pole.
Metody získání hodnoty pole. Tyto metody vyžadují popisovač pole a zodpovídají za čtení hodnoty pole z Javy. Metoda, která se má použít, závisí na typu pole.
Metody hodnoty pole set. Tyto metody vyžadují popisovač pole a zodpovídají za zápis hodnoty pole v javě. Metoda, která se má použít, závisí na typu pole.
Statická pole používají metody JNIEnv.GetStaticFieldID a JNIEnv.GetStatic*Field
JNIEnv.SetStaticField.
Pole instance používají metody JNIEnv.GetFieldIDJNIEnv.Get*Field
a JNIEnv.SetField.
Statickou vlastnost JavaSystem.In
lze například implementovat takto:
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);
}
}
Poznámka: K převodu odkazu JNI na instanci používáme InputStreamInvoker.FromJniHandle a používáme JniHandleOwnership.TransferLocalRef
ji, protože JNIEnv.GetStaticObjectField vrátí místní System.IO.Stream
odkaz.
Mnoho typů Android.Runtime obsahuje FromJniHandle
metody, které převedou odkaz JNI na požadovaný typ.
Vazba metody
Metody Java se zveřejňují jako metody jazyka C# a jako vlastnosti jazyka C#. Například metoda Java java.lang.Runtime.runFinalizersOnExit metoda je vázána jako Java.Lang.Runtime.RunFinalizersOnExit metoda a java.lang.Object.getClass metoda je vázána jako Java.Lang.Object.Class vlastnost.
Vyvolání metody je dvoustupňový proces:
ID metody get pro vyvolání metody. Metoda get method id je zodpovědná za vrácení popisovače metody, kterou metody vyvolání metody budou používat. Získání ID metody vyžaduje znalost deklarujícího typu, názvu metody a podpisu typu JNI metody.
Vyvolá metodu.
Stejně jako u polí se metody používané k získání ID metody a vyvolání metody liší mezi statickými metodami a metodami instance.
Statické metody používají JNIEnv.GetStaticMethodID () k vyhledání ID metody a používají JNIEnv.CallStatic*Method
řadu metod pro vyvolání.
Metody instance používají JNIEnv.GetMethodID k vyhledání ID metody a používají JNIEnv.Call*Method
a JNIEnv.CallNonvirtual*Method
rodiny metod pro vyvolání.
Vazba metody je potenciálně více než jen volání metody. Vazba metody také zahrnuje povolení přepsání metody (pro abstraktní a nedokončné metody) nebo implementované (pro metody rozhraní). Část Podpora dědičnosti, rozhraní se zabývá složitostí podpůrných virtuálních metod a metod rozhraní.
Statické metody
Vytvoření vazby statické metody zahrnuje použití JNIEnv.GetStaticMethodID
k získání popisovače metody a následné použití příslušné JNIEnv.CallStatic*Method
metody v závislosti na návratovém typu metody. Následuje příklad vazby pro metodu 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);
}
Všimněte si, že popisovač metody ukládáme do statického pole , id_getRuntime
. Jedná se o optimalizaci výkonu, takže popisovač metody nemusí být vyhledáno při každém vyvolání. Tímto způsobem není nutné ukládat popisovač metody do mezipaměti. Jakmile se získá popisovač metody, JNIEnv.CallStaticObjectMethod se použije k vyvolání metody. JNIEnv.CallStaticObjectMethod
vrátí popisovač IntPtr
vrácené instance Javy.
Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) se používá k převodu popisovače Java na instanci objektu silného typu.
Vazby metody jiné než virtuální instance
Vytvoření vazby final
metody instance nebo metody instance, která nevyžaduje přepsání, zahrnuje použití JNIEnv.GetMethodID
k získání popisovače metody a následné použití příslušné JNIEnv.Call*Method
metody v závislosti na návratovém typu metody. Následuje příklad vazby vlastnosti Object.Class
:
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);
}
}
Všimněte si, že popisovač metody ukládáme do statického pole , id_getClass
.
Jedná se o optimalizaci výkonu, takže popisovač metody nemusí být vyhledáno při každém vyvolání. Tímto způsobem není nutné ukládat popisovač metody do mezipaměti. Jakmile se získá popisovač metody, JNIEnv.CallStaticObjectMethod se použije k vyvolání metody. JNIEnv.CallStaticObjectMethod
vrátí popisovač IntPtr
vrácené instance Javy.
Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) se používá k převodu popisovače Java na instanci objektu silného typu.
Konstruktory vazeb
Konstruktory jsou metody Java s názvem "<init>"
. Stejně jako u metod JNIEnv.GetMethodID
instancí Java se používá k vyhledání popisovače konstruktoru. Na rozdíl od metod Jazyka Java se metody JNIEnv.NewObject používají k vyvolání popisovače metody konstruktoru. Návratová JNIEnv.NewObject
hodnota je místní referenční dokumentace 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…
Vazba třídy obvykle podtřídě Java.Lang.Object.
Při podtřídě Java.Lang.Object
, další sémantika přichází do hry: Java.Lang.Object
instance udržuje globální odkaz na instanci Java prostřednictvím Java.Lang.Object.Handle
vlastnosti.
Výchozí
Java.Lang.Object
konstruktor přidělí instanci Javy.Pokud typ obsahuje a
RegisterAttribute
RegisterAttribute.DoNotGenerateAcw
jetrue
, pak instanceRegisterAttribute.Name
typu je vytvořena prostřednictvím jeho výchozí konstruktoru.Jinak se volající obálka Androidu (ACW) odpovídající
this.GetType
instanci vytvoří prostřednictvím výchozího konstruktoru. Obálky volatelné pro Android jsou generovány během vytváření balíčku pro každouJava.Lang.Object
podtřídu, pro kterouRegisterAttribute.DoNotGenerateAcw
není nastavena .true
U typů, které nejsou vazby tříd, se jedná o očekávanou sémantickou instanci: Vytvoření instance Mono.Samples.HelloWorld.HelloAndroid
jazyka C# by měla vytvořit instanci Jazyka Java mono.samples.helloworld.HelloAndroid
, která je vygenerovaným obálkou callable pro Android.
U vazeb tříd to může být správné chování, pokud typ Java obsahuje výchozí konstruktor nebo není nutné vyvolat žádný jiný konstruktor. V opačném případě je nutné zadat konstruktor, který provede následující akce:
Vyvolání Java.Lang.Object(IntPtr, JniHandleOwnership) místo výchozího
Java.Lang.Object
konstruktoru. To je potřeba, aby se zabránilo vytvoření nové instance Java.Před vytvořením instancí Javy zkontrolujte hodnotu Java.Lang.Object.Handle . Vlastnost
Object.Handle
bude mít jinou hodnotu nežIntPtr.Zero
v případě, že byl v kódu Jazyka Java vytvořen Callable Wrapper androidu a vazba třídy je vytvořena tak, aby obsahovala vytvořenou instanci Android Callable Wrapper. Například když Android vytvořímono.samples.helloworld.HelloAndroid
instanci, Android Callable Wrapper se vytvoří jako první a konstruktor Jazyka JavaHelloAndroid
vytvoří instanci odpovídajícíhoMono.Samples.HelloWorld.HelloAndroid
typu sObject.Handle
vlastností nastavenou na instanci Java před spuštěním konstruktoru.Pokud aktuální typ modulu runtime není stejný jako deklarující typ, je nutné vytvořit instanci odpovídající obálky volání androidu a použít Object.SetHandle k uložení popisovače vrácené JNIEnv.CreateInstance.
Pokud je aktuální typ modulu runtime stejný jako deklarující typ, vyvoláte konstruktor Jazyka Java a pomocí Object.SetHandle uložte popisovač vrácený
JNIEnv.NewInstance
.
Představte si například konstruktor java.lang.Integer(int). Toto je vázáno jako:
// 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);
}
JNIEnv.CreateInstance metody jsou pomocné rutiny k provedení JNIEnv.FindClass
, JNIEnv.NewObject
JNIEnv.GetMethodID
, a JNIEnv.DeleteGlobalReference
na hodnotu vrácenou z JNIEnv.FindClass
. Podrobnosti naleznete v další části.
Podpora dědičnosti, rozhraní
Podtřídy typu Java nebo implementace rozhraní Javy vyžadují generování obálky volatelné pro Android (ACWs), které jsou generovány pro každou Java.Lang.Object
podtřídu během procesu balení. Generování ACW se řídí pomocí vlastního atributu Android.Runtime.RegisterAttribute .
Pro typy jazyka C# vyžaduje konstruktor vlastního atributu [Register]
jeden argument: zjednodušený odkaz JNI pro odpovídající typ Javy. To umožňuje poskytovat různé názvy mezi Javou a C#.
Před Xamarin.Android 4.0 [Register]
nebyl vlastní atribut dostupný pro existující typy Javy. Důvodem je to, že proces generování acW by vygeneroval ACWs pro každou Java.Lang.Object
podtřídu, ke které došlo.
Xamarin.Android 4.0 zavedl vlastnost RegisterAttribute.DoNotGenerateAcw . Tato vlastnost dává procesu generování ACW pokyn, aby přeskočil anotovaný typ, což umožňuje deklaraci nových spravovaných obálkových volání, které nebudou mít za následek generování ACWs při vytváření balíčku. To umožňuje vazbu existujících typů Javy. Představte si například následující jednoduchou třídu Java, Adder
která obsahuje jednu metodu, add
která se přidá k celým číslům a vrátí výsledek:
package mono.android.test;
public class Adder {
public int add (int a, int b) {
return a + b;
}
}
Typ Adder
může být vázán jako:
[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 {
}
V této části aliasy Adder
Adder
typu jazyka C# typ Java. Atribut [Register]
se používá k určení názvu mono.android.test.Adder
JNI typu Java a DoNotGenerateAcw
vlastnost slouží k inhibici generování ACW. Výsledkem bude generování acW pro ManagedAdder
typ, který správně podtřídy mono.android.test.Adder
typu. Pokud vlastnost RegisterAttribute.DoNotGenerateAcw
nebyla použita, proces sestavení Xamarin.Android by vygeneroval nový mono.android.test.Adder
typ Javy. Výsledkem by byly chyby kompilace, protože mono.android.test.Adder
typ by byl přítomen dvakrát, ve dvou samostatných souborech.
Vazby virtuálních metod
ManagedAdder
podtřídy typu Java Adder
, ale není to zvlášť zajímavé: typ C# Adder
nedefinuje žádné virtuální metody, takže ManagedAdder
nic nejde přepsat.
Vazbové virtual
metody umožňující přepsání podtřídami vyžadují několik věcí, které je potřeba provést, které spadají do následujících dvou kategorií:
Vazba metody
Registrace metody
Vazba metody
Vazba metody vyžaduje přidání dvou členů podpory do definice jazyka C# Adder
: ThresholdType
a ThresholdClass
.
ThresholdType
Vlastnost ThresholdType
vrátí aktuální typ vazby:
partial class Adder {
protected override System.Type ThresholdType {
get {
return typeof (Adder);
}
}
}
ThresholdType
se používá v vazbu metody k určení, kdy má provádět virtuální vs. ne-virtuální metoda dispatch. Vždy by měla vrátit System.Type
instanci, která odpovídá deklarování typu jazyka C#.
ThresholdClass
Vlastnost ThresholdClass
vrátí odkaz na třídu JNI pro vázaný typ:
partial class Adder {
protected override IntPtr ThresholdClass {
get {
return class_ref;
}
}
}
ThresholdClass
se používá v vazbu metody při vyvolání ne-virtuálních metod.
Implementace vazby
Implementace vazby metody zodpovídá za vyvolání metody Java za běhu. Obsahuje také deklaraci vlastního [Register]
atributu, která je součástí registrace metody, a bude popsána v části Registrace metody:
[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));
}
}
Pole id_add
obsahuje ID metody, které má metoda Java vyvolat. Hodnota id_add
je získána z JNIEnv.GetMethodID
, která vyžaduje deklarování třídy (class_ref
), název metody Java ("add"
) a podpis JNI metody ("(II)I"
).
Po získání ID metody se porovná ThresholdType
s určením, GetType
jestli se vyžaduje virtuální nebo ne virtuální dispečer. Virtuální odeslání se vyžaduje, pokud GetType
odpovídá ThresholdType
, jak Handle
může odkazovat na podtřídu přidělenou jazykem Java, která přepíše metodu.
Pokud GetType
se neshoduje ThresholdType
, Adder
byl podtříděn (např. podle ManagedAdder
) a Adder.Add
implementace bude vyvolána pouze v případě, že je vyvolána base.Add
podtřída . Jedná se o ne virtuální případ odeslání, což je místo, kde ThresholdClass
přichází. ThresholdClass
určuje, která třída Java poskytne implementaci metody, která se má vyvolat.
Registrace metody
Předpokládejme, že máme aktualizovanou ManagedAdder
definici, která přepíše metodu Adder.Add
:
partial class ManagedAdder : Adder {
public override int Add (int a, int b) {
return (a*2) + (b*2);
}
}
Vzpomeňte [Register]
si, že Adder.Add
má vlastní atribut:
[Register ("add", "(II)I", "GetAddHandler")]
Konstruktor [Register]
vlastního atributu přijímá tři hodnoty:
Název metody Java,
"add"
v tomto případě.Podpis typu JNI metody v
"(II)I"
tomto případě.Metoda konektoru ,
GetAddHandler
v tomto případě. Metody konektoru budou popsány později.
První dva parametry umožňují procesu generování ACW vygenerovat deklaraci metody přepsání metody. Výsledný acW by obsahoval některý z následujících kódů:
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);
// ...
}
Všimněte si, že @Override
metoda je deklarována, která deleguje na metodu n_
-prefixed se stejným názvem. Tím zajistíte, že se při vyvolání ManagedAdder.add
ManagedAdder.n_add
kódu Java vyvolá, což umožní spuštění přepsání metody C#ManagedAdder.Add
.
Takže nejdůležitější otázka: jak je ManagedAdder.n_add
připojeno k ManagedAdder.Add
?
Metody Java native
se registrují v modulu runtime Java (Android Runtime) prostřednictvím funkce JNI RegisterNatives.
RegisterNatives
přebírá pole struktur obsahující název metody Java, JNI Type Signature a ukazatel funkce vyvolat, které se řídí konvencí volání JNI.
Ukazatel funkce musí být funkce, která přebírá dva argumenty ukazatele následované parametry metody. Metoda Java ManagedAdder.n_add
musí být implementována prostřednictvím funkce, která má následující prototyp jazyka C:
int FunctionName(JNIEnv *env, jobject this, int a, int b)
Xamarin.Android nezpřístupňuje metodu RegisterNatives
. Místo toho acW a MCW společně poskytují informace potřebné k vyvolání RegisterNatives
: ACW obsahuje název metody a podpis typu JNI, jedinou chybějící věcí je ukazatel funkce pro připojení.
Tady přichází metoda konektoru. Třetí [Register]
parametr vlastního atributu je název metody definované v registrovaném typu nebo základní třída registrovaného typu, která nepřijímá žádné parametry a vrací System.Delegate
. System.Delegate
Vrácená funkce zase odkazuje na metodu, která má správný podpis funkce JNI. Nakonec delegát, který metoda konektoru vrací , musí být rootován, aby GC neshromažďoval, protože delegát je poskytován v Javě.
#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
Metoda GetAddHandler
vytvoří delegáta Func<IntPtr, IntPtr, int, int, int>
, který odkazuje na metodu n_Add
a pak vyvolá JNINativeWrapper.CreateDelegate.
JNINativeWrapper.CreateDelegate
zabalí zadanou metodu do bloku try/catch, aby byly zpracovány všechny neošetřené výjimky a výsledkem bude vyvolání události AndroidEvent.UnhandledExceptionRaiser . Výsledný delegát je uložen ve statické cb_add
proměnné, aby GC nezvolil delegáta.
n_Add
Nakonec je metoda zodpovědná za zařazování parametrů JNI do odpovídajících spravovaných typů a delegování volání metody.
Poznámka: Vždy se používá JniHandleOwnership.DoNotTransfer
při získávání MCW přes instanci Javy. Zacházení s nimi jako s místním odkazem (a tím volánímJNIEnv.DeleteLocalRef
) se přeruší spravované přechody zásobníku v Javě>>.
Úplná vazba doplňku
Úplná spravovaná vazba pro mono.android.tests.Adder
typ je:
[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
}
Omezení
Při psaní typu, který odpovídá následujícím kritériím:
Podtříd
Java.Lang.Object
[Register]
Má vlastní atribut.RegisterAttribute.DoNotGenerateAcw
jetrue
Pro interakci GC nesmí typ obsahovat žádná pole, která mohou odkazovat na podtřídu Java.Lang.Object
Java.Lang.Object
za běhu. Například pole typu System.Object
a jakéhokoli typu rozhraní nejsou povolena. Typy, které nemohou odkazovat na Java.Lang.Object
instance, jsou povoleny, například System.String
a List<int>
. Toto omezení je zabránit předčasnému shromažďování objektů uvolňováním paměti.
Pokud typ musí obsahovat pole instance, které může odkazovat na Java.Lang.Object
instanci, musí být System.WeakReference
typ pole nebo GCHandle
.
Abstraktní metody vazby
Metody vazby abstract
jsou z velké části identické s vazbou virtuálních metod. Existují pouze dva rozdíly:
Abstraktní metoda je abstraktní. Stále zachovává
[Register]
atribut a přidruženou registraci metody, vazba metody je právě přesunuta doInvoker
typu.Vytvoří se netyp
abstract
Invoker
, který podtřídy abstraktního typu. TypInvoker
musí přepsat všechny abstraktní metody deklarované v základní třídě a přepsaná implementace je implementace Method Binding, i když je možné ignorovat ne-virtuální případ odeslání.
Předpokládejme například, že výše uvedená mono.android.test.Adder.add
metoda byla abstract
. Vazba jazyka C# se změnila tak, aby Adder.Add
byla abstraktní, a byl by definován nový AdderInvoker
typ, který implementoval 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));
}
}
Typ Invoker
je nutný pouze při získávání odkazů JNI na instance vytvořené v Javě.
Rozhraní vazeb
Rozhraní vazeb jsou koncepčně podobná třídám vazeb obsahujícím virtuální metody, ale mnoho specifik se liší jemnými (a ne tak jemnými) způsoby. Zvažte následující deklaraci rozhraní Java:
public interface Progress {
void onAdd(int[] values, int currentIndex, int currentSum);
}
Vazby rozhraní mají dvě části: definici rozhraní jazyka C# a definici invokeru pro rozhraní.
Definice rozhraní
Definice rozhraní jazyka C# musí splňovat následující požadavky:
Definice rozhraní musí mít
[Register]
vlastní atribut.Definice rozhraní musí rozšířit
IJavaObject interface
. Pokud to neuděláte, zabráníte tomu, aby acWs dědil z rozhraní Javy.Každá metoda rozhraní musí obsahovat
[Register]
atribut určující odpovídající název metody Java, podpis JNI a metodu konektoru.Metoda konektoru musí také zadat typ, na kterém může být metoda konektoru umístěna.
Při vazbě abstract
a virtual
metodách by metoda konektoru byla prohledána v hierarchii dědičnosti zaregistrovaného typu. Rozhraní nemohou obsahovat žádné metody obsahující těla, takže to nefunguje, a proto je nutné zadat typ označující, kde se nachází metoda spojnice. Typ je zadán v řetězci metody konektoru za dvojtečku ':'
a musí být název kvalifikovaného typu sestavení typu obsahujícího invoker.
Deklarace metody rozhraní jsou překladem odpovídající metody Java pomocí kompatibilních typů. U integrovaných typů Java jsou kompatibilní typy odpovídajícími typy jazyka C#, např. Java int
je C# int
. U referenčních typů je kompatibilní typ, který může poskytnout popisovač JNI příslušného typu Java.
Členy rozhraní nebudou přímo vyvolány Jazykem Java – vyvolání se zprostředkuje prostřednictvím typu Invoker, takže je povoleno určité množství flexibility.
Rozhraní Java Progress lze deklarovat v jazyce C# jako:
[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);
}
Všimněte si ve výše uvedeném příkladu, že namapujeme parametr Java int[]
na int> JavaArray<.
To není nutné: mohli jsme ho svázat s jazykem C# int[]
nebo s něčím IList<int>
jiným. Bez ohledu na to, který typ zvolíte, Invoker
musí být schopný ho přeložit na typ Java int[]
pro vyvolání.
Definice invokeru
Definice Invoker
typu musí dědit Java.Lang.Object
, implementovat příslušné rozhraní a poskytnout všechny metody připojení odkazované v definici rozhraní. Existuje ještě jeden návrh, který se liší od vazby třídy: class_ref
pole a ID metod by měly být členy instance, nikoli statické členy.
Důvod, proč preferovat členy instance, musí být v modulu Runtime Androidu co do činění s chováním JNIEnv.GetMethodID
. (Může se jednat také o chování v Javě, které se neotestovalo.) JNIEnv.GetMethodID
vrátí hodnotu null při vyhledávání metody, která pochází z implementovaného rozhraní, a ne deklarovaného rozhraní. Zvažte java.util.SortedMap<K, V> Java rozhraní, které implementuje java.util.Map<K, V> rozhraní. Mapa poskytuje jasnou metodu, takže zdánlivě rozumná Invoker
definice Pro SortedMap by byla:
// 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);
}
// ...
}
Výše uvedené hodnoty selžou, protože JNIEnv.GetMethodID
se vrátí null
při vyhledávání Map.clear
metody prostřednictvím SortedMap
instance třídy.
Existují dvě řešení: sledovat, které rozhraní každá metoda pochází, a mít class_ref
pro každé rozhraní, nebo zachovat vše jako členy instance a provádět vyhledávání metody u nejvíce odvozeného typu třídy, ne typu rozhraní. Druhý se provádí v Mono.Android.dll.
Definice Invokeru má šest částí: konstruktor, metodu Dispose
, ThresholdType
členy, ThresholdClass
metodu GetObject
, implementaci metody rozhraní a implementaci metody konektoru.
Konstruktor
Konstruktor musí vyhledat třídu runtime vyvoláné instance a uložit třídu runtime do pole instance 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);
}
}
Poznámka: Vlastnost Handle
musí být použita v těle konstruktoru handle
, a ne parametr, protože v Androidu v4.0 handle
může být parametr po dokončení provádění základního konstruktoru neplatný.
Dispose – metoda
Metoda Dispose
musí uvolnit globální odkaz přidělený v konstruktoru:
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 a ThresholdClass
Členové ThresholdType
jsou ThresholdClass
identické s tím, co se nachází ve vazbě třídy:
partial class IAdderProgressInvoker {
protected override Type ThresholdType {
get {
return typeof (IAdderProgressInvoker);
}
}
protected override IntPtr ThresholdClass {
get {
return class_ref;
}
}
}
GetObject – metoda
Pro podporu Extensions.JavaCast<T>()se vyžaduje statická GetObject
metoda:
partial class IAdderProgressInvoker {
public static IAdderProgress GetObject (IntPtr handle, JniHandleOwnership transfer)
{
return new IAdderProgressInvoker (handle, transfer);
}
}
Metody rozhraní
Každá metoda rozhraní musí mít implementaci, která vyvolá odpovídající metodu Java prostřednictvím 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));
}
}
Metody spojnice
Metody konektoru a podpůrná infrastruktura zodpovídají za zařazování parametrů JNI do příslušných typů C#. Parametr Java int[]
se předá jako JNI jintArray
, což je IntPtr
v jazyce C#. Aby IntPtr
bylo možné podporovat vyvolání rozhraní jazyka C#, musí být zařazováno do JavaArray<int>
seznamu:
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);
}
}
}
Pokud int[]
by bylo upřednostňovánoJavaList<int>
, můžete místo toho použít JNIEnv.GetArray():
int[] _values = (int[]) JNIEnv.GetArray(values, JniHandleOwnership.DoNotTransfer, typeof (int));
Všimněte si však, že JNIEnv.GetArray
kopíruje celé pole mezi virtuálními počítači, takže u velkých polí to může vést k velkému množství přidaného tlaku GC.
Úplná definice invokeru
Úplná definice IAdderProgressInvoker:
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
}
Odkazy na objekty JNI
Mnoho metod JNIEnv vrací odkazy na objekt JNI, které jsou podobné GCHandle
s. JNI poskytuje tři různé typy odkazů na objekty: místní odkazy, globální odkazy a slabé globální odkazy. Všechny tři jsou reprezentovány jako System.IntPtr
, ale (podle oddílu Typy funkcí JNI) nejsou všechny IntPtr
vrácené z JNIEnv
metod odkazy. Například JNIEnv.GetMethodID vrátí hodnotu IntPtr
, ale nevrací odkaz na objekt, vrátí hodnotu jmethodID
. Podrobnosti najdete v dokumentaci k funkcím JNI.
Většina metod vytváření odkazů vytváří místní odkazy.
Android umožňuje, aby v daném okamžiku existoval omezený počet místních odkazů, obvykle 512. Místní odkazy lze odstranit prostřednictvím JNIEnv.DeleteLocalRef.
Na rozdíl od JNI ne všechny referenční JNIEnv metody, které vracejí odkazy na objekt vracejí místní odkazy; JNIEnv.FindClass vrátí globální odkaz. Důrazně doporučujeme odstranit místní odkazy co nejrychleji, pravděpodobně vytvořením java.Lang.Object kolem objektu a zadáním JniHandleOwnership.TransferLocalRef
konstruktoru Java.Lang.Object(IntPtr handle, JniHandleOwnership) konstruktoru.
Globální odkazy vytváří JNIEnv.NewGlobalRef a JNIEnv.FindClass. Mohou být zničeny pomocí JNIEnv.DeleteGlobalRef. Emulátory mají limit 2 000 nevyřízených globálních odkazů, zatímco hardwarová zařízení mají limit přibližně 52 000 globálních odkazů.
Slabé globální odkazy jsou k dispozici pouze pro Android verze 2.2 (Froyo) a novější. Slabé globální odkazy lze odstranit pomocí JNIEnv.DeleteWeakGlobalRef.
Práce s místními odkazy JNI
Metody JNIEnv.GetObjectField, JNIEnv.GetStaticObjectField, JNIEnv.CallObjectMethod, JNIEnv.CallNonvirtualObjectMethod a JNIEnv.CallStaticObjectMethod vrátí IntPtr
metodu, která obsahuje místní odkaz JNI na objekt Java, nebo IntPtr.Zero
pokud java vrátí null
. Vzhledem k omezenému počtu místních odkazů, které mohou být nevyrovnané najednou (512 položek), je žádoucí zajistit, aby byly odkazy odstraněny včas. Existují tři způsoby, jak se dají místní odkazy zpracovat: explicitně je odstranit, vytvořit Java.Lang.Object
instanci, která je bude uchovávat, a použít Java.Lang.Object.GetObject<T>()
k vytvoření spravovaného obálky s možností volání.
Explicitní odstranění místních odkazů
JNIEnv.DeleteLocalRef slouží k odstranění místních odkazů. Jakmile se místní odkaz odstraní, už ho nejde použít, proto je potřeba dbát na to, aby se zajistilo, že JNIEnv.DeleteLocalRef
se jedná o poslední věc provedenou s místním odkazem.
IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
try {
// Do something with `lref`
}
finally {
JNIEnv.DeleteLocalRef (lref);
}
Zabalení pomocí Java.Lang.Object
Java.Lang.Object
poskytuje konstruktor Java.Lang.Object(IntPtr handle, JniHandleOwnership), který lze použít k zabalení ukončujícího odkazu JNI. Parametr JniHandleOwnership určuje způsob zpracování parametru IntPtr
:
JniHandleOwnership.DoNotTransfer – vytvořená
Java.Lang.Object
instance vytvoří nový globální odkaz z parametruhandle
ahandle
nezmění se. Volající je v případě potřeby zodpovědný za uvolněníhandle
.JniHandleOwnership.TransferLocalRef – vytvořená
Java.Lang.Object
instance vytvoří nový globální odkaz z parametruhandle
ahandle
odstraní se pomocí JNIEnv.DeleteLocalRef . Volající nesmí být volnýhandle
a nesmí používathandle
po dokončení provádění konstruktoru.JniHandleOwnership.TransferGlobalRef – vytvořená
Java.Lang.Object
instance převezme vlastnictví parametruhandle
. Volající nesmí být volnýhandle
.
Vzhledem k tomu, že metody volání metody JNI vracejí místní odkazy, JniHandleOwnership.TransferLocalRef
by se normálně používaly:
IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef);
Vytvořený globální odkaz nebude uvolněn, dokud Java.Lang.Object
instance nebude uvolněna z paměti. Pokud budete schopni, uvolnění instance uvolní globální odkaz a urychlí uvolňování paměti:
IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
using (var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef)) {
// use value ...
}
Použití Java.Lang.Object.GetObject<T>()
Java.Lang.Object
poskytuje metodu Java.Lang.Object.GetObject<T>(IntPtr handle, JniHandleOwnership transfer), která se dá použít k vytvoření spravovaného volatelného obálky zadaného typu.
Typ T
musí splňovat následující požadavky:
T
musí být referenčním typem.T
musí implementovatIJavaObject
rozhraní.Pokud
T
není abstraktní třída nebo rozhraní, musíT
poskytnout konstruktor s typy(IntPtr, JniHandleOwnership)
parametrů .Pokud
T
je abstraktní třída nebo rozhraní, musí být k dispozici invoker proT
. Invoker je ne abstraktní typ, který dědíT
nebo implementujeT
, a má stejný název jakoT
s příponou Invoker. Pokud je například T rozhraníJava.Lang.IRunnable
, typJava.Lang.IRunnableInvoker
musí existovat a musí obsahovat požadovaný(IntPtr, JniHandleOwnership)
konstruktor.
Vzhledem k tomu, že metody volání metody JNI vracejí místní odkazy, JniHandleOwnership.TransferLocalRef
by se normálně používaly:
IntPtr lrefString = JNIEnv.CallObjectMethod(instance, methodID);
Java.Lang.String value = Java.Lang.Object.GetObject<Java.Lang.String>( lrefString, JniHandleOwnership.TransferLocalRef);
Vyhledávání typů Java
Pokud chcete vyhledat pole nebo metodu v JNI, musí být nejprve vyhledán deklarující typ pole nebo metody. Metoda Android.Runtime.JNIEnv.FindClass(string)) se používá k vyhledávání typů Java. Řetězcový parametr je zjednodušeným odkazem na typ nebo úplným odkazem na typ Java. Podrobnosti o zjednodušených a úplných odkazech na typy najdete v části Odkazy na typy JNI.
Poznámka: Na rozdíl od každé jiné JNIEnv
metody, která vrací instance objektů, FindClass
vrátí globální odkaz, nikoli místní odkaz.
Pole instancí
Pole se manipulují s ID polí. ID polí se získávají prostřednictvím JNIEnv.GetFieldID, která vyžaduje třídu, ve které je pole definováno, název pole a podpis typu JNI pole.
ID polí nemusí být uvolněna a jsou platná, pokud je načten odpovídající typ Javy. (Android v současné době nepodporuje uvolňování tříd.)
Existují dvě sady metod pro manipulaci s poli instance: jednu pro čtení polí instance a jednu pro zápis polí instance. Všechny sady metod vyžadují ID pole ke čtení nebo zápisu hodnoty pole.
Čtení hodnot polí instance
Sada metod pro čtení hodnot polí instance se řídí vzorem pojmenování:
* JNIEnv.Get*Field(IntPtr instance, IntPtr fieldID);
kde *
je typ pole:
JNIEnv.GetObjectField – přečte hodnotu libovolného pole instance, které není předdefinovaný typ, například
java.lang.Object
, pole a typy rozhraní. Vrácená hodnota je místní odkaz JNI.JNIEnv.GetBooleanField – Čtení hodnoty
bool
polí instance.JNIEnv.GetByteField – čtení hodnoty
sbyte
polí instance.JNIEnv.GetCharField – čtení hodnoty
char
polí instance.JNIEnv.GetShortField – čtení hodnoty
short
polí instance.JNIEnv.GetIntField – čtení hodnoty
int
polí instance.JNIEnv.GetLongField – Čtení hodnoty
long
polí instance.JNIEnv.GetFloatField – čtení hodnoty
float
polí instance.JNIEnv.GetDoubleField – čtení hodnoty
double
polí instance.
Zápis hodnot polí instance
Sada metod zápisu hodnot polí instance se řídí vzorem pojmenování:
JNIEnv.SetField(IntPtr instance, IntPtr fieldID, Type value);
where Type je typ pole:
JNIEnv.SetField) – Zapište hodnotu libovolného pole, které není předdefinovaný typ, například
java.lang.Object
, pole a typy rozhraní. HodnotaIntPtr
může být místní odkaz JNI, globální odkaz JNI, slabý globální odkaz JNI neboIntPtr.Zero
(pronull
).JNIEnv.SetField) – Zapište hodnotu
bool
polí instance.JNIEnv.SetField) – Zapište hodnotu
sbyte
polí instance.JNIEnv.SetField) – Zapište hodnotu
char
polí instance.JNIEnv.SetField) – Zapište hodnotu
short
polí instance.JNIEnv.SetField) – Zapište hodnotu
int
polí instance.JNIEnv.SetField) – Zapište hodnotu
long
polí instance.JNIEnv.SetField) – Zapište hodnotu
float
polí instance.JNIEnv.SetField) – Zapište hodnotu
double
polí instance.
Statická pole
Statická pole se manipulují s ID polí. ID polí se získávají prostřednictvím JNIEnv.GetStaticFieldID, která vyžaduje třídu, ve které je pole definováno, název pole a podpis typu JNI pole.
ID polí nemusí být uvolněna a jsou platná, pokud je načten odpovídající typ Javy. (Android v současné době nepodporuje uvolňování tříd.)
Existují dvě sady metod pro manipulaci se statickými poli: jednu pro čtení polí instance a jednu pro zápis polí instance. Všechny sady metod vyžadují ID pole ke čtení nebo zápisu hodnoty pole.
Čtení hodnot statických polí
Sada metod pro čtení hodnot statických polí se řídí vzorem pojmenování:
* JNIEnv.GetStatic*Field(IntPtr class, IntPtr fieldID);
kde *
je typ pole:
JNIEnv.GetStaticObjectField – přečte hodnotu jakéhokoli statického pole, které není předdefinovaný typ, například
java.lang.Object
, pole a typy rozhraní. Vrácená hodnota je místní odkaz JNI.JNIEnv.GetStaticBooleanField – přečte hodnotu statických
bool
polí.JNIEnv.GetStaticByteField – přečte hodnotu statických
sbyte
polí.JNIEnv.GetStaticCharField – přečte hodnotu statických
char
polí.JNIEnv.GetStaticShortField – čtení hodnoty statických
short
polí.JNIEnv.GetStaticLongField – čtení hodnoty statických
long
polí.JNIEnv.GetStaticFloatField – Čtení hodnoty statických
float
políJNIEnv.GetStaticDoubleField – čtení hodnoty statických
double
polí
Zápis hodnot statických polí
Sada metod zápisu statických hodnot polí se řídí vzorem pojmenování:
JNIEnv.SetStaticField(IntPtr class, IntPtr fieldID, Type value);
where Type je typ pole:
JNIEnv.SetStaticField) – Zapište hodnotu libovolného statického pole, které není předdefinovaný typ, například
java.lang.Object
, pole a typy rozhraní. HodnotaIntPtr
může být místní odkaz JNI, globální odkaz JNI, slabý globální odkaz JNI neboIntPtr.Zero
(pronull
).JNIEnv.SetStaticField) – Zapište hodnotu statických
bool
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
sbyte
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
char
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
short
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
int
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
long
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
float
polí.JNIEnv.SetStaticField) – Zapište hodnotu statických
double
polí.
Metody instance
Metody instance se vyvolávají prostřednictvím ID metod. ID metody se získávají prostřednictvím JNIEnv.GetMethodID, který vyžaduje typ, ve kterém je metoda definována, název metody a podpis typu JNI metody.
ID metod nemusí být uvolněna a jsou platná, pokud je načten odpovídající typ Javy. (Android v současné době nepodporuje uvolňování tříd.)
Existují dvě sady metod pro vyvolání metod: jednu pro virtuální vyvolání metod a jednu pro vyvolání metod, které nejsou virtuální. Obě sady metod vyžadují ID metody k vyvolání metody a ne virtuální vyvolání také vyžaduje, abyste určili, která implementace třídy má být vyvolána.
Metody rozhraní lze vyhledat pouze v rámci deklarujícího typu; metody, které pocházejí z rozšířených nebo zděděných rozhraní, nelze vyhledat. Další podrobnosti najdete v části Implementace rozhraní pro pozdější vazby nebo invoker.
Lze vyhledat jakoukoli metodu deklarovanou ve třídě nebo jakékoli základní třídě nebo implementovaném rozhraní.
Vyvolání virtuální metody
Sada metod pro vyvolání metod se prakticky řídí vzorem pojmenování:
* JNIEnv.Call*Method( IntPtr instance, IntPtr methodID, params JValue[] args );
kde *
je návratový typ metody.
JNIEnv.CallObjectMethod – Vyvolá metodu, která vrací nedefinovaný typ, například
java.lang.Object
, pole a rozhraní. Vrácená hodnota je místní odkaz JNI.JNIEnv.CallBooleanMethod – Vyvolá metodu
bool
, která vrátí hodnotu.JNIEnv.CallByteMethod – vyvolá metodu
sbyte
, která vrátí hodnotu.JNIEnv.CallCharMethod – Vyvolá metodu
char
, která vrátí hodnotu.JNIEnv.CallShortMethod – Vyvolá metodu
short
, která vrátí hodnotu.JNIEnv.CallLongMethod – Vyvolá metodu
long
, která vrátí hodnotu.JNIEnv.CallFloatMethod – Vyvolá metodu
float
, která vrátí hodnotu.JNIEnv.CallDoubleMethod – Vyvolá metodu
double
, která vrátí hodnotu.
Volání jiné než virtuální metody
Sada metod pro vyvolání metod, které nejsou prakticky, se řídí vzorem pojmenování:
* JNIEnv.CallNonvirtual*Method( IntPtr instance, IntPtr class, IntPtr methodID, params JValue[] args );
kde *
je návratový typ metody. Volání jiné než virtuální metody se obvykle používá k vyvolání základní metody virtuální metody.
JNIEnv.CallNonvirtualObjectMethod – nevirtuálně vyvolat metodu, která vrací nedefinovaný typ, například
java.lang.Object
, pole a rozhraní. Vrácená hodnota je místní odkaz JNI.JNIEnv.CallNonvirtualBooleanMethod – non-virtually invoke a method, která vrací
bool
hodnotu.JNIEnv.CallNonvirtualByteMethod – nevirtuálně vyvolá metodu
sbyte
, která vrátí hodnotu.JNIEnv.CallNonvirtualCharMethod – nevirtuálně vyvolat metodu
char
, která vrací hodnotu.JNIEnv.CallNonvirtualShortMethod – non-virtually invoke a method, která vrací
short
hodnotu.JNIEnv.CallNonvirtualLongMethod – nevirtuálně vyvolá metodu
long
, která vrátí hodnotu.JNIEnv.CallNonvirtualFloatMethod – non-virtually invoke a method, která vrací
float
hodnotu.JNIEnv.CallNonvirtualDoubleMethod – non-virtually invoke a method, která vrací
double
hodnotu.
Statické metody
Statické metody se vyvolávají prostřednictvím ID metod. ID metody se získávají prostřednictvím JNIEnv.GetStaticMethodID, který vyžaduje typ, ve kterém je metoda definována, název metody a JNI Type Signature metody.
ID metod nemusí být uvolněna a jsou platná, pokud je načten odpovídající typ Javy. (Android v současné době nepodporuje uvolňování tříd.)
Vyvolání statické metody
Sada metod pro vyvolání metod se prakticky řídí vzorem pojmenování:
* JNIEnv.CallStatic*Method( IntPtr class, IntPtr methodID, params JValue[] args );
kde *
je návratový typ metody.
JNIEnv.CallStaticObjectMethod – Vyvolá statickou metodu, která vrátí nedefinovaný typ, například
java.lang.Object
, pole a rozhraní. Vrácená hodnota je místní odkaz JNI.JNIEnv.CallStaticBooleanMethod – vyvolá statickou metodu
bool
, která vrátí hodnotu.JNIEnv.CallStaticByteMethod – vyvolá statickou metodu
sbyte
, která vrátí hodnotu.JNIEnv.CallStaticCharMethod – Vyvolá statickou metodu
char
, která vrátí hodnotu.JNIEnv.CallStaticShortMethod – vyvolá statickou metodu
short
, která vrátí hodnotu.JNIEnv.CallStaticLongMethod – vyvolá statickou metodu
long
, která vrátí hodnotu.JNIEnv.CallStaticFloatMethod – vyvolá statickou metodu
float
, která vrátí hodnotu.JNIEnv.CallStaticDoubleMethod – vyvolá statickou metodu
double
, která vrátí hodnotu.
Podpisy typu JNI
Podpisy typu JNI jsou odkazy typu JNI (i když nejsou zjednodušené odkazy na typy), s výjimkou metod. S metodami JNI Type Signature je otevřená závorka '('
, následované odkazy na typ pro všechny typy parametrů zřetězeny dohromady (bez oddělení čárky nebo cokoli jiného), následované pravou závorkou ')'
, následované odkazem typu JNI návratového typu metody.
Například s ohledem na metodu Java:
long f(int n, String s, int[] array);
Podpis typu JNI by byl:
(ILjava/lang/String;[I)J
Obecně se důrazně doporučuje použít javap
příkaz k určení podpisů JNI. Například podpis typu JNI metody java.lang.Thread.State.valueOf(String) je "(Ljava/lang/String;).Ljava/lang/Thread$State;" zatímco podpis typu JNI metody java.lang.Thread.State.values je "()[Ljava/lang/Thread$State;". Dávejte pozor na koncové středníky; jsou součástí podpisu typu JNI.
Odkazy na typy JNI
Odkazy na typy JNI se liší od odkazů na typy Javy. Nemůžete použít plně kvalifikované názvy typů Javy, jako java.lang.String
je JNI, musíte místo toho použít varianty "java/lang/String"
JNI nebo "Ljava/lang/String;"
v závislosti na kontextu. Podrobnosti najdete níže.
Existují čtyři typy odkazů na typy JNI:
- vestavěný
- zjednodušený
- type
- pole
Předdefinované odkazy na typy
Předdefinované odkazy na typy jsou jeden znak, který slouží k odkazu na předdefinované typy hodnot. Mapování hodnot je následující:
"B"
prosbyte
."S"
proshort
."I"
proint
."J"
prolong
."F"
profloat
."D"
prodouble
."C"
prochar
."Z"
probool
."V"
provoid
návratové typy metody.
Zjednodušené odkazy na typy
Zjednodušené odkazy na typy lze použít pouze v JNIEnv.FindClass(string)). Zjednodušené odkazy na typ lze odvodit dvěma způsoby:
Z plně kvalifikovaného názvu Javy nahraďte každý
'.'
v názvu balíčku a před názvem'/'
typu , a každý'.'
v názvu typu s'$'
.Přečtěte si výstup funkce
'unzip -l android.jar | grep JavaName'
.
Výsledkem obou dvou bude mapování typu Java java.lang.Thread.State na zjednodušený odkaz java/lang/Thread$State
na typ .
Odkazy na typy
Odkaz na typ je předdefinovaný odkaz na typ nebo zjednodušený odkaz na typ s předponou 'L'
a příponou ';'
. Pro java typ java.lang.String je zjednodušený typ odkaz je "java/lang/String"
, zatímco typ odkaz je "Ljava/lang/String;"
.
Odkazy na typy se používají s odkazy na typ pole a s podpisy JNI.
Dalším způsobem, jak získat odkaz na typ, je čtení výstupu 'javap -s -classpath android.jar fully.qualified.Java.Name'
.
V závislosti na použitém typu můžete k určení názvu JNI použít deklaraci konstruktoru nebo návratový typ metody. Příklad:
$ 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
je typ výčtu Java, takže můžeme použít Signature valueOf
metody k určení, že typ odkaz je Ljava/lang/Thread$State;.
Odkazy na typ pole
Odkazy na typ pole mají '['
předponu odkazu na typ JNI.
Zjednodušené odkazy na typy nelze použít při zadávání polí.
Například je , int[]
int[][]
je "[I"
"[[I"
a java.lang.Object[]
je "[Ljava/lang/Object;"
.
Obecné typy Java a mazání typů
Většina času, jak je vidět prostřednictvím JNI, obecné typy Java neexistují. Existují nějaké "vrásky", ale tyto vrásky jsou v tom, jak Java komunikuje s obecnými typy, ne s tím, jak JNI hledá a vyvolává obecné členy.
Neexistuje žádný rozdíl mezi obecným typem nebo členem a ne generickým typem nebo členem při interakci prostřednictvím JNI. Například obecný typ java.lang.Class<T> je také "nezpracovaný" obecný typ java.lang.Class
, oba mají stejný zjednodušený odkaz na typ, "java/lang/Class"
.
Podpora nativního rozhraní Java
Android.Runtime.JNIEnv je spravovaný obálka pro rozhraní JNI (Jave Native Interface). Funkce JNI jsou deklarovány ve specifikaci nativního rozhraní Java, ačkoli metody byly změněny tak, aby se odebral explicitní JNIEnv*
parametr a IntPtr
používá se místo jobject
, , jclass
, jmethodID
atd. Představte si například funkci JNI NewObject:
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
Zobrazí se jako metoda JNIEnv.NewObject :
public static IntPtr NewObject(IntPtr clazz, IntPtr jmethod, params JValue[] parms);
Překlad mezi těmito dvěma voláními je přiměřeně jednoduchý. V jazyce C byste měli:
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;
}
Ekvivalent jazyka C# by byl:
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;
}
Jakmile máte instanci objektu Java uloženou v IntPtr, pravděpodobně s ní budete chtít něco udělat. K tomu můžete použít metody JNIEnv, jako je JNIEnv.CallVoidMethod(), ale pokud už existuje analogový obálka jazyka C#, budete chtít vytvořit obálku přes odkaz JNI. Můžete to provést prostřednictvím metody rozšíření 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>();
Můžete také použít metodu 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);
Kromě toho byly všechny funkce JNI upraveny odebráním parametru JNIEnv*
v každé funkci JNI.
Shrnutí
Práce přímo s JNI je hrozné zkušenosti, které by se měly vyhnout za všechny náklady. Bohužel, není to vždy vyhnout; Doufáme, že tato příručka vám poskytne pomoc, když dosáhnete nevázaných případů Javy s Mono pro Android.