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:
- Hermetyzowanie interfejsu JNI używa, aby kod klienta nie musiał wiedzieć o podstawowej złożoności.
- 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 iwriteObject
. 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 typuParcelable.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 systemachandroid:onClick
ijava.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 zandroid.os.Parcelable
programem .
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
lubExportFieldAttribute
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ą iDeprecated
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:
Dojście JNI do typu Java jest powiązane.
Identyfikatory i właściwości pól JNI dla każdego pola powiązanego.
Identyfikatory metod JNI i metody dla każdej powiązanej metody.
Jeśli wymagana jest podklasa, typ musi mieć atrybut niestandardowy RegisterAttribute w deklaracji typu z atrybutem RegisterAttribute.DoNotGenerateAcw ustawiony na
true
wartość .
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:
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.
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.
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*Field
i JNIEnv.SetStaticField.
Pola wystąpienia używają metod JNIEnv.GetFieldID, JNIEnv.Get*Field
i 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:
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.
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.Object
nastę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 .
Domyślny
Java.Lang.Object
konstruktor przydzieli wystąpienie języka Java.Jeśli typ ma
RegisterAttribute
wartość , iRegisterAttribute.DoNotGenerateAcw
totrue
, wystąpienieRegisterAttribute.Name
typu jest tworzone za pomocą jego konstruktora domyślnego.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żdejJava.Lang.Object
podklasy, dla którejRegisterAttribute.DoNotGenerateAcw
nie ustawiono wartościtrue
.
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:
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.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 utworzymono.samples.helloworld.HelloAndroid
wystąpienie, najpierw zostanie utworzona otoka wywołalna systemu Android, a konstruktor Języka JavaHelloAndroid
utworzy wystąpienie odpowiedniegoMono.Samples.HelloWorld.HelloAndroid
typu zObject.Handle
właściwością ustawioną na wystąpienie języka Java przed wykonaniem konstruktora.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.
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 & 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.FindClass
wartości , JNIEnv.GetMethodID
, JNIEnv.NewObject
i 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, Adder
która zawiera jedną metodę , add
któ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:
Powiązanie metody
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.GetMethodID
klasy , 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 ThresholdType
elementu , co Handle
może odnosić się do podklasy przydzielonej przez język Java, która zastępuje metodę.
Gdy GetType
element nie jest zgodny ThresholdType
z 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:
W tym przypadku nazwa metody
"add"
Java.W tym przypadku sygnatura typu JNI metody
"(II)I"
.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.add
metodę , 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.DeleteLocalRef
zarzą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:
Podklasy
Java.Lang.Object
[Register]
Ma atrybut niestandardowyRegisterAttribute.DoNotGenerateAcw
jesttrue
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:
Metoda abstrakcyjna jest abstrakcyjna. Nadal zachowuje
[Register]
atrybut i skojarzona rejestracja metody, powiązanie metody jest właśnie przenoszone doInvoker
typu.Tworzony jest typ inny niż
abstract
Invoker
, który klasy podrzędne typu abstrakcyjnego. TypInvoker
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 GCHandle
s. 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 IntPtr
zwrócone z JNIEnv
metod są odwołaniami. Na przykład JNIEnv.GetMethodID zwraca wartość IntPtr
, ale nie zwraca odwołania do obiektu, zwraca jmethodID
wartość . 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.Object
udostę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 parametruhandle
ihandle
pozostaje niezmienione. Obiekt wywołujący jest odpowiedzialny za zwolnieniehandle
, w razie potrzeby.JniHandleOwnership.TransferLocalRef — utworzone
Java.Lang.Object
wystąpienie utworzy nowe odwołanie globalne z parametru ihandle
zostanie usunięte z parametruhandle
JNIEnv.DeleteLocalRef. Obiekt wywołujący nie może zwolnićhandle
elementu i nie może być używanyhandle
po zakończeniu wykonywania konstruktora.JniHandleOwnership.TransferGlobalRef — utworzone
Java.Lang.Object
wystąpienie przejmie własność parametruhandle
. 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:
T
musi być typem odwołania.T
musi implementowaćIJavaObject
interfejs.Jeśli
T
nie jest abstrakcyjną klasą lub interfejsem,T
należy podać konstruktor z typami parametrów(IntPtr, JniHandleOwnership)
.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 implementujeT
T
element , i ma taką samą nazwę jakT
w przypadku sufiksu Invoker. Na przykład jeśli T jest interfejsemJava.Lang.IRunnable
, typJava.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:
JNIEnv.GetObjectField — odczytaj wartość dowolnego pola wystąpienia, które nie jest typem wbudowanym, takim jak
java.lang.Object
, tablice i typy interfejsów. Zwracana wartość jest odwołaniem lokalnym JNI.JNIEnv.GetBooleanField — odczyt wartości
bool
pól wystąpienia.JNIEnv.GetByteField — odczyt wartości
sbyte
pól wystąpienia.JNIEnv.GetCharField — odczyt wartości
char
pól wystąpienia.JNIEnv.GetShortField — odczyt wartości
short
pól wystąpienia.JNIEnv.GetIntField — odczyt wartości
int
pól wystąpienia.JNIEnv.GetLongField — odczyt wartości
long
pól wystąpienia.JNIEnv.GetFloatField — odczyt wartości
float
pól wystąpienia.JNIEnv.GetDoubleField — odczytaj wartość
double
pól wystąpienia.
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 lubIntPtr.Zero
(dla elementunull
).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:
JNIEnv.GetStaticObjectField — odczytaj wartość dowolnego pola statycznego, które nie jest typem wbudowanym, takim jak
java.lang.Object
, tablice i typy interfejsów. Zwracana wartość jest odwołaniem lokalnym JNI.JNIEnv.GetStaticBooleanField — odczyt wartości pól statycznych
bool
.JNIEnv.GetStaticByteField — odczyt wartości pól statycznych
sbyte
.JNIEnv.GetStaticCharField — odczyt wartości pól statycznych
char
.JNIEnv.GetStaticShortField — odczyt wartości pól statycznych
short
.JNIEnv.GetStaticLongField — odczyt wartości pól statycznych
long
.JNIEnv.GetStaticFloatField — odczyt wartości pól statycznych
float
.JNIEnv.GetStaticDoubleField — odczyt wartości pól statycznych
double
.
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:
JNIEnv.SetStaticField) — zapisz wartość dowolnego pola statycznego, 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 lubIntPtr.Zero
(dla elementunull
).JNIEnv.SetStaticField) — zapis wartości pól statycznych
bool
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
sbyte
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
char
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
short
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
int
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
long
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
float
.JNIEnv.SetStaticField) — zapis wartości pól statycznych
double
.
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.
JNIEnv.CallObjectMethod — wywołaj metodę, która zwraca typ niekompilowany, taki jak
java.lang.Object
, tablice i interfejsy. Zwracana wartość jest odwołaniem lokalnym JNI.JNIEnv.CallBooleanMethod — wywołaj metodę zwracającą
bool
wartość.JNIEnv.CallByteMethod — wywołaj metodę zwracającą
sbyte
wartość.JNIEnv.CallCharMethod — wywołaj metodę zwracającą
char
wartość.JNIEnv.CallShortMethod — wywołaj metodę zwracającą
short
wartość.JNIEnv.CallLongMethod — wywołaj metodę zwracającą
long
wartość.JNIEnv.CallFloatMethod — wywołaj metodę zwracającą
float
wartość.JNIEnv.CallDoubleMethod — wywołaj metodę zwracającą
double
wartość.
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.
JNIEnv.CallNonvirtualObjectMethod — niewirtualnie wywołaj metodę, która zwraca typ niekompilowany, taki jak
java.lang.Object
, tablice i interfejsy. Zwracana wartość jest odwołaniem lokalnym JNI.JNIEnv.CallNonvirtualBooleanMethod — niewirtualnie wywołaj metodę zwracającą
bool
wartość.JNIEnv.CallNonvirtualByteMethod — niewirtualnie wywołaj metodę zwracającą
sbyte
wartość.JNIEnv.CallNonvirtualCharMethod — niewirtualnie wywołaj metodę zwracającą
char
wartość.JNIEnv.CallNonvirtualShortMethod — niewirtualnie wywołaj metodę zwracającą
short
wartość.JNIEnv.CallNonvirtualLongMethod — niewirtualnie wywołaj metodę zwracającą
long
wartość.JNIEnv.CallNonvirtualFloatMethod — niewirtualnie wywołaj metodę zwracającą
float
wartość.JNIEnv.CallNonvirtualDoubleMethod — niewirtualnie wywołaj metodę zwracającą
double
wartość.
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.
JNIEnv.CallStaticObjectMethod — wywołaj metodę statyczną, która zwraca typ niekompilowany, taki jak
java.lang.Object
, tablice i interfejsy. Zwracana wartość jest odwołaniem lokalnym JNI.JNIEnv.CallStaticBooleanMethod — wywołaj metodę statyczną zwracającą
bool
wartość.JNIEnv.CallStaticByteMethod — wywołaj metodę statyczną
sbyte
, która zwraca wartość.JNIEnv.CallStaticCharMethod — wywołaj metodę statyczną
char
, która zwraca wartość.JNIEnv.CallStaticShortMethod — wywołaj metodę statyczną
short
, która zwraca wartość.JNIEnv.CallStaticLongMethod — wywołaj metodę statyczną zwracającą
long
wartość.JNIEnv.CallStaticFloatMethod — wywołaj metodę statyczną zwracającą
float
wartość.JNIEnv.CallStaticDoubleMethod — wywołaj metodę statyczną zwracającą
double
wartość.
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"
dlasbyte
."S"
dlashort
."I"
dlaint
."J"
dlalong
."F"
dlafloat
."D"
dladouble
."C"
dlachar
."Z"
dlabool
."V"
dlavoid
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:
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 .'$'
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$State
typu .
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.Class
ogó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*
jobject
parametru i IntPtr
są używane zamiast , jclass
, jmethodID
itp. 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.