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.Google Mapy.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:

  1. Zapouzdřte JNI, aby klientský kód nemusel znát základní složitost.
  2. 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 IJavaObjectvš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 a writeObject 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 typu Parcelable.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 s android:onClick a java.io.Serializable.

  • ExportFieldAttribute – určuje název pole. Nachází se v metodě, která funguje jako inicializátor pole. To lze použít s android.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 nebo ExportFieldAttribute 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 ze java.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 budou Deprecated a DeprecatedAttribute 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:

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:

  1. 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.

  2. 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.

  3. 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*FieldJNIEnv.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:

  1. 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.

  2. 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.

  1. Výchozí Java.Lang.Object konstruktor přidělí instanci Javy.

  2. Pokud typ obsahuje a RegisterAttributeRegisterAttribute.DoNotGenerateAcw je true , pak instance RegisterAttribute.Name typu je vytvořena prostřednictvím jeho výchozí konstruktoru.

  3. 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ždou Java.Lang.Object podtřídu, pro kterou RegisterAttribute.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:

  1. 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.

  2. 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 Java HelloAndroid vytvoří instanci odpovídajícího Mono.Samples.HelloWorld.HelloAndroid typu s Object.Handle vlastností nastavenou na instanci Java před spuštěním konstruktoru.

  3. 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.

  4. 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 &amp; 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.NewObjectJNIEnv.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, Adderkterá obsahuje jednu metodu, addkterá 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 AdderAdder 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í:

  1. Vazba metody

  2. Registrace metody

Vazba metody

Vazba metody vyžaduje přidání dvou členů podpory do definice jazyka C# Adder : ThresholdTypea 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.Addpodtří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:

  1. Název metody Java, "add" v tomto případě.

  2. Podpis typu JNI metody v "(II)I" tomto případě.

  3. Metoda konektoru , GetAddHandler v tomto případě. Připojení orové metody 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.addManagedAdder.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:

  1. Podtříd Java.Lang.Object

  2. [Register] Má vlastní atribut.

  3. RegisterAttribute.DoNotGenerateAcw Je true

Pro interakci GC nesmí typ obsahovat žádná pole, která mohou odkazovat na podtřídu Java.Lang.ObjectJava.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:

  1. Abstraktní metoda je abstraktní. Stále zachovává [Register] atribut a přidruženou registraci metody, vazba metody je právě přesunuta do Invoker typu.

  2. Vytvoří se netyp abstractInvoker , který podtřídy abstraktního typu. Typ Invoker 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));
    }
}

Připojení orové metody

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é GCHandles. 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 IntPtrvrá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.Objectposkytuje 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 parametru handle a handle 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 parametru handle a handle odstraní se pomocí JNIEnv.DeleteLocalRef . Volající nesmí být volný handle a nesmí používat handle po dokončení provádění konstruktoru.

  • JniHandleOwnership.TransferGlobalRef – vytvořená Java.Lang.Object instance převezme vlastnictví parametru handle . 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:

  1. T musí být referenčním typem.

  2. T musí implementovat IJavaObject rozhraní.

  3. Pokud T není abstraktní třída nebo rozhraní, musí T poskytnout konstruktor s typy (IntPtr, JniHandleOwnership) parametrů .

  4. Pokud T je abstraktní třída nebo rozhraní, musí být k dispozici invoker pro T . Invoker je ne abstraktní typ, který dědí T nebo implementuje T , a má stejný název jako T s příponou Invoker. Pokud je například T rozhraní Java.Lang.IRunnable , typ Java.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:

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í. Hodnota IntPtr může být místní odkaz JNI, globální odkaz JNI, slabý globální odkaz JNI nebo IntPtr.Zero (pro null ).

  • 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:

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:

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.

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.

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.

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í je následující:

  • "B" pro sbyte .
  • "S" pro short .
  • "I" pro int .
  • "J" pro long .
  • "F" pro float .
  • "D" pro double .
  • "C" pro char .
  • "Z" pro bool .
  • "V" pro void 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:

  1. 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 '$' .

  2. 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$Statena 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, jmethodIDatd. 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.