Udostępnij za pośrednictwem


Praca z interfejsami JNI i Xamarin.Android

Platforma Xamarin.Android zezwala na pisanie aplikacji systemu Android za pomocą języka C# zamiast języka Java. Kilka zestawów jest dostarczanych z platformą Xamarin.Android, które udostępniają powiązania dla bibliotek Języka Java, w tym Mono.Android.dll i Mono.Android.GoogleMaps.dll. Jednak powiązania nie są udostępniane dla każdej możliwej biblioteki Języka Java, a podane powiązania mogą nie wiązać każdego typu i elementu członkowskiego języka Java. Aby użyć niezwiązanych typów i elementów członkowskich języka Java, można użyć interfejsu natywnego Języka Java (JNI). W tym artykule pokazano, jak używać interfejsu JNI do interakcji z typami i elementami członkowskimi języka Java z aplikacji platformy Xamarin.Android.

Omówienie

Nie zawsze jest konieczne utworzenie zarządzanej otoki wywołującej (MCW) w celu wywołania kodu Java. W wielu przypadkach "wbudowany" interfejs JNI jest całkowicie akceptowalny i przydatny w przypadku jednorazowego użycia niezwiązanych elementów członkowskich języka Java. Często łatwiej jest używać interfejsu JNI do wywoływania pojedynczej metody w klasie Java niż generowania całego powiązania .jar.

Rozszerzenie Xamarin.Android udostępnia Mono.Android.dll zestaw, który zapewnia powiązanie dla biblioteki systemu Android android.jar . Typy i elementy członkowskie, które nie istnieją, Mono.Android.dll i typy, w których nie występują, android.jar mogą być używane przez ręczne powiązanie ich. Aby powiązać typy i elementy członkowskie języka Java, należy użyć interfejsu natywnego Języka Java (JNI) do typów odnośników, pól odczytu i zapisu oraz wywoływania metod.

Interfejs API JNI na platformie Xamarin.Android jest koncepcyjnie bardzo podobny do interfejsu System.Reflection API na platformie .NET: umożliwia wyszukiwanie typów i elementów członkowskich według nazw, odczytywania i zapisywania wartości pól, wywoływania metod i nie tylko. Za pomocą interfejsu JNI i atrybutu niestandardowego Android.Runtime.RegisterAttribute można zadeklarować metody wirtualne, które można powiązać z obsługą zastępowania. Interfejsy można powiązać, aby można je było zaimplementować w języku C#.

W tym dokumencie wyjaśniono:

  • Jak interfejs JNI odnosi się do typów.
  • Jak wyszukiwać, odczytywać i zapisywać pola.
  • Jak wyszukiwać i wywoływać metody.
  • Jak uwidocznić metody wirtualne, aby umożliwić zastępowanie kodu zarządzanego.
  • Jak uwidaczniać interfejsy.

Wymagania

Interfejs JNI, udostępniany za pośrednictwem przestrzeni nazw Android.Runtime.JNIEnv, jest dostępny w każdej wersji platformy Xamarin.Android. Aby powiązać typy i interfejsy Języka Java, należy użyć platformy Xamarin.Android 4.0 lub nowszej.

Zarządzane otoki z możliwością wywołania

Zarządzana otoka wywoływana (MCW) to powiązanie klasy java lub interfejsu, które opakowuje wszystkie maszyny JNI, aby kod C# klienta nie musiał martwić się o podstawową złożoność interfejsu JNI. Większość z tych elementów Mono.Android.dll składa się z zarządzanych otoek z możliwością wywoływania.

Zarządzane otoki z możliwością wywołania służą dwóm celom:

  1. Hermetyzowanie interfejsu JNI używa, aby kod klienta nie musiał wiedzieć o podstawowej złożoności.
  2. Umożliwianie podklasowych typów języka Java i implementowanie interfejsów Języka Java.

Pierwszym celem jest wyłącznie wygoda i hermetyzacja złożoności, dzięki czemu konsumenci mają prosty, zarządzany zestaw klas do użycia. Wymaga to użycia różnych elementów członkowskich JNIEnv zgodnie z opisem w dalszej części tego artykułu. Należy pamiętać, że zarządzane otoki z możliwością wywołania nie są ściśle niezbędne — użycie "wbudowanego" interfejsu JNI jest całkowicie akceptowalne i jest przydatne w przypadku jednorazowego użycia niezwiązanych elementów członkowskich języka Java. Implementacja podklasy i interfejsu wymaga użycia zarządzanych otoek wywoływanych.

Wywoływane otoki systemu Android

Wywoływane otoki systemu Android (ACW) są wymagane za każdym razem, gdy środowisko uruchomieniowe systemu Android (ART) musi wywołać kod zarządzany; te otoki są wymagane, ponieważ nie ma możliwości rejestrowania klas w środowisku uruchomieniowym ART. (W szczególności, Funkcja DefineClass JNI nie jest obsługiwana przez środowisko uruchomieniowe systemu Android. W ten sposób wywoływane otoki systemu Android stanowią brak obsługi rejestracji typu środowiska uruchomieniowego).

Za każdym razem, gdy kod systemu Android musi wykonać metodę wirtualną lub interfejs, która jest zastępowana lub implementowana w kodzie zarządzanym, platforma Xamarin.Android musi podać serwer proxy języka Java, aby ta metoda została wysłana do odpowiedniego typu zarządzanego. Te typy serwerów proxy Języka Java to kod Java, który ma "tę samą" klasę bazową i listę interfejsów Java co typ zarządzany, implementując te same konstruktory i deklarując wszelkie zastąpione metody klasy bazowej i interfejsu.

Wywoływane otoki systemu Android są generowane przez program monodroid.exe podczas procesu kompilacji i są generowane dla wszystkich typów, które (bezpośrednio lub pośrednio) dziedziczą obiekt Java.Lang.Object.

Implementowanie interfejsów

Czasami może być konieczne zaimplementowanie interfejsu systemu Android (np . Android.Content.IComponentCallbacks).

Wszystkie klasy i interfejsy systemu Android rozszerzają interfejs Android.Runtime.IJavaObject , dlatego wszystkie typy systemu Android muszą implementować element IJavaObject. Platforma Xamarin.Android korzysta z tego faktu — używa IJavaObject go do zapewnienia systemu Android z serwerem proxy Języka Java (zawijanym przez system Android) dla danego typu zarządzanego. Ponieważ monodroid.exe szuka Java.Lang.Object tylko podklas (które muszą implementować IJavaObject), podklasy zapewniają nam sposób implementowania Java.Lang.Object interfejsów w kodzie zarządzanym. Na przykład:

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

Szczegóły implementacji

Pozostała część tego artykułu zawiera szczegóły implementacji, które mogą ulec zmianie bez powiadomienia (i jest tu prezentowane tylko dlatego, że deweloperzy mogą być ciekawi, co dzieje się pod maską).

Na przykład biorąc pod uwagę następujące źródło języka 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 wygeneruje następującą otokę z możliwością wywołania systemu Android:

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

Zwróć uwagę, że klasa bazowa jest zachowywana, a deklaracje metody natywnej są udostępniane dla każdej metody, która jest zastępowana w kodzie zarządzanym.

ExportAttribute i ExportFieldAttribute

Zazwyczaj platforma Xamarin.Android automatycznie generuje kod Java, który składa się z acW; ta generacja jest oparta na nazwach klas i metod, gdy klasa pochodzi z klasy Języka Java i zastępuje istniejące metody języka Java. Jednak w niektórych scenariuszach generowanie kodu nie jest odpowiednie, jak opisano poniżej:

  • System Android obsługuje nazwy akcji w atrybutach XML układu, na przykład atrybut android:onClick XML. Gdy zostanie określony, zawyżone wystąpienie widoku próbuje wyszukać metodę Java.

  • Interfejs java.io.Serializable wymaga readObject metod i writeObject . Ponieważ nie są one członkami tego interfejsu, nasza odpowiednia implementacja zarządzana nie uwidacznia tych metod w kodzie Java.

  • Interfejs android.os.Parcelable oczekuje, że klasa implementacji musi mieć pole CREATOR statyczne typu Parcelable.Creator. Wygenerowany kod Java wymaga określonego pola jawnego. W naszym standardowym scenariuszu nie ma możliwości wyprowadzenia pola w kodzie Java z kodu zarządzanego.

Ponieważ generowanie kodu nie zapewnia rozwiązania do generowania dowolnych metod Języka Java z dowolnymi nazwami, począwszy od platformy Xamarin.Android 4.2, atrybuty ExportAttribute i ExportFieldAttribute zostały wprowadzone w celu zaoferowania rozwiązania dla powyższych scenariuszy. Oba atrybuty znajdują się w Java.Interop przestrzeni nazw:

  • ExportAttribute — określa nazwę metody i jej oczekiwane typy wyjątków (aby nadać jawne "throws" w języku Java). Gdy jest ona używana w metodzie, metoda będzie "eksportować" metodę Java, która generuje kod wysyłania do odpowiedniego wywołania JNI do metody zarządzanej. Można go używać w systemach android:onClick i java.io.Serializable.

  • ExportFieldAttribute — określa nazwę pola. Znajduje się on w metodzie, która działa jako inicjator pól. Może być używany z android.os.Parcelableprogramem .

Rozwiązywanie problemów z atrybutami ExportAttribute i ExportFieldAttribute

  • Tworzenie pakietów kończy się niepowodzeniem z powodu braku Mono.Android.Export.dll — jeśli użyto ExportAttribute lub ExportFieldAttribute w niektórych metodach w kodzie lub bibliotekach zależnych, musisz dodać Mono.Android.Export.dll. Ten zestaw jest izolowany do obsługi kodu wywołania zwrotnego z języka Java. Jest on oddzielony od Mono.Android.dll , ponieważ dodaje dodatkowy rozmiar do aplikacji.

  • W kompilacji wydania występuje w przypadku metod eksportu — w kompilacji MissingMethodException MissingMethodException wydania występuje w przypadku metod eksportu. (Ten problem został rozwiązany w najnowszej wersji platformy Xamarin.Android).

ExportParameterAttribute

ExportAttribute i ExportFieldAttribute udostępniają funkcje, których może używać kod czasu wykonywania języka Java. Ten kod w czasie wykonywania uzyskuje dostęp do kodu zarządzanego za pomocą wygenerowanych metod JNI opartych na tych atrybutach. W związku z tym nie ma istniejącej metody Java powiązanej przez metodę zarządzaną; w związku z tym metoda Języka Java jest generowana na podstawie sygnatury metody zarządzanej.

Jednak ten przypadek nie jest w pełni determinantem. W szczególności dotyczy to niektórych zaawansowanych mapowań między typami zarządzanymi i typami języka Java, takimi jak:

  • InputStream
  • OutputStream
  • XmlPullParser
  • XmlResourceParser

Gdy typy, takie jak te, są potrzebne do wyeksportowanych metod, ExportParameterAttribute należy użyć go do jawnego nadania odpowiedniego parametru lub zwracanej wartości typu.

Atrybut adnotacji

W programie Xamarin.Android 4.2 przekonwertowaliśmy IAnnotation typy implementacji na atrybuty (System.Attribute) i dodaliśmy obsługę generowania adnotacji w otoce języka Java.

Oznacza to następujące zmiany kierunkowe:

  • Generator powiązań generuje Java.Lang.DeprecatedAttribute wartość java.Lang.Deprecated (chociaż powinien być [Obsolete] w kodzie zarządzanym).

  • Nie oznacza to, że istniejąca Java.Lang.Deprecated klasa zniknie. Te obiekty oparte na języku Java mogą być nadal używane jako zwykłe obiekty Java (jeśli takie użycie istnieje). Będą i Deprecated DeprecatedAttribute klasy.

  • Klasa Java.Lang.DeprecatedAttribute jest oznaczona jako [Annotation] . Jeśli istnieje atrybut niestandardowy dziedziczony z tego atrybutu, zadanie msbuild wygeneruje adnotację Języka Java dla tego [Annotation] atrybutu niestandardowego (@Deprecated) w otoce wywoływanej systemu Android (ACW).

  • Adnotacje można wygenerować na klasy, metody i wyeksportowane pola (czyli metodę w kodzie zarządzanym).

Jeśli klasa zawierająca (sama klasa z adnotacjami lub klasa zawierająca adnotacje składowe) nie jest zarejestrowana, całe źródło klasy Java nie jest w ogóle generowane, w tym adnotacje. W przypadku metod można określić metodę ExportAttribute , aby jawnie wygenerować metodę i dodać adnotację. Ponadto nie jest to funkcja "generowania" definicji klasy adnotacji w języku Java. Innymi słowy, jeśli zdefiniujesz niestandardowy atrybut zarządzany dla określonej adnotacji, musisz dodać kolejną bibliotekę .jar zawierającą odpowiednią klasę adnotacji Języka Java. Dodanie pliku źródłowego Java definiującego typ adnotacji nie jest wystarczające. Kompilator języka Java nie działa w taki sam sposób, jak apt.

Ponadto obowiązują następujące ograniczenia:

  • Ten proces konwersji nie uwzględnia @Target adnotacji dla typu adnotacji do tej pory.

  • Atrybuty na właściwość nie działają. Zamiast tego użyj atrybutów metody getter lub setter właściwości.

Powiązanie klasy

Powiązanie klasy oznacza napisanie zarządzanej otoki wywoływanej w celu uproszczenia wywołania bazowego typu Java.

Powiązanie metod wirtualnych i abstrakcyjnych w celu zezwolenia na zastępowanie z języka C# wymaga platformy Xamarin.Android 4.0. Jednak dowolna wersja platformy Xamarin.Android może wiązać metody inne niż wirtualne, metody statyczne lub metody wirtualne bez obsługi przesłonięć.

Powiązanie zwykle zawiera następujące elementy:

Deklarowanie uchwytu typu

Metody wyszukiwania pól i metod wymagają odwołania do obiektu odwołującego się do ich typu deklarowanego. Zgodnie z konwencją class_ref odbywa się to w terenie:

static IntPtr class_ref = JNIEnv.FindClass(CLASS);

Aby uzyskać szczegółowe informacje na temat tokenuCLASS, zobacz sekcję Odwołania do typów JNI.

Pola powiązania

Pola języka Java są widoczne jako właściwości języka C#, na przykład pole Języka Java java.lang.System.in jest powiązane jako Java.Lang.JavaSystem.In właściwości języka C#. Ponadto, ponieważ interfejs JNI rozróżnia pola statyczne i pola wystąpienia, podczas implementowania właściwości należy używać różnych metod.

Powiązanie pola obejmuje trzy zestawy metod:

  1. Metoda get field id . Metoda get field id jest odpowiedzialna za zwrócenie uchwytu pola, którego będą używać metody get field value i set field value . Uzyskanie identyfikatora pola wymaga znajomości typu deklarowania, nazwy pola i podpisu typu JNI pola.

  2. Metody pobierania wartości pola. Te metody wymagają uchwytu pola i są odpowiedzialne za odczytywanie wartości pola z języka Java. Metoda do użycia zależy od typu pola.

  3. Metody ustawiania wartości pola. Te metody wymagają uchwytu pola i są odpowiedzialne za zapisywanie wartości pola w języku Java. Metoda do użycia zależy od typu pola.

Pola statyczne używają metod JNIEnv.GetStaticFieldID, JNIEnv.GetStatic*Fieldi JNIEnv.SetStaticField.

Pola wystąpienia używają metod JNIEnv.GetFieldID, JNIEnv.Get*Fieldi JNIEnv.SetField .

Na przykład właściwość JavaSystem.In statyczna można zaimplementować jako:

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

Uwaga: używamy metody InputStreamInvoker.FromJniHandle w celu przekonwertowania odwołania JNI na System.IO.Stream wystąpienie i używamy polecenia JniHandleOwnership.TransferLocalRef JNIEnv.GetStaticObjectField zwraca odwołanie lokalne.

Wiele typów Android.Runtime ma FromJniHandle metody, które przekonwertują odwołanie JNI na żądany typ.

Powiązanie metody

Metody języka Java są udostępniane jako metody języka C# i jako właściwości języka C#. Na przykład metoda Java java.lang.Runtime.runFinalizersOnExit jest powiązana z właściwością Java.Lang.Runtime.RunFinalizersOnExit , a metoda java.lang.Object.getClass jest powiązana z właściwością Java.Lang.Object.Class .

Wywołanie metody jest procesem dwuetapowym:

  1. Identyfikator metody get dla metody do wywołania. Metoda get method id jest odpowiedzialna za zwrócenie uchwytu metody, którego będą używać metody wywołania. Uzyskanie identyfikatora metody wymaga znajomości typu deklarowanego, nazwy metody i podpisu typu JNI metody.

  2. Wywołaj metodę .

Podobnie jak w przypadku pól metody używane do pobrania identyfikatora metody i wywołania metody różnią się między metodami statycznymi i metodami wystąpień.

Metody statyczne używają metody JNIEnv.GetStaticMethodID(), aby wyszukać identyfikator metody i użyć JNIEnv.CallStatic*Method rodziny metod do wywołania.

Metody wystąpień używają identyfikatora metody JNIEnv.GetMethodID, aby wyszukać identyfikator metody i użyć JNIEnv.CallNonvirtual*Method JNIEnv.Call*Method rodzin metod do wywołania.

Powiązanie metody jest potencjalnie bardziej niż wywołanie metody. Powiązanie metody obejmuje również umożliwienie zastąpienia metody (dla metod abstrakcyjnych i niefinałowych) lub zaimplementowanych (dla metod interfejsu). Sekcja Obsługa dziedziczenia, interfejsy obejmuje złożoność obsługi metod wirtualnych i metod interfejsu.

Metody statyczne

Powiązanie metody statycznej obejmuje użycie metody JNIEnv.GetStaticMethodID w celu uzyskania uchwytu metody, a następnie użycie odpowiedniej JNIEnv.CallStatic*Method metody w zależności od typu zwracanego metody. Poniżej przedstawiono przykład powiązania dla metody 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);
}

Należy pamiętać, że przechowujemy uchwyt metody w polu statycznym . id_getRuntime Jest to optymalizacja wydajności, dzięki czemu obsługa metody nie musi być sprawdzana przy każdym wywołaniu. W ten sposób nie jest konieczne buforowanie uchwytu metody. Po uzyskaniu uchwytu metody metoda JNIEnv.CallStaticObjectMethod jest używana do wywoływania metody. JNIEnv.CallStaticObjectMethod zwraca element IntPtr zawierający uchwyt zwróconego wystąpienia Java. Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) służy do konwertowania uchwytu Java na silnie typizowane wystąpienie obiektu.

Powiązanie metody wystąpienia innego niż wirtualne

final Powiązanie metody wystąpienia lub metody wystąpienia, która nie wymaga zastąpienia, obejmuje użycie JNIEnv.GetMethodID metody w celu uzyskania uchwytu metody, a następnie użycie odpowiedniej JNIEnv.Call*Method metody w zależności od typu zwracanego metody. Poniżej przedstawiono przykład powiązania dla Object.Class właściwości:

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

Należy pamiętać, że przechowujemy uchwyt metody w polu statycznym . id_getClass Jest to optymalizacja wydajności, dzięki czemu obsługa metody nie musi być sprawdzana przy każdym wywołaniu. W ten sposób nie jest konieczne buforowanie uchwytu metody. Po uzyskaniu uchwytu metody metoda JNIEnv.CallStaticObjectMethod jest używana do wywoływania metody. JNIEnv.CallStaticObjectMethod zwraca element IntPtr zawierający uchwyt zwróconego wystąpienia Java. Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) służy do konwertowania uchwytu Java na silnie typizowane wystąpienie obiektu.

Konstruktory powiązań

Konstruktory to metody języka Java o nazwie "<init>". Podobnie jak w przypadku metod wystąpień języka Java, JNIEnv.GetMethodID służy do wyszukiwania dojścia konstruktora. W przeciwieństwie do metod języka Java metody JNIEnv.NewObject są używane do wywoływania uchwytu metody konstruktora. Zwracana wartość JNIEnv.NewObject to odwołanie lokalne 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…

Zwykle powiązanie klasy będzie podklasą Java.Lang.Object. W przypadku podklasowania Java.Lang.Objectnastępuje dodatkowa semantyka: Java.Lang.Object wystąpienie utrzymuje globalne odwołanie do wystąpienia języka Java za pośrednictwem Java.Lang.Object.Handle właściwości .

  1. Domyślny Java.Lang.Object konstruktor przydzieli wystąpienie języka Java.

  2. Jeśli typ ma RegisterAttribute wartość , i RegisterAttribute.DoNotGenerateAcw to true , wystąpienie RegisterAttribute.Name typu jest tworzone za pomocą jego konstruktora domyślnego.

  3. W przeciwnym razie wywołana otoka wywoływana systemu Android (ACW) odpowiadająca this.GetType jej wystąpienia jest tworzone za pomocą jego konstruktora domyślnego. Otoki z możliwością wywołania systemu Android są generowane podczas tworzenia pakietów dla każdej Java.Lang.Object podklasy, dla której RegisterAttribute.DoNotGenerateAcw nie ustawiono wartości true.

W przypadku typów, które nie są powiązaniami klas, jest to oczekiwana semantyka: utworzenie wystąpienia Mono.Samples.HelloWorld.HelloAndroid języka C# powinno utworzyć wystąpienie języka Java mono.samples.helloworld.HelloAndroid , które jest wygenerowaną otoką wywoływaną systemu Android.

W przypadku powiązań klas może to być poprawne zachowanie, jeśli typ języka Java zawiera konstruktor domyślny i/lub nie trzeba wywoływać żadnego innego konstruktora. W przeciwnym razie należy podać konstruktor, który wykonuje następujące akcje:

  1. Wywołanie obiektu Java.Lang.Object(IntPtr, JniHandleOwnership) zamiast konstruktora domyślnego Java.Lang.Object . Jest to konieczne, aby uniknąć tworzenia nowego wystąpienia Języka Java.

  2. Sprawdź wartość elementu Java.Lang.Object.Handle przed utworzeniem jakichkolwiek wystąpień języka Java. Właściwość Object.Handle będzie miała wartość inną niż IntPtr.Zero jeśli w kodzie java został skonstruowany element Callable Wrapper systemu Android, a powiązanie klasy jest tworzone tak, aby zawierało utworzone wystąpienie otoki wywołującej systemu Android. Na przykład gdy system Android utworzy mono.samples.helloworld.HelloAndroid wystąpienie, najpierw zostanie utworzona otoka wywołalna systemu Android, a konstruktor Języka Java HelloAndroid utworzy wystąpienie odpowiedniego Mono.Samples.HelloWorld.HelloAndroid typu z Object.Handle właściwością ustawioną na wystąpienie języka Java przed wykonaniem konstruktora.

  3. Jeśli bieżący typ środowiska uruchomieniowego nie jest taki sam jak typ deklarowania, należy utworzyć wystąpienie odpowiadającego mu otoki z możliwością wywołania systemu Android i użyć obiektu Object.SetHandle do przechowywania dojścia zwróconego przez klasę JNIEnv.CreateInstance.

  4. Jeśli bieżący typ środowiska uruchomieniowego jest taki sam jak typ deklarowania, wywołaj konstruktor Języka Java i użyj metody Object.SetHandle do przechowywania dojścia zwróconego przez JNIEnv.NewInstance .

Rozważmy na przykład konstruktor java.lang.Integer(int). Jest to powiązane 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);
}

Metody JNIEnv.CreateInstance są pomocnikami do wykonania JNIEnv.FindClasswartości , JNIEnv.GetMethodID, JNIEnv.NewObjecti JNIEnv.DeleteGlobalReference na wartości zwróconej z JNIEnv.FindClass. Zobacz następną sekcję, aby uzyskać szczegółowe informacje.

Obsługa dziedziczenia, interfejsów

Podklasowanie typu Java lub implementowanie interfejsu Java wymaga generowania opakowań z możliwością wywołania systemu Android (ACW), które są generowane dla każdej Java.Lang.Object podklasy podczas procesu pakowania. Generowanie ACW jest kontrolowane za pomocą atrybutu niestandardowego Android.Runtime.RegisterAttribute .

W przypadku typów [Register] języka C# konstruktor atrybutu niestandardowego wymaga jednego argumentu: uproszczone odwołanie typu JNI dla odpowiedniego typu Języka Java. Umożliwia to udostępnianie różnych nazw między językiem Java i językiem C#.

Przed Xamarin.Android 4.0 [Register] atrybut niestandardowy był niedostępny dla istniejących typów języka Java "alias". Dzieje się tak, ponieważ proces generowania ACW generuje acW dla każdej Java.Lang.Object napotkanej podklasy.

Środowisko Xamarin.Android 4.0 wprowadziło właściwość RegisterAttribute.DoNotGenerateAcw . Ta właściwość instruuje proces generowania ACW, aby pominąć typ adnotacji, zezwalając na deklarację nowych zarządzanych otoek z możliwością wywołania, które nie spowodują wygenerowania acW w czasie tworzenia pakietu. Umożliwia to powiązanie istniejących typów języka Java. Rozważmy na przykład następującą prostą klasę Języka Java, Adderktóra zawiera jedną metodę , addktóra dodaje do liczb całkowitych i zwraca wynik:

package mono.android.test;
public class Adder {
    public int add (int a, int b) {
        return a + b;
    }
}

Typ Adder może być powiązany 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 {
}

Adder W tym miejscu typ języka C# aliasuje Adder typ języka Java. Atrybut [Register] służy do określania nazwy mono.android.test.Adder JNI typu Java, a DoNotGenerateAcw właściwość jest używana do hamowania generowania ACW. Spowoduje to wygenerowanie acW dla ManagedAdder typu, który prawidłowo podklasuje mono.android.test.Adder typ. RegisterAttribute.DoNotGenerateAcw Jeśli właściwość nie została użyta, proces kompilacji platformy Xamarin.Android wygenerowałby nowy mono.android.test.Adder typ języka Java. Spowoduje to błędy kompilacji, ponieważ mono.android.test.Adder typ będzie występować dwa razy, w dwóch oddzielnych plikach.

Wiązanie metod wirtualnych

ManagedAdder podklasy typu Java Adder , ale nie jest szczególnie interesujące: typ języka C# Adder nie definiuje żadnych metod wirtualnych, więc ManagedAdder nie może nic zastąpić.

Metody wiązania virtual w celu zezwolenia na zastępowanie przez podklasy wymagają wykonania kilku czynności, które należy wykonać, które należą do następujących dwóch kategorii:

  1. Powiązanie metody

  2. Rejestracja metody

Powiązanie metody

Powiązanie metody wymaga dodania dwóch elementów członkowskich obsługi do definicji języka C# Adder : ThresholdType, i ThresholdClass.

Typ progu

Właściwość ThresholdType zwraca bieżący typ powiązania:

partial class Adder {
    protected override System.Type ThresholdType {
        get {
            return typeof (Adder);
        }
    }
}

ThresholdType Jest używany w powiązaniu metody, aby określić, kiedy ma wykonywać wysyłanie metody wirtualnej a niewirtualnej. Zawsze powinna zwracać System.Type wystąpienie, które odpowiada deklarującemu typowi języka C#.

ThresholdClass

Właściwość ThresholdClass zwraca odwołanie do klasy JNI dla typu powiązanego:

partial class Adder {
    protected override IntPtr ThresholdClass {
        get {
            return class_ref;
        }
    }
}

ThresholdClass Jest używany w powiązaniu metody podczas wywoływania metod innych niż wirtualne.

Implementacja powiązania

Implementacja powiązania metody jest odpowiedzialna za wywołanie metody Java w czasie wykonywania. Zawiera również deklarację atrybutu niestandardowego [Register] , która jest częścią rejestracji metody i zostanie omówiona w sekcji Rejestracja 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 zawiera identyfikator metody do wywołania metody Java. Wartość id_add jest uzyskiwana z JNIEnv.GetMethodIDklasy , która wymaga klasy deklaratywnej (class_ref), nazwy metody Java ("add") i podpisu JNI metody ("(II)I").

Po uzyskaniu identyfikatora metody jest porównywany do ThresholdType określenia, GetType czy jest wymagana wysyłka wirtualna lub inna niż wirtualna. Wysyłanie wirtualne jest wymagane w przypadku GetType dopasowania ThresholdTypeelementu , co Handle może odnosić się do podklasy przydzielonej przez język Java, która zastępuje metodę.

Gdy GetType element nie jest zgodny ThresholdTypez parametrem , Adder został sklasyfikowany podklasą (np. przez ManagedAdder), a implementacja Adder.Add zostanie wywołana tylko wtedy, gdy podklasa wywołana base.Add. Jest to przypadek wysyłania niewirtualnego, w którym się ThresholdClass znajduje. ThresholdClass określa, która klasa Java zapewni implementację metody do wywołania.

Rejestracja metody

Załóżmy, że mamy zaktualizowaną ManagedAdder definicję, która zastępuje metodę Adder.Add :

partial class ManagedAdder : Adder {
    public override int Add (int a, int b) {
        return (a*2) + (b*2);
    }
}

Pamiętaj, że Adder.Add miał [Register] atrybut niestandardowy:

[Register ("add", "(II)I", "GetAddHandler")]

Konstruktor [Register] atrybutu niestandardowego akceptuje trzy wartości:

  1. W tym przypadku nazwa metody "add" Java.

  2. W tym przypadku sygnatura typu JNI metody "(II)I" .

  3. W tym przypadku metoda GetAddHandler łącznika . Metody łącznika zostaną omówione później.

Pierwsze dwa parametry umożliwiają procesowi generowania ACW generowania deklaracji metody w celu zastąpienia metody. Wynikowy acW zawierałby niektóre z następujących kodów:

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

Należy pamiętać, że @Override metoda jest zadeklarowana, która deleguje do n_metody -prefiksed o tej samej nazwie. Dzięki temu, gdy kod Java wywołuje ManagedAdder.addmetodę , ManagedAdder.n_add zostanie wywołana, co umożliwi wykonanie zastępowania metody języka C# ManagedAdder.Add .

W związku z tym najważniejsze pytanie: jak jest ManagedAdder.n_add podłączyć się do ManagedAdder.Add?

Metody języka Java native są rejestrowane w środowisku uruchomieniowym Java (środowisko uruchomieniowe systemu Android) za pomocą funkcji JNI RegisterNatives. RegisterNatives pobiera tablicę struktur zawierających nazwę metody Java, sygnaturę typu JNI i wskaźnik funkcji w celu wywołania, który jest zgodny z konwencją wywoływania JNI. Wskaźnik funkcji musi być funkcją, która przyjmuje dwa argumenty wskaźnika, a następnie parametry metody. Metoda Języka Java ManagedAdder.n_add musi zostać zaimplementowana za pomocą funkcji, która ma następujący prototyp języka C:

int FunctionName(JNIEnv *env, jobject this, int a, int b)

Platforma Xamarin.Android nie uwidacznia RegisterNatives metody. Zamiast tego ACW i MCW razem dostarczają informacje niezbędne do wywołania RegisterNatives: ACW zawiera nazwę metody i podpis typu JNI, jedyną rzeczą, której brakuje, jest wskaźnik funkcji do podłączenia.

W tym miejscu znajduje się metoda łącznika. Trzeci [Register] parametr atrybutu niestandardowego to nazwa metody zdefiniowanej w zarejestrowanym typie lub klasie bazowej zarejestrowanego typu, która nie akceptuje parametrów i zwraca wartość System.Delegate. Zwrócony System.Delegate z kolei odnosi się do metody, która ma prawidłowy podpis funkcji JNI. Na koniec delegat zwracany przez metodę łącznika musi być rooted, aby GC nie zbierał go, ponieważ delegat jest dostarczany do języka Java.

#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 tworzy Func<IntPtr, IntPtr, int, int, int> delegata, który odwołuje się do n_Add metody, a następnie wywołuje metodę JNINativeWrapper.CreateDelegate. JNINativeWrapper.CreateDelegate opakowuje podaną metodę w bloku try/catch, tak aby wszystkie nieobsługiwane wyjątki zostały obsłużone i spowoduje podniesienie zdarzenia AndroidEvent.UnhandledExceptionRaiser . Wynikowy delegat jest przechowywany w zmiennej statycznej cb_add , dzięki czemu GC nie zwolni delegata.

n_Add Na koniec metoda jest odpowiedzialna za kierowanie parametrów JNI do odpowiednich typów zarządzanych, a następnie delegowanie wywołania metody.

Uwaga: zawsze używaj JniHandleOwnership.DoNotTransfer podczas uzyskiwania mcw przez wystąpienie języka Java. Traktowanie ich jako odwołania lokalnego (a tym samym wywołanie ) spowoduje przerwanie JNIEnv.DeleteLocalRefzarządzania —> Java —> przejścia zarządzanego stosu.

Ukończ powiązanie dodatku

Pełne powiązanie zarządzane dla mono.android.tests.Adder typu to:

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

Ograniczenia

Podczas pisania typu zgodnego z następującymi kryteriami:

  1. Podklasy Java.Lang.Object

  2. [Register] Ma atrybut niestandardowy

  3. RegisterAttribute.DoNotGenerateAcw jest true

Następnie w przypadku interakcji Z GC typ nie może zawierać żadnych pól, które mogą odwoływać się do Java.Lang.Object klasy lub Java.Lang.Object podklasy w czasie wykonywania. Na przykład pola typu System.Object i dowolny typ interfejsu są niedozwolone. Typy, które nie mogą odwoływać się do Java.Lang.Object wystąpień, są dozwolone, takie jak System.String i List<int>. To ograniczenie polega na zapobieganiu przedwczesnej kolekcji obiektów przez GC.

Jeśli typ musi zawierać pole wystąpienia, które może odwoływać się do Java.Lang.Object wystąpienia, typ pola musi mieć System.WeakReference wartość lub GCHandle.

Wiązanie metod abstrakcyjnych

Metody wiązania abstract są w dużej mierze identyczne z powiązaniami metod wirtualnych. Istnieją tylko dwie różnice:

  1. Metoda abstrakcyjna jest abstrakcyjna. Nadal zachowuje [Register] atrybut i skojarzona rejestracja metody, powiązanie metody jest właśnie przenoszone do Invoker typu.

  2. Tworzony jest typ inny niż abstract Invoker , który klasy podrzędne typu abstrakcyjnego. Typ Invoker musi zastąpić wszystkie metody abstrakcyjne zadeklarowane w klasie bazowej, a zastąpiona implementacja to implementacja powiązania metody, chociaż przypadek wysyłania niewirtualnego można zignorować.

Załóżmy na przykład, że powyższa mono.android.test.Adder.add metoda to abstract. Powiązanie języka C# zmieniłoby się tak, aby Adder.Add było abstrakcyjne, a nowy AdderInvoker typ zostałby zdefiniowany, który zaimplementował element 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 jest niezbędny tylko w przypadku uzyskiwania odwołań JNI do wystąpień utworzonych w języku Java.

Interfejsy powiązań

Interfejsy powiązań są koncepcyjnie podobne do klas powiązań zawierających metody wirtualne, ale wiele z tych specyfiki różni się subtelnymi (a nie tak subtelnymi) sposobami. Rozważ następującą deklarację interfejsu Java:

public interface Progress {
    void onAdd(int[] values, int currentIndex, int currentSum);
}

Powiązania interfejsu mają dwie części: definicję interfejsu języka C# i definicję wywołania dla interfejsu.

Definicja interfejsu

Definicja interfejsu języka C# musi spełniać następujące wymagania:

  • Definicja interfejsu [Register] musi mieć atrybut niestandardowy.

  • Definicja interfejsu musi rozszerzać wartość IJavaObject interface. Nie można tego zrobić, aby zapobiec dziedziczeniu z interfejsu Java przez acWs.

  • Każda metoda interfejsu musi zawierać [Register] atrybut określający odpowiednią nazwę metody Java, podpis JNI i metodę łącznika.

  • Metoda łącznika musi również określać typ, na który można znaleźć metodę łącznika.

Podczas tworzenia powiązań abstract i virtual metod metoda łącznika będzie przeszukiwana w hierarchii dziedziczenia typu, który jest rejestrowany. Interfejsy nie mogą mieć metod zawierających jednostki, więc nie działa to, dlatego wymaganie, aby typ został określony wskazujący, gdzie znajduje się metoda łącznika. Typ jest określony w ciągu metody łącznika, po dwukropku ':', i musi być kwalifikowaną nazwą typu zestawu typu zawierającego invoker.

Deklaracje metod interfejsu to tłumaczenie odpowiedniej metody Języka Java przy użyciu zgodnych typów. W przypadku typów wbudowanych języka Java zgodne typy są odpowiednimi typami języka C#, np. Java int to C# int. W przypadku typów referencyjnych zgodny typ jest typem, który może zapewnić dojście JNI odpowiedniego typu Java.

Elementy członkowskie interfejsu nie będą bezpośrednio wywoływane przez język Java — wywołanie zostanie wyśmiewane za pośrednictwem typu Invoker, więc dozwolona jest pewna elastyczność.

Interfejs Java Progress można zadeklarować w języku 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);
}

Zwróć uwagę, że w powyższej sekcji zamapujemy parametr Java int[] na int> javaarray<. Nie jest to konieczne: moglibyśmy powiązać go z C# int[]lub , IList<int>lub coś innego całkowicie. Niezależnie od wybranego typu, Invoker musi być w stanie przetłumaczyć go na typ języka Java int[] na potrzeby wywołania.

Definicja wywołania

Invoker Definicja typu musi dziedziczyć Java.Lang.Object, implementować odpowiedni interfejs i udostępniać wszystkie metody połączenia, do których odwołuje się definicja interfejsu. Istnieje jeszcze jedna sugestia, która różni się od powiązania klasy: class_ref identyfikatory pól i metod powinny być elementami członkowskimi wystąpienia, a nie statycznymi elementami członkowskimi.

Przyczyna preferowania elementów członkowskich wystąpień ma związek z JNIEnv.GetMethodID zachowaniem w środowisku uruchomieniowym systemu Android. (Może to być również zachowanie języka Java; nie zostało przetestowane). JNIEnv.GetMethodID Zwraca wartość null podczas wyszukiwania metody pochodzącej z zaimplementowanego interfejsu, a nie zadeklarowanego interfejsu. Rozważmy interfejs java.util.SortedMap<K, V> Java, który implementuje interfejs java.util.Map<K, V> . Mapa zapewnia wyraźną metodę, dlatego pozornie rozsądna Invoker definicja elementu SortedMap byłaby następująca:

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

Powyższa metoda zakończy się niepowodzeniem, ponieważ JNIEnv.GetMethodID zostanie zwrócona null Map.clear podczas wyszukiwania metody za pośrednictwem SortedMap wystąpienia klasy.

Istnieją dwa rozwiązania: śledzenie interfejsu, z którego pochodzi każda metoda, i mają class_ref dla każdego interfejsu, lub zachować wszystko jako składowe wystąpienia i wykonać wyszukiwanie metody dla najbardziej pochodnego typu klasy, a nie typu interfejsu. Ten ostatni jest wykonywany w Mono.Android.dll.

Definicja wywołania zawiera sześć sekcji: konstruktor, Dispose metodę ThresholdType i ThresholdClass elementy członkowskie, GetObject metodę, implementację metody interfejsu i implementację metody łącznika.

Konstruktor

Konstruktor musi wyszukać klasę środowiska uruchomieniowego wywoływanego wystąpienia i zapisać klasę środowiska uruchomieniowego w polu wystąpienia 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);
    }
}

Uwaga: Handle Właściwość musi być używana w treści konstruktora, a nie handle parametru, ponieważ w systemie Android w wersji 4.0 handle parametr może być nieprawidłowy po zakończeniu wykonywania konstruktora podstawowego.

Dispose — Metoda

Metoda Dispose musi zwolnić globalne odwołanie przydzielone w konstruktorze:

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 i ThresholdClass

Składowe ThresholdType i ThresholdClass są identyczne z elementami znajdującymi się w powiązaniu klasy:

partial class IAdderProgressInvoker {
    protected override Type ThresholdType {
        get {
            return typeof (IAdderProgressInvoker);
        }
    }
    protected override IntPtr ThresholdClass {
        get {
            return class_ref;
        }
    }
}

GetObject — Metoda

Metoda statyczna GetObject jest wymagana do obsługi rozszerzenia.JavaCast<T>():

partial class IAdderProgressInvoker {
    public static IAdderProgress GetObject (IntPtr handle, JniHandleOwnership transfer)
    {
        return new IAdderProgressInvoker (handle, transfer);
    }
}

Metody interfejsu

Każda metoda interfejsu musi mieć implementację, która wywołuje odpowiednią metodę Java za pośrednictwem interfejsu 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 łącznika

Metody łącznika i infrastruktura pomocnicza są odpowiedzialne za przeprowadzanie marshalingu parametrów JNI do odpowiednich typów języka C#. Parametr Java int[] zostanie przekazany jako JNI jintArray, który znajduje się IntPtr w języku C#. Element IntPtr musi zostać przesłonięty do obiektu JavaArray<int> w celu obsługi wywoływania interfejsu języka C#:

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

W przeciwnym razie int[] JavaList<int>można użyć polecenia JNIEnv.GetArray():

int[] _values = (int[]) JNIEnv.GetArray(values, JniHandleOwnership.DoNotTransfer, typeof (int));

Należy jednak pamiętać, że JNIEnv.GetArray kopiuje całą tablicę między maszynami wirtualnymi, więc w przypadku dużych tablic może to spowodować dużą liczbę dodanych nacisków GC.

Ukończ definicję wywołania

Pełna definicja 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
}

Odwołania do obiektów JNI

Wiele metod JNIEnv zwraca odwołania do obiektów JNI, które są podobne do GCHandles. Interfejs JNI udostępnia trzy różne typy odwołań do obiektów: odwołania lokalne, odwołania globalne i słabe odwołania globalne. Wszystkie trzy są reprezentowane jako System.IntPtr, ale (zgodnie z sekcją Typy funkcji JNI) nie wszystkie IntPtrzwrócone z JNIEnv metod są odwołaniami. Na przykład JNIEnv.GetMethodID zwraca wartość IntPtr, ale nie zwraca odwołania do obiektu, zwraca jmethodIDwartość . Aby uzyskać szczegółowe informacje, zapoznaj się z dokumentacją funkcji JNI.

Odwołania lokalne są tworzone przez większość metod tworzenia odwołań. System Android zezwala tylko na istnienie ograniczonej liczby odwołań lokalnych w danym momencie, zwykle 512. Odwołania lokalne można usunąć za pośrednictwem JNIEnv.DeleteLocalRef. W przeciwieństwie do interfejsu JNI, nie wszystkie metody referencyjne JNIEnv zwracające odwołania do obiektów zwracają odwołania lokalne; Klasa JNIEnv.FindClass zwraca odwołanie globalne . Zdecydowanie zaleca się usunięcie odwołań lokalnych tak szybko, jak to możliwe, tworząc obiekt Java.Lang.Object wokół obiektu i określając JniHandleOwnership.TransferLocalRef go do uchwytu Java.Lang.Object(IntPtr, konstruktor transferu JniHandleOwnership).

Odwołania globalne są tworzone przez JNIEnv.NewGlobalRef i JNIEnv.FindClass. Można je zniszczyć za pomocą elementu JNIEnv.DeleteGlobalRef. Emulatory mają limit 2000 wybitnych odwołań globalnych, podczas gdy urządzenia sprzętowe mają limit około 52 000 odwołań globalnych.

Słabe odwołania globalne są dostępne tylko w systemie Android w wersji 2.2 (Froyo) i nowszych wersjach. Słabe odwołania globalne można usunąć za pomocą polecenia JNIEnv.DeleteWeakGlobalRef.

Obsługa odwołań lokalnych interfejsu JNI

Metody JNIEnv.GetObjectField, JNIEnv.GetStaticObjectField, JNIEnv.CallObjectMethod, JNIEnv.CallNonvirtualObjectMethod i JNIEnv.CallStaticObjectMethod zwracają IntPtr odwołanie lokalne JNI do obiektu Java lub IntPtr.Zero jeśli język Java zwrócił null. Ze względu na ograniczoną liczbę odwołań lokalnych, które mogą być zaległe jednocześnie (512 wpisów), pożądane jest zapewnienie usunięcia odwołań w odpowiednim czasie. Istnieją trzy sposoby obsługi odwołań lokalnych: jawne usunięcie ich, utworzenie Java.Lang.Object wystąpienia do ich przechowywania i użycie do Java.Lang.Object.GetObject<T>() utworzenia zarządzanej otoki wywołującej wokół nich.

Jawne usuwanie odwołań lokalnych

Element JNIEnv.DeleteLocalRef służy do usuwania odwołań lokalnych. Po usunięciu odwołania lokalnego nie można go już używać, dlatego należy zadbać o to, aby upewnić się, że JNIEnv.DeleteLocalRef jest to ostatnia czynność wykonana z odwołaniem lokalnym.

IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
try {
    // Do something with `lref`
}
finally {
    JNIEnv.DeleteLocalRef (lref);
}

Zawijanie za pomocą obiektu Java.Lang.Object

Java.Lang.Objectudostępnia konstruktor Java.Lang.Object(IntPtr, JniHandleOwnership transfer), który może służyć do zawijania odwołania JNI. Parametr JniHandleOwnership określa sposób traktowania parametru IntPtr :

  • JniHandleOwnership.DoNotTransfer — utworzone Java.Lang.Object wystąpienie utworzy nowe odwołanie globalne z parametru handle i handle pozostaje niezmienione. Obiekt wywołujący jest odpowiedzialny za zwolnienie handle , w razie potrzeby.

  • JniHandleOwnership.TransferLocalRef — utworzone Java.Lang.Object wystąpienie utworzy nowe odwołanie globalne z parametru i handle zostanie usunięte z parametru handle JNIEnv.DeleteLocalRef. Obiekt wywołujący nie może zwolnić handle elementu i nie może być używany handle po zakończeniu wykonywania konstruktora.

  • JniHandleOwnership.TransferGlobalRef — utworzone Java.Lang.Object wystąpienie przejmie własność parametru handle . Obiekt wywołujący nie może zwolnić handle elementu .

Ponieważ metody wywołania metody JNI zwracają lokalne odwołania, JniHandleOwnership.TransferLocalRef zwykle są używane:

IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef);

Utworzone globalne odwołanie nie zostanie zwolnione do momentu Java.Lang.Object usunięcia pamięci wystąpienia. Jeśli jest to możliwe, usunięcie wystąpienia spowoduje zwolnienie globalnego odwołania, przyspieszając odzyskiwanie pamięci:

IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
using (var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef)) {
    // use value ...
}

Korzystanie z języka Java.Lang.Object.GetObject<T>()

Java.Lang.Object Udostępnia metodę java.Lang.Object.GetObject<T>(IntPtr handle, JniHandleOwnership transfer), która może służyć do tworzenia zarządzanej otoki wywoływanej określonego typu.

Typ T musi spełniać następujące wymagania:

  1. T musi być typem odwołania.

  2. T musi implementować IJavaObject interfejs.

  3. Jeśli T nie jest abstrakcyjną klasą lub interfejsem, T należy podać konstruktor z typami parametrów (IntPtr, JniHandleOwnership) .

  4. Jeśli T jest klasą abstrakcyjną lub interfejsem, musi istnieć element wywołujący dostępny dla elementu .T Wywołanie jest typem nie abstrakcyjnym, który dziedziczy lub implementuje T T element , i ma taką samą nazwę jak T w przypadku sufiksu Invoker. Na przykład jeśli T jest interfejsem Java.Lang.IRunnable , typ Java.Lang.IRunnableInvoker musi istnieć i musi zawierać wymagany (IntPtr, JniHandleOwnership) konstruktor.

Ponieważ metody wywołania metody JNI zwracają lokalne odwołania, JniHandleOwnership.TransferLocalRef zwykle są używane:

IntPtr lrefString = JNIEnv.CallObjectMethod(instance, methodID);
Java.Lang.String value = Java.Lang.Object.GetObject<Java.Lang.String>( lrefString, JniHandleOwnership.TransferLocalRef);

Szukanie typów języka Java

Aby wyszukać pole lub metodę w usłudze JNI, należy najpierw wyszukać typ deklaratora dla pola lub metody. Metoda Android.Runtime.JNIEnv.FindClass(string)) służy do wyszukiwania typów języka Java. Parametr ciągu jest uproszczonym odwołaniem do typu lub pełnym odwołaniem typu dla typu Java. Szczegółowe informacje na temat uproszczonych i pełnych odwołań do typów można znaleźć w sekcji Odwołania do typów JNI.

Uwaga: w przeciwieństwie do każdej innej JNIEnv metody, która zwraca wystąpienia obiektów, FindClass zwraca odwołanie globalne, a nie odwołanie lokalne.

Pola wystąpienia

Pola są manipulowane za pomocą identyfikatorów pól. Identyfikatory pól są uzyskiwane za pośrednictwem identyfikatorów JNIEnv.GetFieldID, która wymaga klasy zdefiniowanej w polu, nazwy pola i podpisu typu JNI pola.

Identyfikatory pól nie muszą być zwalniane i są prawidłowe, o ile odpowiedni typ języka Java jest ładowany. (System Android nie obsługuje obecnie zwalniania klas).

Istnieją dwa zestawy metod manipulowania polami wystąpienia: jeden do odczytywania pól wystąpienia i jeden do zapisywania pól wystąpienia. Wszystkie zestawy metod wymagają identyfikatora pola do odczytu lub zapisu wartości pola.

Odczytywanie wartości pól wystąpienia

Zestaw metod odczytywania wartości pól wystąpienia jest zgodny ze wzorcem nazewnictwa:

* JNIEnv.Get*Field(IntPtr instance, IntPtr fieldID);

gdzie * jest typem pola:

Pisanie wartości pól wystąpienia

Zestaw metod pisania wartości pól wystąpienia jest zgodny ze wzorcem nazewnictwa:

JNIEnv.SetField(IntPtr instance, IntPtr fieldID, Type value);

gdzie Typ jest typem pola:

  • JNIEnv.SetField) — zapisz wartość dowolnego pola, które nie jest typem wbudowanym, takim jak java.lang.Object , tablice i typy interfejsów. Wartość IntPtr może być lokalnym odwołaniem JNI, odwołaniem globalnym JNI, słabym odwołaniem globalnym JNI lub IntPtr.Zero (dla elementu null ).

  • JNIEnv.SetField) — zapis wartości bool pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości sbyte pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości char pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości short pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości int pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości long pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości float pól wystąpienia.

  • JNIEnv.SetField) — zapis wartości double pól wystąpienia.

Pola statyczne

Pola statyczne są manipulowane za pomocą identyfikatorów pól. Identyfikatory pól są uzyskiwane za pośrednictwem identyfikatorów JNIEnv.GetStaticFieldID, która wymaga klasy zdefiniowanej w polu, nazwy pola i podpisu typu JNI pola.

Identyfikatory pól nie muszą być zwalniane i są prawidłowe, o ile odpowiedni typ języka Java jest ładowany. (System Android nie obsługuje obecnie zwalniania klas).

Istnieją dwa zestawy metod manipulowania polami statycznych: jeden do odczytywania pól wystąpienia i jeden do pisania pól wystąpienia. Wszystkie zestawy metod wymagają identyfikatora pola do odczytu lub zapisu wartości pola.

Odczytywanie wartości pól statycznych

Zestaw metod odczytywania wartości pól statycznych jest zgodny ze wzorcem nazewnictwa:

* JNIEnv.GetStatic*Field(IntPtr class, IntPtr fieldID);

gdzie * jest typem pola:

Pisanie wartości pól statycznych

Zestaw metod zapisywania wartości pól statycznych jest zgodny ze wzorcem nazewnictwa:

JNIEnv.SetStaticField(IntPtr class, IntPtr fieldID, Type value);

gdzie Typ jest typem pola:

Metody wystąpienia

Metody wystąpień są wywoływane za pomocą identyfikatorów metod. Identyfikatory metod są uzyskiwane za pośrednictwem identyfikatorów JNIEnv.GetMethodID, co wymaga typu zdefiniowanego w metodzie, nazwy metody i sygnatury typu JNI metody.

Identyfikatory metod nie muszą być zwalniane i są prawidłowe, o ile odpowiedni typ języka Java jest ładowany. (System Android nie obsługuje obecnie zwalniania klas).

Istnieją dwa zestawy metod wywoływania metod: jeden do wywoływania metod wirtualnych, a drugi do wywoływania metod niewirtualnie. Oba zestawy metod wymagają identyfikatora metody do wywołania metody, a wywołanie niewirtualne wymaga również określenia, która implementacja klasy ma być wywoływana.

Metody interfejsu można wyszukać tylko w obrębie typu deklaratywnego; nie można wyszukać metod pochodzących z interfejsów rozszerzonych/dziedziczynych. Aby uzyskać więcej informacji, zobacz sekcję Późniejsze powiązania interfejsów /Implementacja wywołania.

Można wyszukać dowolną metodę zadeklarowaną w klasie lub dowolnej klasie bazowej lub zaimplementowanym interfejsie.

Wywołanie metody wirtualnej

Zestaw metod wywoływania metod jest praktycznie zgodny ze wzorcem nazewnictwa:

* JNIEnv.Call*Method( IntPtr instance, IntPtr methodID, params JValue[] args );

gdzie * jest zwracany typ metody.

Wywołanie metody niewirtualnej

Zestaw metod wywoływania metod, które nie są praktycznie zgodne ze wzorcem nazewnictwa:

* JNIEnv.CallNonvirtual*Method( IntPtr instance, IntPtr class, IntPtr methodID, params JValue[] args );

gdzie * jest zwracany typ metody. Wywołanie metody niewirtualnej jest zwykle używane do wywoływania podstawowej metody metody wirtualnej.

Metody statyczne

Metody statyczne są wywoływane za pomocą identyfikatorów metod. Identyfikatory metod są uzyskiwane za pośrednictwem identyfikatorów JNIEnv.GetStaticMethodID, co wymaga typu zdefiniowanego w metodzie, nazwy metody i sygnatury typu JNI metody.

Identyfikatory metod nie muszą być zwalniane i są prawidłowe, o ile odpowiedni typ języka Java jest ładowany. (System Android nie obsługuje obecnie zwalniania klas).

Wywołanie metody statycznej

Zestaw metod wywoływania metod jest praktycznie zgodny ze wzorcem nazewnictwa:

* JNIEnv.CallStatic*Method( IntPtr class, IntPtr methodID, params JValue[] args );

gdzie * jest zwracany typ metody.

Podpisy typu JNI

Sygnatury typów JNI są odwołaniami do typów JNI (choć nie uproszczonymi odwołaniami do typów), z wyjątkiem metod. Za pomocą metod sygnatura typu JNI jest otwartym '('nawiasem, a następnie odwołania do typu dla wszystkich typów parametrów połączone razem (bez rozdzielania przecinków lub niczego innego), a następnie nawias ')'zamykający , a następnie odwołanie typu JNI typu zwracanej metody.

Na przykład, biorąc pod uwagę metodę Java:

long f(int n, String s, int[] array);

Podpis typu JNI będzie:

(ILjava/lang/String;[I)J

Ogólnie rzecz biorąc, zdecydowanie zaleca się użycie javap polecenia w celu określenia podpisów JNI. Na przykład podpis typu JNI metody java.lang.Thread.State.valueOf(String) to "(Ljava/lang/String;)Ljava/lang/Thread$State;", podczas gdy sygnatura typu JNI metody java.lang.Thread.State.values to "()[Ljava/lang/Thread$State;". Uważaj na końcowe średniki; są one częścią podpisu typu JNI.

Odwołania do typów JNI

Odwołania do typów JNI różnią się od odwołań typów języka Java. Nie można używać w pełni kwalifikowanych nazw typów Języka Java, takich jak java.lang.String w przypadku interfejsu JNI, należy zamiast tego używać odmian "java/lang/String" JNI lub "Ljava/lang/String;", w zależności od kontekstu. Aby uzyskać szczegółowe informacje, zobacz poniżej. Istnieją cztery typy odwołań typu JNI:

  • Wbudowane
  • Uproszczony
  • type
  • tablica

Wbudowane odwołania do typów

Wbudowane odwołania do typów są pojedynczym znakiem używanym do odwołowania się do wbudowanych typów wartości. Mapowanie przebiega w następujący sposób:

  • "B" dla sbyte .
  • "S" dla short .
  • "I" dla int .
  • "J" dla long .
  • "F" dla float .
  • "D" dla double .
  • "C" dla char .
  • "Z" dla bool .
  • "V" dla void typów zwracanych metod.

Uproszczone odwołania do typów

Uproszczone odwołania do typów mogą być używane tylko w JNIEnv.FindClass(ciąg)). Istnieją dwa sposoby uzyskania uproszczonego odwołania do typu:

  1. Z w pełni kwalifikowanej nazwy języka Java zastąp każdą '.' nazwę pakietu i przed nazwą '/' typu , a każda '.' w obrębie nazwy typu .'$'

  2. Odczytywanie danych wyjściowych polecenia 'unzip -l android.jar | grep JavaName' .

Jeden z tych dwóch spowoduje, że typ java.lang.Thread.State zostanie zamapowany na uproszczone odwołanie do java/lang/Thread$Statetypu .

Odwołania do typów

Odwołanie do typu jest wbudowanym odwołaniem do typu lub uproszczonym odwołaniem do typu z 'L' prefiksem i sufiksem ';' . W przypadku typu java.lang.String uproszczona dokumentacja typu to "java/lang/String", a odwołanie do typu to "Ljava/lang/String;".

Odwołania do typów są używane z odwołaniami typu tablicy i sygnaturami JNI.

Dodatkowym sposobem uzyskania odwołania do typu jest odczytanie danych wyjściowych polecenia 'javap -s -classpath android.jar fully.qualified.Java.Name'. W zależności od używanego typu można użyć deklaracji konstruktora lub zwracanego typu metody, aby określić nazwę JNI. Na przykład:

$ 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 jest typem wyliczenia Języka Java, więc możemy użyć sygnatury valueOf metody, aby określić, czy odwołanie do typu to Ljava/lang/Thread$State;.

Odwołania do typu tablicy

Odwołania do typu tablicy są '[' poprzedzone odwołaniem typu JNI. Nie można używać uproszczonych odwołań typów podczas określania tablic.

Na przykład int[] to , ma "[[I""[I"wartość , int[][] i java.lang.Object[] ma wartość "[Ljava/lang/Object;".

Typy ogólne języka Java i wymazywanie typów

W większości przypadków, jak widać za pośrednictwem interfejsu JNI, typy ogólne Języka Java nie istnieją. Istnieją pewne "zmarszczki", ale te zmarszczki są w jaki sposób Java współdziała z rodzajami ogólnymi, a nie ze sposobem, w jaki JNI wyszukuje i wywołuje ogólne elementy członkowskie.

Podczas interakcji za pośrednictwem interfejsu JNI nie ma różnicy między typem ogólnym lub elementem członkowskim a typem niegeneryjnym. Na przykład typ ogólny java.lang.Class<T> jest również "nieprzetworzonym" typem java.lang.Classogólnym , z których oba mają takie samo uproszczone odwołanie do typu . "java/lang/Class"

Obsługa interfejsu natywnego Języka Java

Android.Runtime.JNIEnv to zarządzana otoka interfejsu natywnego Jave (JNI). Funkcje JNI są deklarowane w specyfikacji interfejsu natywnego Języka Java, chociaż metody zostały zmienione w celu usunięcia jawnego JNIEnv* jobjectparametru i IntPtr są używane zamiast , jclass, jmethodIDitp. Rozważmy na przykład funkcję JNI NewObject:

jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);

Jest to uwidocznione jako metoda JNIEnv.NewObject :

public static IntPtr NewObject(IntPtr clazz, IntPtr jmethod, params JValue[] parms);

Tłumaczenie między dwoma wywołaniami jest dość proste. W języku C masz:

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

Odpowiednik języka C# to:

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

Po utworzeniu wystąpienia obiektu Java przechowywanego w intPtr prawdopodobnie zechcesz wykonać z nim coś. W tym celu można użyć metod JNIEnv, takich jak JNIEnv.CallVoidMethod(), ale jeśli istnieje już analogowa otoka języka C#, należy utworzyć otokę nad odwołaniem JNI. Można to zrobić za pomocą metody rozszerzenia 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>();

Możesz również użyć metody 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);

Ponadto wszystkie funkcje JNI zostały zmodyfikowane przez usunięcie parametru obecnego JNIEnv* w każdej funkcji JNI.

Podsumowanie

Bezpośrednie radzenie sobie z JNI to straszne doświadczenie, którego należy unikać za wszelką cenę. Niestety, nie zawsze jest to możliwe do uniknięcia; miejmy nadzieję, że ten przewodnik zapewni pewną pomoc w przypadku trafienia niepowiązanych przypadków Języka Java z platformą Mono dla systemu Android.