Uso di JNI e Xamarin.Android
Xamarin.Android consente di scrivere app Android con C# invece di Java. Sono disponibili diversi assembly con Xamarin.Android che forniscono associazioni per le librerie Java, tra cui Mono.Android.dll e Mono.Android.GoogleMaps.dll. Tuttavia, le associazioni non vengono fornite per tutte le possibili librerie Java e le associazioni fornite potrebbero non associare tutti i tipi e i membri Java. Per usare tipi e membri Java non associati, è possibile usare Java Native Interface (JNI). Questo articolo illustra come usare JNI per interagire con tipi e membri Java dalle applicazioni Xamarin.Android.
Panoramica
Non è sempre necessario o possibile creare un Wrapper chiamabile gestito (MCW) per richiamare il codice Java. In molti casi, JNI "inline" è perfettamente accettabile e utile per l'uso occasionale di membri Java non associati. Spesso è più semplice usare JNI per richiamare un singolo metodo in una classe Java rispetto a generare un'intera associazione .jar.
Xamarin.Android fornisce l'assembly, che fornisce un'associazione Mono.Android.dll
per la libreria di android.jar
Android. I tipi e i membri non presenti all'interno di e i tipi non presenti all'interno Mono.Android.dll
android.jar
possono essere usati associandoli manualmente. Per associare tipi e membri Java, usare Java Native Interface (JNI) per cercare tipi, leggere e scrivere campi e richiamare i metodi.
L'API JNI in Xamarin.Android è concettualmente molto simile all'API System.Reflection
in .NET: consente di cercare tipi e membri in base al nome, leggere e scrivere valori di campo, richiamare metodi e altro ancora. È possibile usare JNI e l'attributo personalizzato per dichiarare metodi virtuali che possono essere associati per supportare l'override Android.Runtime.RegisterAttribute
. È possibile associare interfacce in modo che possano essere implementate in C#.
Questo documento illustra:
- In che modo JNI fa riferimento ai tipi.
- Come cercare, leggere e scrivere campi.
- Come cercare e richiamare i metodi.
- Come esporre metodi virtuali per consentire l'override dal codice gestito.
- Come esporre le interfacce.
Requisiti
JNI, come esposto tramite lo spazio dei nomi Android.Runtime.JNIEnv, è disponibile in ogni versione di Xamarin.Android. Per associare tipi e interfacce Java, è necessario usare Xamarin.Android 4.0 o versione successiva.
Wrapper chiamabili gestiti
Un wrapper chiamabile gestito (MCW) è un'associazione per una classe o un'interfaccia Java che esegue il wrapping di tutti i macchinari JNI in modo che il codice C# client non debba preoccuparsi della complessità sottostante di JNI. La maggior parte di Mono.Android.dll
è costituita da wrapper chiamabili gestiti.
I wrapper chiamabili gestiti servono due scopi:
- Incapsulare l'uso di JNI in modo che il codice client non debba conoscere la complessità sottostante.
- Rendere possibile i tipi Java di sottoclasse e implementare interfacce Java.
Il primo scopo è puramente per praticità e incapsulamento della complessità in modo che i consumer abbiano un set semplice e gestito di classi da usare. Questo richiede l'uso dei vari membri JNIEnv , come descritto più avanti in questo articolo. Tenere presente che i wrapper chiamabili gestiti non sono strettamente necessari: l'uso JNI "inline" è perfettamente accettabile ed è utile per l'uso uni-off dei membri Java non associati. L'implementazione di sottoclassi e interfacce richiede l'uso di wrapper chiamabili gestiti.
Android Callable Wrapper
I wrapper chiamabili Android (ACW) sono necessari ogni volta che il runtime Android (ART) deve richiamare il codice gestito; questi wrapper sono necessari perché non è possibile registrare le classi con ART in fase di esecuzione. (In particolare, il La funzione JNI DefineClass non è supportata dal runtime Android. I wrapper chiamabili Android costituiscono quindi la mancanza di supporto per la registrazione dei tipi di runtime.
Ogni volta che il codice Android deve eseguire un metodo virtuale o di interfaccia sottoposto a override o implementato nel codice gestito, Xamarin.Android deve fornire un proxy Java in modo che questo metodo venga inviato al tipo gestito appropriato. Questi tipi di proxy Java sono codice Java con la "stessa" classe di base e l'elenco di interfacce Java del tipo gestito, implementando gli stessi costruttori e dichiarando eventuali metodi di interfaccia e classe base sottoposti a override.
I wrapper chiamabili Android vengono generati dal programma monodroid.exe durante il processo di compilazione e vengono generati per tutti i tipi che (direttamente o indirettamente) ereditano Java.Lang.Object.
Implementazione di interfacce
In alcuni casi potrebbe essere necessario implementare un'interfaccia Android, ad esempio Android.Content.IComponentCallbacks.
Tutte le classi e le interfacce Android estendono l'interfaccia Android.Runtime.IJavaObject , pertanto tutti i tipi Android devono implementare IJavaObject
.
Xamarin.Android sfrutta questo fatto: usa IJavaObject
per fornire ad Android un proxy Java (un wrapper chiamabile Android) per il tipo gestito specificato. Poiché monodroid.exe cerca Java.Lang.Object
solo sottoclassi (che devono implementare IJavaObject
), la sottoclasse Java.Lang.Object
fornisce un modo per implementare le interfacce nel codice gestito. Ad esempio:
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...
}
}
Dettagli dell'implementazione
La parte restante di questo articolo fornisce dettagli sull'implementazione soggetti a modifiche senza preavviso (e viene presentato qui solo perché gli sviluppatori potrebbero essere curiosi di cosa sta succedendo sotto il cappuccio).
Ad esempio, data l'origine C# seguente:
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);
}
}
}
Il programma mandroid.exe genererà il wrapper chiamabile Android seguente:
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);
}
Si noti che la classe base viene mantenuta e vengono fornite dichiarazioni di metodo native per ogni metodo sottoposto a override all'interno del codice gestito.
ExportAttribute ed ExportFieldAttribute
In genere, Xamarin.Android genera automaticamente il codice Java che comprende l'AW; questa generazione si basa sui nomi di classe e metodo quando una classe deriva da una classe Java ed esegue l'override dei metodi Java esistenti. In alcuni scenari, tuttavia, la generazione del codice non è adeguata, come descritto di seguito:
Android supporta i nomi di azione negli attributi XML di layout, ad esempio l'attributo XML android:onClick . Quando viene specificato, l'istanza di View gonfiata tenta di cercare il metodo Java.
L'interfaccia java.io.Serializable richiede
readObject
metodi ewriteObject
. Poiché non sono membri di questa interfaccia, l'implementazione gestita corrispondente non espone questi metodi al codice Java.L'interfaccia android.os.Parcelable prevede che una classe di implementazione abbia un campo
CREATOR
statico di tipoParcelable.Creator
. Il codice Java generato richiede un campo esplicito. Con lo scenario standard, non esiste alcun modo per restituire il campo nel codice Java dal codice gestito.
Poiché la generazione di codice non fornisce una soluzione per generare metodi Java arbitrari con nomi arbitrari, a partire da Xamarin.Android 4.2, Sono stati introdotti ExportAttribute ed ExportFieldAttribute per offrire una soluzione agli scenari precedenti. Entrambi gli attributi si trovano nello spazio dei Java.Interop
nomi :
ExportAttribute
: specifica un nome di metodo e i relativi tipi di eccezione previsti (per fornire "throw" espliciti in Java). Quando viene usato in un metodo, il metodo "esporta" un metodo Java che genera un codice dispatch alla chiamata JNI corrispondente al metodo gestito. Può essere usato conandroid:onClick
ejava.io.Serializable
.ExportFieldAttribute
: specifica un nome di campo. Si trova in un metodo che funziona come inizializzatore di campo. Può essere usato conandroid.os.Parcelable
.
Risoluzione dei problemi relativi a ExportAttribute ed ExportFieldAttribute
La creazione di pacchetti non riesce a causa di Mono.Android.Export.dll mancanti: se sono stati usati
ExportAttribute
oExportFieldAttribute
in alcuni metodi nel codice o nelle librerie dipendenti, è necessario aggiungere Mono.Android.Export.dll. Questo assembly è isolato per supportare il codice di callback da Java. È separato da Mono.Android.dll in quanto aggiunge dimensioni aggiuntive all'applicazione.Nella build di rilascio si
MissingMethodException
verifica per i metodi di esportazione: nella build release siMissingMethodException
verifica per i metodi di esportazione. Questo problema è stato risolto nella versione più recente di Xamarin.Android.
ExportParameterAttribute
ExportAttribute
e ExportFieldAttribute
forniscono funzionalità che il codice di runtime Java può usare. Questo codice di runtime accede al codice gestito tramite i metodi JNI generati basati su tali attributi. Di conseguenza, non esiste alcun metodo Java esistente associato dal metodo gestito; di conseguenza, il metodo Java viene generato da una firma del metodo gestito.
Tuttavia, questo caso non è completamente determinante. In particolare, questo vale in alcuni mapping avanzati tra i tipi gestiti e i tipi Java, ad esempio:
- InputStream
- OutputStream
- XmlPullParser
- XmlResourceParser
Quando sono necessari tipi come questi per i metodi esportati, è necessario utilizzare per ExportParameterAttribute
assegnare in modo esplicito al parametro corrispondente o al valore restituito un tipo.
Attributo annotazione
In Xamarin.Android 4.2 i tipi di implementazione sono stati convertiti IAnnotation
in attributi (System.Attribute) e sono stati aggiunti il supporto per la generazione di annotazioni nei wrapper Java.
Ciò significa che le modifiche direzionali seguenti:
Il generatore di binding genera
Java.Lang.DeprecatedAttribute
dajava.Lang.Deprecated
(mentre deve essere[Obsolete]
nel codice gestito).Questo non significa che la classe esistente
Java.Lang.Deprecated
svanirà. Questi oggetti basati su Java possono essere comunque usati come oggetti Java normali (se tali utilizzi esistono). Ci sarannoDeprecated
classi eDeprecatedAttribute
.La
Java.Lang.DeprecatedAttribute
classe è contrassegnata come[Annotation]
. Quando è presente un attributo personalizzato ereditato da questo[Annotation]
attributo, l'attività msbuild genererà un'annotazione Java per tale attributo personalizzato (@Deprecated) in Android Callable Wrapper (ACW).Le annotazioni possono essere generate in classi, metodi ed esportati campi (ovvero un metodo nel codice gestito).
Se la classe contenitore (la classe con annotazioni o la classe che contiene i membri con annotazioni) non viene registrata, l'intera origine della classe Java non viene generata affatto, incluse le annotazioni. Per i metodi, è possibile specificare per ExportAttribute
ottenere il metodo generato e annotato in modo esplicito. Inoltre, non è una funzionalità per "generare" una definizione di classe di annotazione Java. In altre parole, se si definisce un attributo gestito personalizzato per una determinata annotazione, sarà necessario aggiungere un'altra libreria .jar contenente la classe di annotazione Java corrispondente. L'aggiunta di un file di origine Java che definisce il tipo di annotazione non è sufficiente. Il compilatore Java non funziona allo stesso modo di apt.
Si applicano anche le limitazioni seguenti:
Questo processo di conversione non considera
@Target
finora l'annotazione sul tipo di annotazione.Gli attributi in una proprietà non funzionano. Usare invece gli attributi per il getter o il setter della proprietà.
Associazione di classi
L'associazione di una classe significa scrivere un wrapper chiamabile gestito per semplificare la chiamata del tipo Java sottostante.
L'associazione di metodi virtuali e astratti per consentire l'override da C# richiede Xamarin.Android 4.0. Tuttavia, qualsiasi versione di Xamarin.Android può associare metodi non virtuali, metodi statici o metodi virtuali senza supportare gli override.
Un'associazione contiene in genere gli elementi seguenti:
Handle JNI per il tipo Java associato.
ID e proprietà dei campi JNI per ogni campo associato.
ID e metodi del metodo JNI per ogni metodo associato.
Se è necessaria una sottoclasse, il tipo deve avere un attributo personalizzato RegisterAttribute nella dichiarazione di tipo con RegisterAttribute.DoNotGenerateAcw impostato su
true
.
Dichiarazione dell'handle di tipo
I metodi di ricerca di campi e metodi richiedono un riferimento all'oggetto che fa riferimento al tipo dichiarante. Per convenzione, questo si trova in un class_ref
campo:
static IntPtr class_ref = JNIEnv.FindClass(CLASS);
Per informazioni dettagliate sul CLASS
token, vedere la sezione Riferimenti ai tipi JNI.
Campi di associazione
I campi Java vengono esposti come proprietà C#, ad esempio il campo Java java.lang.System.in è associato come proprietà C# Java.Lang.JavaSystem.In. Inoltre, poiché JNI distingue tra campi statici e campi di istanza, vengono usati metodi diversi per l'implementazione delle proprietà.
L'associazione di campi prevede tre set di metodi:
Metodo get field id . Il metodo get field id è responsabile della restituzione di un handle di campo che verrà utilizzato dai metodi get field value e set field value . Per ottenere l'ID campo è necessario conoscere il tipo dichiarante, il nome del campo e la firma del tipo JNI del campo.
Metodi get field value . Questi metodi richiedono l'handle di campo e sono responsabili della lettura del valore del campo da Java. Il metodo da utilizzare dipende dal tipo del campo.
Metodi set field value . Questi metodi richiedono l'handle di campo e sono responsabili della scrittura del valore del campo in Java. Il metodo da utilizzare dipende dal tipo del campo.
I campi statici usano i metodi JNIEnv.GetStaticFieldID, JNIEnv.GetStatic*Field
e JNIEnv.SetStaticField .
I campi dell'istanza usano i metodi JNIEnv.GetFieldID, JNIEnv.Get*Field
e JNIEnv.SetField .
Ad esempio, la proprietà JavaSystem.In
statica può essere implementata come segue:
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);
}
}
Nota: si usa InputStreamInvoker.FromJniHandle per convertire il riferimento JNI in un'istanza System.IO.Stream
e viene usato JniHandleOwnership.TransferLocalRef
perché JNIEnv.GetStaticObjectField restituisce un riferimento locale.
Molti dei tipi Android.Runtime hanno FromJniHandle
metodi che convertiranno un riferimento JNI nel tipo desiderato.
Associazione di metodi
I metodi Java vengono esposti come metodi C# e come proprietà C#. Ad esempio, il metodo Java.lang.Runtime.runFinalizersOnExit viene associato come metodo Java.Lang.Runtime.RunFinalizersOnExit e il metodo java.lang.Object.getClass viene associato come proprietà Java.Lang.Object.Class.
La chiamata al metodo è un processo in due passaggi:
ID del metodo get da richiamare. Il metodo get method id è responsabile della restituzione di un handle di metodo che verrà utilizzato dai metodi di chiamata al metodo. Per ottenere l'ID del metodo è necessario conoscere il tipo dichiarante, il nome del metodo e la firma del tipo JNI del metodo.
Richiamare il metodo.
Analogamente ai campi, i metodi da usare per ottenere l'ID del metodo e richiamare il metodo differiscono tra metodi statici e metodi di istanza.
I metodi statici usano JNIEnv.GetStaticMethodID() per cercare l'ID del metodo e usare la famiglia di metodi per la JNIEnv.CallStatic*Method
chiamata.
I metodi di istanza usano JNIEnv.GetMethodID per cercare l'ID del metodo e usare le famiglie di metodi e JNIEnv.CallNonvirtual*Method
per la JNIEnv.Call*Method
chiamata.
L'associazione di metodi è potenzialmente più di una semplice chiamata al metodo. L'associazione di metodi include anche la possibilità di eseguire l'override di un metodo (per i metodi astratti e non finali) o implementati (per i metodi di interfaccia). La sezione Supporto dell'ereditarietà, delle interfacce illustra le complessità del supporto di metodi virtuali e metodi di interfaccia.
Metodi statici
L'associazione di un metodo statico comporta l'uso JNIEnv.GetStaticMethodID
di per ottenere un handle di metodo, quindi l'uso del metodo appropriato JNIEnv.CallStatic*Method
, a seconda del tipo restituito del metodo. Di seguito è riportato un esempio di associazione per il metodo 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);
}
Si noti che l'handle del metodo viene archiviato in un campo statico, id_getRuntime
. Si tratta di un'ottimizzazione delle prestazioni, in modo che l'handle del metodo non debba essere cercato in ogni chiamata. Non è necessario memorizzare nella cache l'handle del metodo in questo modo. Dopo aver ottenuto l'handle del metodo, JNIEnv.CallStaticObjectMethod viene usato per richiamare il metodo . JNIEnv.CallStaticObjectMethod
restituisce un oggetto IntPtr
che contiene l'handle dell'istanza Java restituita.
Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) viene usato per convertire l'handle Java in un'istanza di oggetto fortemente tipizzata.
Associazione del metodo di istanza non virtuale
L'associazione di un final
metodo di istanza o di un metodo di istanza che non richiede l'override comporta l'uso JNIEnv.GetMethodID
di per ottenere un handle di metodo, quindi l'uso del metodo appropriato JNIEnv.Call*Method
, a seconda del tipo restituito del metodo. Di seguito è riportato un esempio di associazione per la Object.Class
proprietà :
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);
}
}
Si noti che l'handle del metodo viene archiviato in un campo statico, id_getClass
.
Si tratta di un'ottimizzazione delle prestazioni, in modo che l'handle del metodo non debba essere cercato in ogni chiamata. Non è necessario memorizzare nella cache l'handle del metodo in questo modo. Dopo aver ottenuto l'handle del metodo, JNIEnv.CallStaticObjectMethod viene usato per richiamare il metodo . JNIEnv.CallStaticObjectMethod
restituisce un oggetto IntPtr
che contiene l'handle dell'istanza Java restituita.
Java.Lang.Object.GetObject<T>(IntPtr, JniHandleOwnership) viene usato per convertire l'handle Java in un'istanza di oggetto fortemente tipizzata.
Costruttori di binding
I costruttori sono metodi Java con il nome "<init>"
. Analogamente ai metodi di istanza Java, JNIEnv.GetMethodID
viene usato per cercare l'handle del costruttore. A differenza dei metodi Java, i metodi JNIEnv.NewObject vengono usati per richiamare l'handle del metodo del costruttore. Il valore restituito di JNIEnv.NewObject
è un riferimento locale 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…
In genere un'associazione di classi sottoclasserà Java.Lang.Object.
Quando si sottoclassa Java.Lang.Object
, entra in gioco una semantica aggiuntiva: un'istanza Java.Lang.Object
mantiene un riferimento globale a un'istanza Java tramite la Java.Lang.Object.Handle
proprietà .
Il
Java.Lang.Object
costruttore predefinito allocherà un'istanza Java.Se il tipo ha un
RegisterAttribute
eRegisterAttribute.DoNotGenerateAcw
ètrue
, viene creata un'istanza del tipo tramite ilRegisterAttribute.Name
relativo costruttore predefinito.In caso contrario, viene creata un'istanza di Android Callable Wrapper (ACW) corrispondente
this.GetType
tramite il relativo costruttore predefinito. I wrapper chiamabili Android vengono generati durante la creazione del pacchetto per ogniJava.Lang.Object
sottoclasse per cuiRegisterAttribute.DoNotGenerateAcw
non è impostato sutrue
.
Per i tipi che non sono associazioni di classi, si tratta della semantica prevista: la creazione di un'istanza Mono.Samples.HelloWorld.HelloAndroid
C# deve creare un'istanza Java mono.samples.helloworld.HelloAndroid
che è un wrapper chiamabile Android generato.
Per le associazioni di classe, questo potrebbe essere il comportamento corretto se il tipo Java contiene un costruttore predefinito e/o nessun altro costruttore deve essere richiamato. In caso contrario, è necessario specificare un costruttore che esegue le azioni seguenti:
Richiamare Java.Lang.Object(IntPtr, JniHandleOwnership) anziché il costruttore predefinito
Java.Lang.Object
. Questa operazione è necessaria per evitare di creare una nuova istanza Java.Controllare il valore di Java.Lang.Object.Handle prima di creare istanze Java. La
Object.Handle
proprietà avrà un valore diverso daIntPtr.Zero
se un wrapper chiamabile Android è stato costruito nel codice Java e l'associazione di classe viene costruita per contenere l'istanza di Android Callable Wrapper creata. Ad esempio, quando Android crea un'istanzamono.samples.helloworld.HelloAndroid
, android Callable Wrapper verrà creato per primo e il costruttore JavaHelloAndroid
creerà un'istanza del tipo corrispondenteMono.Samples.HelloWorld.HelloAndroid
, con laObject.Handle
proprietà impostata sull'istanza Java prima dell'esecuzione del costruttore.Se il tipo di runtime corrente non corrisponde al tipo dichiarante, è necessario creare un'istanza del wrapper chiamabile Android corrispondente e usare Object.SetHandle per archiviare l'handle restituito da JNIEnv.CreateInstance.
Se il tipo di runtime corrente è uguale al tipo dichiarante, richiamare il costruttore Java e usare Object.SetHandle per archiviare l'handle restituito da
JNIEnv.NewInstance
.
Si consideri ad esempio il costruttore java.lang.Integer(int). Questo è associato come segue:
// 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);
}
I metodi JNIEnv.CreateInstance sono helper per eseguire un JNIEnv.FindClass
oggetto , JNIEnv.GetMethodID
, JNIEnv.NewObject
e JNIEnv.DeleteGlobalReference
sul valore restituito da JNIEnv.FindClass
. Per informazioni dettagliate, vedere la sezione che segue.
Supporto dell'ereditarietà, delle interfacce
La sottoclassazione di un tipo Java o l'implementazione di un'interfaccia Java richiede la generazione di ACL (Callable Wrapper ) Android generati per ogni Java.Lang.Object
sottoclasse durante il processo di creazione del pacchetto. La generazione di ACW viene controllata tramite l'attributo personalizzato Android.Runtime.RegisterAttribute .
Per i tipi C#, il [Register]
costruttore dell'attributo personalizzato richiede un argomento: il riferimento al tipo semplificato JNI per il tipo Java corrispondente. In questo modo è possibile specificare nomi diversi tra Java e C#.
Prima di Xamarin.Android 4.0, l'attributo [Register]
personalizzato non era disponibile per i tipi Java esistenti "alias". Ciò è dovuto al fatto che il processo di generazione acw genera gli ACL per ogni Java.Lang.Object
sottoclasse rilevata.
Xamarin.Android 4.0 ha introdotto la proprietà RegisterAttribute.DoNotGenerateAcw . Questa proprietà indica al processo di generazione ACW di ignorare il tipo annotato, consentendo la dichiarazione di nuovi wrapper chiamabili gestiti che non comportano la generazione di ACL al momento della creazione del pacchetto. In questo modo è possibile eseguire l'associazione di tipi Java esistenti. Si consideri, ad esempio, la classe Java semplice seguente, Adder
, che contiene un metodo, add
, che aggiunge a numeri interi e restituisce il risultato:
package mono.android.test;
public class Adder {
public int add (int a, int b) {
return a + b;
}
}
Il Adder
tipo può essere associato come segue:
[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 {
}
In questo caso, il Adder
tipo C# aliasa il Adder
tipo Java. L'attributo [Register]
viene usato per specificare il nome JNI del mono.android.test.Adder
tipo Java e la proprietà viene usata per inibire la DoNotGenerateAcw
generazione di AW. Ciò comporterà la generazione di un AW per il ManagedAdder
tipo, che sottoclassa correttamente il mono.android.test.Adder
tipo. Se la RegisterAttribute.DoNotGenerateAcw
proprietà non fosse stata usata, il processo di compilazione di Xamarin.Android avrebbe generato un nuovo mono.android.test.Adder
tipo Java. Ciò comporterebbe errori di compilazione, perché il mono.android.test.Adder
tipo sarebbe presente due volte, in due file separati.
Binding di metodi virtuali
ManagedAdder
sottoclassi del tipo Java Adder
, ma non è particolarmente interessante: il tipo C# Adder
non definisce metodi virtuali, quindi ManagedAdder
non può eseguire l'override di nulla.
I virtual
metodi di associazione per consentire l'override da sottoclassi richiedono diverse operazioni che devono essere eseguite che rientrano nelle due categorie seguenti:
Associazione di metodi
Registrazione del metodo
Associazione di metodi
Un'associazione di metodi richiede l'aggiunta di due membri di supporto alla definizione C# Adder
: ThresholdType
e ThresholdClass
.
ThresholdType
La ThresholdType
proprietà restituisce il tipo corrente dell'associazione:
partial class Adder {
protected override System.Type ThresholdType {
get {
return typeof (Adder);
}
}
}
ThresholdType
viene usato nell'associazione di metodi per determinare quando deve eseguire l'invio di metodi virtuali e non virtuali. Deve restituire sempre un'istanza System.Type
che corrisponde al tipo C# dichiarante.
ThresholdClass
La ThresholdClass
proprietà restituisce il riferimento alla classe JNI per il tipo associato:
partial class Adder {
protected override IntPtr ThresholdClass {
get {
return class_ref;
}
}
}
ThresholdClass
viene utilizzato nell'associazione di metodi quando si richiamano metodi non virtuali.
Implementazione dell'associazione
L'implementazione dell'associazione di metodi è responsabile della chiamata in fase di esecuzione del metodo Java. Contiene anche una [Register]
dichiarazione di attributo personalizzata che fa parte della registrazione del metodo e verrà illustrata nella sezione Registrazione metodo:
[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));
}
}
Il id_add
campo contiene l'ID del metodo per il metodo Java da richiamare. Il id_add
valore viene ottenuto da JNIEnv.GetMethodID
, che richiede la classe dichiarante (class_ref
), il nome del metodo Java ("add"
) e la firma JNI del metodo ("(II)I"
).
Dopo aver ottenuto l'ID del metodo, GetType
viene confrontato per ThresholdType
determinare se è necessario inviare virtuale o non virtuale. L'invio virtuale è obbligatorio quando GetType
corrisponde ThresholdType
a , come Handle
può fare riferimento a una sottoclasse allocata da Java che esegue l'override del metodo .
Quando GetType
non corrisponde ThresholdType
a , Adder
è stato sottoclassato (ad esempio, da ManagedAdder
) e l'implementazione Adder.Add
verrà richiamata solo se la sottoclasse ha richiamato base.Add
. Questo è il caso di invio non virtuale, che è dove ThresholdClass
entra in gioco. ThresholdClass
specifica la classe Java che fornirà l'implementazione del metodo da richiamare.
Registrazione del metodo
Si supponga di avere una definizione aggiornata ManagedAdder
che esegue l'override del Adder.Add
metodo :
partial class ManagedAdder : Adder {
public override int Add (int a, int b) {
return (a*2) + (b*2);
}
}
Tenere presente che Adder.Add
aveva un [Register]
attributo personalizzato:
[Register ("add", "(II)I", "GetAddHandler")]
Il costruttore dell'attributo [Register]
personalizzato accetta tre valori:
Nome del metodo Java,
"add"
in questo caso.La firma del tipo JNI del metodo,
"(II)I"
in questo caso.Metodo del connettore ,
GetAddHandler
in questo caso. I metodi del connettore verranno illustrati più avanti.
I primi due parametri consentono al processo di generazione ACW di generare una dichiarazione di metodo per eseguire l'override del metodo. L'AW risultante conterrà alcuni dei codici seguenti:
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);
// ...
}
Si noti che un @Override
metodo viene dichiarato, che delega a un n_
metodo con prefisso con lo stesso nome. In questo modo, quando il codice Java richiama ManagedAdder.add
, ManagedAdder.n_add
verrà richiamato, che consentirà l'esecuzione del metodo C# ManagedAdder.Add
di override.
Quindi, la domanda più importante: come viene ManagedAdder.n_add
collegato a ManagedAdder.Add
?
I metodi Java native
vengono registrati con il runtime Java (il runtime Android) tramite la funzione RegisterNatives JNI.
RegisterNatives
accetta una matrice di strutture contenenti il nome del metodo Java, la firma del tipo JNI e un puntatore a funzione per richiamare che segue la convenzione di chiamata JNI.
Il puntatore di funzione deve essere una funzione che accetta due argomenti puntatore seguiti dai parametri del metodo. Il metodo Java ManagedAdder.n_add
deve essere implementato tramite una funzione con il prototipo C seguente:
int FunctionName(JNIEnv *env, jobject this, int a, int b)
Xamarin.Android non espone un RegisterNatives
metodo. Al contrario, acw e MCW forniscono insieme le informazioni necessarie per richiamare RegisterNatives
: l'AW contiene il nome del metodo e la firma del tipo JNI, l'unica cosa mancante è un puntatore a funzione da associare.
È qui che entra in gioco il metodo del connettore. Il terzo [Register]
parametro dell'attributo personalizzato è il nome di un metodo definito nel tipo registrato o una classe base del tipo registrato che non accetta parametri e restituisce un oggetto System.Delegate
. L'oggetto restituito System.Delegate
a sua volta fa riferimento a un metodo con la firma della funzione JNI corretta. Infine, il delegato restituito dal metodo del connettore deve essere rooted in modo che il GC non lo raccolga, perché il delegato viene fornito a 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
Il GetAddHandler
metodo crea un Func<IntPtr, IntPtr, int, int, int>
delegato che fa riferimento al n_Add
metodo , quindi richiama JNINativeWrapper.CreateDelegate.
JNINativeWrapper.CreateDelegate
esegue il wrapping del metodo fornito in un blocco try/catch, in modo che tutte le eccezioni non gestite vengano gestite e generano l'evento AndroidEvent.UnhandledExceptionRaiser . Il delegato risultante viene archiviato nella variabile statica cb_add
in modo che il GC non liberi il delegato.
Infine, il metodo è responsabile del n_Add
marshalling dei parametri JNI ai tipi gestiti corrispondenti, quindi delegando la chiamata al metodo.
Nota: usare JniHandleOwnership.DoNotTransfer
sempre quando si ottiene un MCW su un'istanza Java. Considerarli come un riferimento locale (e quindi chiamare JNIEnv.DeleteLocalRef
) interromperà le transizioni dello stack gestito -> Java -> stack gestito.
Associazione completa adder
L'associazione gestita completa per il mono.android.tests.Adder
tipo è:
[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
}
Restrizioni
Quando si scrive un tipo che corrisponde ai criteri seguenti:
Sottoclassi
Java.Lang.Object
Ha un
[Register]
attributo personalizzatoRegisterAttribute.DoNotGenerateAcw
ètrue
Quindi per l'interazione GC il tipo non deve avere campi che possono fare riferimento a una Java.Lang.Object
sottoclasse o Java.Lang.Object
in fase di esecuzione. Ad esempio, i campi di tipo System.Object
e qualsiasi tipo di interfaccia non sono consentiti. I tipi che non possono fare riferimento alle Java.Lang.Object
istanze sono consentiti, ad esempio System.String
e List<int>
. Questa restrizione consiste nel impedire la raccolta di oggetti prematuri da parte del GC.
Se il tipo deve contenere un campo di istanza che può fare riferimento a un'istanza Java.Lang.Object
di , il tipo di campo deve essere System.WeakReference
o GCHandle
.
Binding di metodi astratti
I metodi di associazione abstract
sono in gran parte identici ai metodi virtuali di associazione. Esistono solo due differenze:
Il metodo astratto è astratto. Mantiene ancora l'attributo
[Register]
e la registrazione del metodo associata, l'associazione al metodo viene spostata nelInvoker
tipo .Viene creato un tipo non
abstract
Invoker
che sottoclassi il tipo astratto. IlInvoker
tipo deve eseguire l'override di tutti i metodi astratti dichiarati nella classe di base e l'implementazione sottoposta a override è l'implementazione dell'associazione di metodi, anche se il caso dispatch non virtuale può essere ignorato.
Si supponga, ad esempio, che il metodo precedente mono.android.test.Adder.add
fosse abstract
. L'associazione C# verrà modificata in modo che Adder.Add
fosse astratta e verrà definito un nuovo AdderInvoker
tipo che implementa 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));
}
}
Il Invoker
tipo è necessario solo quando si ottengono riferimenti JNI alle istanze create da Java.
Interfacce di binding
Le interfacce di associazione sono concettualmente simili alle classi di associazione contenenti metodi virtuali, ma molte delle specifiche differiscono in modi sottili (e non così sottili). Si consideri la dichiarazione di interfaccia Java seguente:
public interface Progress {
void onAdd(int[] values, int currentIndex, int currentSum);
}
Le associazioni di interfaccia hanno due parti: la definizione dell'interfaccia C# e una definizione invoker per l'interfaccia.
Definizione dell'interfaccia
La definizione dell'interfaccia C# deve soddisfare i requisiti seguenti:
La definizione dell'interfaccia deve avere un
[Register]
attributo personalizzato.La definizione dell'interfaccia deve estendere .
IJavaObject interface
In caso contrario, gli ACL non ereditano dall'interfaccia Java.Ogni metodo di interfaccia deve contenere un
[Register]
attributo che specifica il nome del metodo Java corrispondente, la firma JNI e il metodo del connettore.Il metodo connettore deve inoltre specificare il tipo in cui è possibile individuare il metodo del connettore.
Quando si esegue l'associazione abstract
e virtual
i metodi, il metodo connettore viene cercato all'interno della gerarchia di ereditarietà del tipo registrato. Le interfacce non possono avere metodi contenenti corpi, quindi questo non funziona, pertanto il requisito che un tipo venga specificato che indica dove si trova il metodo del connettore. Il tipo viene specificato all'interno della stringa del metodo del connettore, dopo i due punti ':'
e deve essere il nome del tipo completo dell'assembly del tipo contenente il invoker.
Le dichiarazioni dei metodi di interfaccia sono una traduzione del metodo Java corrispondente usando tipi compatibili . Per i tipi predefiniti Java, i tipi compatibili sono i tipi C# corrispondenti, ad esempio Java int
è C# int
. Per i tipi di riferimento, il tipo compatibile è un tipo che può fornire un handle JNI del tipo Java appropriato.
I membri dell'interfaccia non verranno richiamati direttamente da Java. La chiamata verrà mediata tramite il tipo invoker, quindi è consentita una certa flessibilità.
L'interfaccia Java Progress può essere dichiarata in C# come:
[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);
}
Si noti che il parametro Java int[]
viene mappato a un codice JavaArray<int>.
Questo non è necessario: potrebbe essere stato associato a un oggetto C# int[]
o a un IList<int>
oggetto o a un'altra operazione completamente diversa. Qualsiasi tipo venga scelto, deve Invoker
essere in grado di convertirlo in un tipo Java int[]
per la chiamata.
Definizione invoker
La Invoker
definizione del tipo deve ereditare , implementare Java.Lang.Object
l'interfaccia appropriata e fornire tutti i metodi di connessione a cui viene fatto riferimento nella definizione dell'interfaccia. Esiste un altro suggerimento che differisce da un'associazione di classi: gli class_ref
ID campo e metodo devono essere membri dell'istanza, non membri statici.
Il motivo per cui i membri dell'istanza preferita hanno a che fare con JNIEnv.GetMethodID
il comportamento nel runtime Android. Può trattarsi anche di un comportamento Java che non è stato testato. JNIEnv.GetMethodID
restituisce null durante la ricerca di un metodo proveniente da un'interfaccia implementata e non dall'interfaccia dichiarata. Si consideri l'interfaccia java.util.SortedMap<K, V> Java, che implementa l'interfaccia java.util.Map<K, V>. Map fornisce un metodo chiaro , pertanto una definizione apparentemente ragionevole Invoker
per SortedMap sarà:
// 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);
}
// ...
}
L'errore precedente avrà esito negativo perché JNIEnv.GetMethodID
verrà restituito null
durante la ricerca del Map.clear
metodo tramite l'istanza della SortedMap
classe .
Esistono due soluzioni a questo scopo: tenere traccia dell'interfaccia da cui proviene ogni metodo e avere un class_ref
per ogni interfaccia oppure mantenere tutti gli elementi come membri dell'istanza ed eseguire la ricerca del metodo sul tipo di classe più derivato, non sul tipo di interfaccia. Quest'ultima viene eseguita in Mono.Android.dll.
La definizione invoker include sei sezioni: il costruttore, il Dispose
metodo, i ThresholdType
membri e ThresholdClass
, il metodo, l'implementazione del GetObject
metodo di interfaccia e l'implementazione del metodo connettore.
Costruttore
Il costruttore deve cercare la classe di runtime dell'istanza richiamata e archiviare la classe di runtime nel campo dell'istanza 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);
}
}
Nota: la Handle
proprietà deve essere usata all'interno del corpo del costruttore e non il handle
parametro , come in Android v4.0 il handle
parametro potrebbe non essere valido al termine dell'esecuzione del costruttore di base.
Metodo Dispose
Il Dispose
metodo deve liberare il riferimento globale allocato nel costruttore:
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 e ThresholdClass
I ThresholdType
membri e ThresholdClass
sono identici a quanto trovato in un'associazione di classi:
partial class IAdderProgressInvoker {
protected override Type ThresholdType {
get {
return typeof (IAdderProgressInvoker);
}
}
protected override IntPtr ThresholdClass {
get {
return class_ref;
}
}
}
Metodo GetObject
Per supportare Extensions.JavaCast<T>(), è necessario un metodo statico GetObject
:
partial class IAdderProgressInvoker {
public static IAdderProgress GetObject (IntPtr handle, JniHandleOwnership transfer)
{
return new IAdderProgressInvoker (handle, transfer);
}
}
Metodi di interfaccia
Ogni metodo dell'interfaccia deve avere un'implementazione, che richiama il metodo Java corrispondente tramite 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));
}
}
Metodi del connettore
I metodi del connettore e l'infrastruttura di supporto sono responsabili del marshalling dei parametri JNI ai tipi C# appropriati. Il parametro Java int[]
verrà passato come JNI jintArray
, che è un oggetto all'interno di IntPtr
C#. Deve IntPtr
essere sottoposto a marshalling a per JavaArray<int>
supportare la chiamata dell'interfaccia 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);
}
}
}
Se int[]
si preferisce rispetto JavaList<int>
a , è possibile usare invece JNIEnv.GetArray():
int[] _values = (int[]) JNIEnv.GetArray(values, JniHandleOwnership.DoNotTransfer, typeof (int));
Si noti, tuttavia, che JNIEnv.GetArray
copia l'intera matrice tra le macchine virtuali, quindi per le matrici di grandi dimensioni questo potrebbe comportare un numero elevato di pressione GC aggiunta.
Definizione completa dell'invoker
Definizione IAdderProgressInvoker completa:
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
}
Riferimenti agli oggetti JNI
Molti metodi JNIEnv restituiscono riferimenti a oggetti JNI, simili a GCHandle
s. JNI offre tre diversi tipi di riferimenti a oggetti: riferimenti locali, riferimenti globali e riferimenti globali deboli. Tutti e tre sono rappresentati come System.IntPtr
, ma (in base alla sezione Tipi di funzione JNI) non tutti i IntPtr
tipi restituiti dai JNIEnv
metodi sono riferimenti. Ad esempio, JNIEnv.GetMethodID restituisce un IntPtr
oggetto , ma non restituisce un riferimento a un oggetto, restituisce un oggetto jmethodID
. Per informazioni dettagliate, vedere la documentazione della funzione JNI.
I riferimenti locali vengono creati dalla maggior parte dei metodi di creazione dei riferimenti.
Android consente solo a un numero limitato di riferimenti locali di esistere in qualsiasi momento, in genere 512. I riferimenti locali possono essere eliminati tramite JNIEnv.DeleteLocalRef.
A differenza di JNI, non tutti i metodi JNIEnv di riferimento che restituiscono riferimenti a oggetti restituiscono riferimenti locali; JNIEnv.FindClass restituisce un riferimento globale . È consigliabile eliminare i riferimenti locali nel modo più rapido possibile creando un oggetto Java.Lang.Object intorno all'oggetto e specificando JniHandleOwnership.TransferLocalRef
il costruttore Java.Lang.Object(Handle IntPtr, JniHandleOwnership transfer).
I riferimenti globali vengono creati da JNIEnv.NewGlobalRef e JNIEnv.FindClass. Possono essere distrutti con JNIEnv.DeleteGlobalRef. Gli emulatori hanno un limite di 2.000 riferimenti globali in sospeso, mentre i dispositivi hardware hanno un limite di circa 52.000 riferimenti globali.
I riferimenti globali deboli sono disponibili solo in Android v2.2 (Froyo) e versioni successive. I riferimenti globali deboli possono essere eliminati con JNIEnv.DeleteWeakGlobalRef.
Gestione dei riferimenti locali JNI
I metodi JNIEnv.GetObjectField, JNIEnv.GetStaticObjectField, JNIEnv.CallObjectMethod, JNIEnv.CallNonvirtualObjectMethod e JNIEnv.CallStaticObjectMethod restituiscono un IntPtr
riferimento locale JNI a un oggetto Java o IntPtr.Zero
se Java ha restituito null
. A causa del numero limitato di riferimenti locali che possono essere in sospeso contemporaneamente (512 voci), è consigliabile assicurarsi che i riferimenti vengano eliminati in modo tempestivo. Esistono tre modi in cui è possibile gestire i riferimenti locali: eliminarli in modo esplicito, creare un'istanza Java.Lang.Object
per tenerli in attesa e usare Java.Lang.Object.GetObject<T>()
per creare un wrapper chiamabile gestito intorno a essi.
Eliminazione esplicita dei riferimenti locali
JNIEnv.DeleteLocalRef viene usato per eliminare i riferimenti locali. Dopo aver eliminato il riferimento locale, non è più possibile usarlo, quindi è necessario prestare attenzione a assicurarsi che JNIEnv.DeleteLocalRef
sia l'ultima operazione eseguita con il riferimento locale.
IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
try {
// Do something with `lref`
}
finally {
JNIEnv.DeleteLocalRef (lref);
}
Wrapping con Java.Lang.Object
Java.Lang.Object
fornisce un costruttore Java.Lang.Object(Handle IntPtr, trasferimento JniHandleOwnership) che può essere usato per eseguire il wrapping di un riferimento JNI di uscita. Il parametro JniHandleOwnership determina la modalità di trattamento del IntPtr
parametro:
JniHandleOwnership.DoNotTransfer : l'istanza creata
Java.Lang.Object
creerà un nuovo riferimento globale dalhandle
parametro ehandle
rimane invariato. Il chiamante è responsabile della liberazionehandle
di , se necessario.JniHandleOwnership.TransferLocalRef : l'istanza creata
Java.Lang.Object
creerà un nuovo riferimento globale dalhandle
parametro ehandle
viene eliminato con JNIEnv.DeleteLocalRef . Il chiamante non deve liberarehandle
e non deve usarehandle
al termine dell'esecuzione del costruttore.JniHandleOwnership.TransferGlobalRef : l'istanza creata
Java.Lang.Object
assumerà la proprietà delhandle
parametro. Il chiamante non deve liberarehandle
.
Poiché i metodi di chiamata al metodo JNI restituiscono riferimenti locali, JniHandleOwnership.TransferLocalRef
in genere vengono usati:
IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef);
Il riferimento globale creato non verrà liberato finché l'istanza Java.Lang.Object
non viene sottoposto a Garbage Collection. Se è possibile, l'eliminazione dell'istanza consente di liberare il riferimento globale, velocizzando le operazioni di Garbage Collection:
IntPtr lref = JNIEnv.CallObjectMethod(instance, methodID);
using (var value = new Java.Lang.Object (lref, JniHandleOwnership.TransferLocalRef)) {
// use value ...
}
Uso di Java.Lang.Object.GetObject<T>()
Java.Lang.Object
fornisce un metodo Java.Lang.Object.GetObject<T>(IntPtr handle, JniHandleOwnership transfer) che può essere usato per creare un wrapper chiamabile gestito del tipo specificato.
Il tipo T
deve soddisfare i requisiti seguenti:
T
deve essere un tipo riferimento.T
deve implementare l'interfacciaIJavaObject
.Se
T
non è una classe o un'interfaccia astratta,T
deve fornire un costruttore con i tipi di(IntPtr, JniHandleOwnership)
parametro .Se
T
è una classe astratta o un'interfaccia, è necessario che sia disponibile un invoker perT
. Un invoker è un tipo non astratto che ereditaT
o implementaT
e ha lo stesso nome diT
con un suffisso Invoker. Ad esempio, se T è l'interfacciaJava.Lang.IRunnable
, il tipoJava.Lang.IRunnableInvoker
deve esistere e deve contenere il costruttore richiesto(IntPtr, JniHandleOwnership)
.
Poiché i metodi di chiamata al metodo JNI restituiscono riferimenti locali, JniHandleOwnership.TransferLocalRef
in genere vengono usati:
IntPtr lrefString = JNIEnv.CallObjectMethod(instance, methodID);
Java.Lang.String value = Java.Lang.Object.GetObject<Java.Lang.String>( lrefString, JniHandleOwnership.TransferLocalRef);
Ricerca di tipi Java
Per cercare un campo o un metodo in JNI, è necessario cercare prima il tipo dichiarante per il campo o il metodo. Il metodo Android.Runtime.JNIEnv.FindClass(string)) viene usato per cercare i tipi Java. Il parametro string è il riferimento al tipo semplificato o il riferimento completo al tipo per il tipo Java. Per informazioni dettagliate sui riferimenti ai tipi semplificati e completi, vedere la sezione Riferimenti ai tipi JNI.
Nota: a differenza di ogni altro JNIEnv
metodo che restituisce istanze dell'oggetto, FindClass
restituisce un riferimento globale, non un riferimento locale.
Campi istanza
I campi vengono modificati tramite ID campo. Gli ID campo vengono ottenuti tramite JNIEnv.GetFieldID, che richiede la classe in cui è definito il campo, il nome del campo e la firma del tipo JNI del campo.
Gli ID campo non devono essere liberati e sono validi purché venga caricato il tipo Java corrispondente. Android attualmente non supporta lo scaricamento delle classi.
Esistono due set di metodi per la modifica dei campi dell'istanza: uno per la lettura dei campi dell'istanza e uno per la scrittura dei campi dell'istanza. Tutti i set di metodi richiedono un ID campo per leggere o scrivere il valore del campo.
Lettura dei valori dei campi dell'istanza
Il set di metodi per la lettura dei valori dei campi dell'istanza segue il modello di denominazione:
* JNIEnv.Get*Field(IntPtr instance, IntPtr fieldID);
dove *
è il tipo del campo:
JNIEnv.GetObjectField : legge il valore di qualsiasi campo di istanza che non è un tipo predefinito, ad esempio
java.lang.Object
, matrici e tipi di interfaccia. Il valore restituito è un riferimento locale JNI.JNIEnv.GetBooleanField : leggere il valore dei campi dell'istanza
bool
.JNIEnv.GetByteField : leggere il valore dei campi dell'istanza
sbyte
.JNIEnv.GetCharField : leggere il valore dei campi dell'istanza
char
.JNIEnv.GetShortField : leggere il valore dei campi dell'istanza
short
.JNIEnv.GetIntField : leggere il valore dei campi dell'istanza
int
.JNIEnv.GetLongField : leggere il valore dei campi dell'istanza
long
.JNIEnv.GetFloatField : leggere il valore dei campi dell'istanza
float
.JNIEnv.GetDoubleField : leggere il valore dei campi dell'istanza
double
.
Scrittura di valori dei campi dell'istanza
Il set di metodi per la scrittura dei valori dei campi dell'istanza segue il modello di denominazione:
JNIEnv.SetField(IntPtr instance, IntPtr fieldID, Type value);
dove Type è il tipo del campo:
JNIEnv.SetField): scrivere il valore di qualsiasi campo che non sia un tipo predefinito, ad esempio
java.lang.Object
, matrici e tipi di interfaccia. IlIntPtr
valore può essere un riferimento locale JNI, un riferimento globale JNI, un riferimento globale debole JNI oIntPtr.Zero
(pernull
).JNIEnv.SetField): scrivere il valore dei campi dell'istanza
bool
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
sbyte
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
char
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
short
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
int
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
long
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
float
.JNIEnv.SetField): scrivere il valore dei campi dell'istanza
double
.
Campi statici
I campi statici vengono modificati tramite ID campo. Gli ID campo vengono ottenuti tramite JNIEnv.GetStaticFieldID, che richiede la classe in cui è definito il campo, il nome del campo e la firma del tipo JNI del campo.
Gli ID campo non devono essere liberati e sono validi purché venga caricato il tipo Java corrispondente. Android attualmente non supporta lo scaricamento delle classi.
Sono disponibili due set di metodi per la modifica di campi statici: uno per la lettura dei campi dell'istanza e uno per la scrittura dei campi dell'istanza. Tutti i set di metodi richiedono un ID campo per leggere o scrivere il valore del campo.
Lettura dei valori dei campi statici
Il set di metodi per la lettura dei valori dei campi statici segue il modello di denominazione:
* JNIEnv.GetStatic*Field(IntPtr class, IntPtr fieldID);
dove *
è il tipo del campo:
JNIEnv.GetStaticObjectField : legge il valore di qualsiasi campo statico che non è un tipo predefinito, ad esempio
java.lang.Object
, matrici e tipi di interfaccia. Il valore restituito è un riferimento locale JNI.JNIEnv.GetStaticBooleanField : leggere il valore dei
bool
campi statici.JNIEnv.GetStaticByteField : leggere il valore dei
sbyte
campi statici.JNIEnv.GetStaticCharField : leggere il valore dei
char
campi statici.JNIEnv.GetStaticShortField : leggere il valore dei
short
campi statici.JNIEnv.GetStaticLongField : leggere il valore dei
long
campi statici.JNIEnv.GetStaticFloatField : leggere il valore dei
float
campi statici.JNIEnv.GetStaticDoubleField : leggere il valore dei
double
campi statici.
Scrittura di valori di campo statici
Il set di metodi per la scrittura di valori di campo statici segue il modello di denominazione:
JNIEnv.SetStaticField(IntPtr class, IntPtr fieldID, Type value);
dove Type è il tipo del campo:
JNIEnv.SetStaticField): scrivere il valore di qualsiasi campo statico che non sia un tipo predefinito, ad esempio
java.lang.Object
, matrici e tipi di interfaccia. IlIntPtr
valore può essere un riferimento locale JNI, un riferimento globale JNI, un riferimento globale debole JNI oIntPtr.Zero
(pernull
).JNIEnv.SetStaticField): scrivere il valore dei
bool
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
sbyte
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
char
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
short
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
int
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
long
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
float
campi statici.JNIEnv.SetStaticField): scrivere il valore dei
double
campi statici.
Metodi di istanza
I metodi di istanza vengono richiamati tramite ID metodo. Gli ID metodo vengono ottenuti tramite JNIEnv.GetMethodID, che richiede il tipo in cui è definito il metodo, il nome del metodo e la firma del tipo JNI del metodo.
Gli ID metodo non devono essere liberati e sono validi purché venga caricato il tipo Java corrispondente. Android attualmente non supporta lo scaricamento delle classi.
Esistono due set di metodi per richiamare metodi: uno per richiamare i metodi virtualmente e uno per richiamare metodi non virtualmente. Entrambi i set di metodi richiedono un ID metodo per richiamare il metodo e la chiamata non virtuale richiede anche di specificare quale implementazione della classe deve essere richiamata.
I metodi di interfaccia possono essere cercati solo all'interno del tipo dichiarante; non è possibile cercare metodi provenienti da interfacce estese/ereditate. Per altri dettagli, vedere la sezione interfacce di binding/implementazione del invoker più avanti.
È possibile cercare qualsiasi metodo dichiarato nella classe o in qualsiasi classe di base o interfaccia implementata.
Chiamata al metodo virtuale
Il set di metodi per richiamare i metodi segue praticamente il modello di denominazione:
* JNIEnv.Call*Method( IntPtr instance, IntPtr methodID, params JValue[] args );
dove *
è il tipo restituito del metodo.
JNIEnv.CallObjectMethod : richiamare un metodo che restituisce un tipo non predefinito, ad esempio
java.lang.Object
, matrici e interfacce. Il valore restituito è un riferimento locale JNI.JNIEnv.CallBooleanMethod : richiamare un metodo che restituisce un
bool
valore.JNIEnv.CallByteMethod : richiamare un metodo che restituisce un
sbyte
valore.JNIEnv.CallCharMethod : richiamare un metodo che restituisce un
char
valore.JNIEnv.CallShortMethod : richiamare un metodo che restituisce un
short
valore.JNIEnv.CallLongMethod : richiamare un metodo che restituisce un
long
valore.JNIEnv.CallFloatMethod : richiamare un metodo che restituisce un
float
valore.JNIEnv.CallDoubleMethod : richiamare un metodo che restituisce un
double
valore.
Chiamata al metodo non virtuale
Il set di metodi per richiamare metodi non virtualmente segue il modello di denominazione:
* JNIEnv.CallNonvirtual*Method( IntPtr instance, IntPtr class, IntPtr methodID, params JValue[] args );
dove *
è il tipo restituito del metodo. La chiamata al metodo non virtuale viene in genere usata per richiamare il metodo di base di un metodo virtuale.
JNIEnv.CallNonvirtualObjectMethod : non richiama virtualmente un metodo che restituisce un tipo non predefinito, ad esempio
java.lang.Object
, matrici e interfacce. Il valore restituito è un riferimento locale JNI.JNIEnv.CallNonvirtualBooleanMethod : non richiama virtualmente un metodo che restituisce un
bool
valore.JNIEnv.CallNonvirtualByteMethod : non richiama virtualmente un metodo che restituisce un
sbyte
valore.JNIEnv.CallNonvirtualCharMethod : non richiama virtualmente un metodo che restituisce un
char
valore.JNIEnv.CallNonvirtualShortMethod : non richiama virtualmente un metodo che restituisce un
short
valore.JNIEnv.CallNonvirtualLongMethod : non richiama virtualmente un metodo che restituisce un
long
valore.JNIEnv.CallNonvirtualFloatMethod : non richiama virtualmente un metodo che restituisce un
float
valore.JNIEnv.CallNonvirtualDoubleMethod : non richiama virtualmente un metodo che restituisce un
double
valore.
Metodi statici
I metodi statici vengono richiamati tramite ID metodo. Gli ID metodo vengono ottenuti tramite JNIEnv.GetStaticMethodID, che richiede il tipo in cui è definito il metodo, il nome del metodo e la firma del tipo JNI del metodo.
Gli ID metodo non devono essere liberati e sono validi purché venga caricato il tipo Java corrispondente. Android attualmente non supporta lo scaricamento delle classi.
Chiamata al metodo statico
Il set di metodi per richiamare i metodi segue praticamente il modello di denominazione:
* JNIEnv.CallStatic*Method( IntPtr class, IntPtr methodID, params JValue[] args );
dove *
è il tipo restituito del metodo.
JNIEnv.CallStaticObjectMethod : richiamare un metodo statico che restituisce un tipo non predefinito, ad esempio
java.lang.Object
, matrici e interfacce. Il valore restituito è un riferimento locale JNI.JNIEnv.CallStaticBooleanMethod : richiamare un metodo statico che restituisce un
bool
valore.JNIEnv.CallStaticByteMethod : richiamare un metodo statico che restituisce un
sbyte
valore.JNIEnv.CallStaticCharMethod : richiamare un metodo statico che restituisce un
char
valore.JNIEnv.CallStaticShortMethod : richiamare un metodo statico che restituisce un
short
valore.JNIEnv.CallStaticLongMethod : richiamare un metodo statico che restituisce un
long
valore.JNIEnv.CallStaticFloatMethod : richiamare un metodo statico che restituisce un
float
valore.JNIEnv.CallStaticDoubleMethod : richiamare un metodo statico che restituisce un
double
valore.
Firme di tipo JNI
Le firme dei tipi JNI sono riferimenti ai tipi JNI (anche se non sono stati semplificati i riferimenti ai tipi), ad eccezione dei metodi. Con i metodi, la firma del tipo JNI è una parentesi '('
aperta , seguita dai riferimenti di tipo per tutti i tipi di parametro concatenati insieme (senza separare virgole o altro), seguita da una parentesi ')'
chiusa , seguita dal riferimento al tipo JNI del tipo restituito del metodo.
Ad esempio, dato il metodo Java:
long f(int n, String s, int[] array);
La firma del tipo JNI sarà:
(ILjava/lang/String;[I)J
In generale, è consigliabile usare il javap
comando per determinare le firme JNI. Ad esempio, la firma del tipo JNI del metodo java.lang.Thread.State.valueOf(String) è "(Ljava/lang/String;)Ljava/lang/Thread$State;", mentre la firma del tipo JNI del metodo java.lang.Thread.State.values è "()[Ljava/lang/Thread$State;". Attenzione ai punti e virgola finali; che fanno parte della firma del tipo JNI.
Riferimenti ai tipi JNI
I riferimenti ai tipi JNI sono diversi dai riferimenti ai tipi Java. Non è possibile usare nomi di tipi Java completi, ad java.lang.String
esempio con JNI, è invece necessario usare le varianti "java/lang/String"
JNI o "Ljava/lang/String;"
, a seconda del contesto. Per informazioni dettagliate, vedere di seguito.
Esistono quattro tipi di riferimenti ai tipi JNI:
- incorporato
- semplificato
- type
- array
Riferimenti ai tipi predefiniti
I riferimenti di tipo predefiniti sono un singolo carattere, usato per fare riferimento a tipi valore predefiniti. La mappatura è la seguente:
"B"
persbyte
."S"
pershort
."I"
perint
."J"
perlong
."F"
perfloat
."D"
perdouble
."C"
perchar
."Z"
perbool
."V"
pervoid
i tipi restituiti dal metodo.
Riferimenti ai tipi semplificati
I riferimenti ai tipi semplificati possono essere usati solo in JNIEnv.FindClass(string)). Esistono due modi per derivare un riferimento al tipo semplificato:
Da un nome Java completo sostituire ogni
'.'
elemento all'interno del nome del pacchetto e prima del nome del tipo con'/'
e ogni'.'
all'interno di un nome di tipo con'$'
.Leggere l'output di
'unzip -l android.jar | grep JavaName'
.
Una delle due causerà il mapping del tipo Java java.lang.Thread.State al riferimento java/lang/Thread$State
di tipo semplificato .
Riferimenti ai tipi
Un riferimento al tipo è un riferimento di tipo predefinito o un riferimento di tipo semplificato con un 'L'
prefisso e un ';'
suffisso. Per il tipo Java java.lang.String, il riferimento al tipo semplificato è "java/lang/String"
, mentre il riferimento al tipo è "Ljava/lang/String;"
.
I riferimenti ai tipi vengono usati con i riferimenti ai tipi di matrice e con le firme JNI.
Un modo aggiuntivo per ottenere un riferimento al tipo consiste nel leggere l'output di 'javap -s -classpath android.jar fully.qualified.Java.Name'
.
A seconda del tipo interessato, è possibile usare una dichiarazione del costruttore o un tipo restituito del metodo per determinare il nome JNI. Ad esempio:
$ 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
è un tipo di enumerazione Java, quindi è possibile usare la firma del valueOf
metodo per determinare che il riferimento al tipo è Ljava/lang/lang/Thread$State;.
Riferimenti ai tipi di matrice
I riferimenti ai tipi di matrice sono '['
preceduti da un riferimento al tipo JNI.
Non è possibile usare riferimenti ai tipi semplificati quando si specificano matrici.
Ad esempio, int[]
è "[I"
, int[][]
è "[[I"
e java.lang.Object[]
è "[Ljava/lang/Object;"
.
Generics Java e Cancellazione dei tipi
Nella maggior parte dei casi, come illustrato tramite JNI, i generics Java non esistono. Ci sono alcune "rughe", ma queste rughe sono in come Java interagisce con generics, non con come JNI cerca e richiama membri generici.
Non esiste alcuna differenza tra un tipo o un membro generico e un tipo o un membro non generico durante l'interazione tramite JNI. Ad esempio, il tipo generico java.lang.Class<T> è anche il tipo java.lang.Class
generico "raw", entrambi con lo stesso riferimento di tipo semplificato, "java/lang/Class"
.
Supporto dell'interfaccia nativa Java
Android.Runtime.JNIEnv è un wrapper gestito per l'interfaccia JNI (Jave Native Interface). Le funzioni JNI vengono dichiarate all'interno della specifica dell'interfaccia nativa Java, anche se i metodi sono stati modificati per rimuovere il parametro esplicito JNIEnv*
e IntPtr
vengono usati invece di jobject
, jclass
, jmethodID
e così via. Si consideri ad esempio la funzione NewObject JNI:
jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args);
Viene esposto come metodo JNIEnv.NewObject :
public static IntPtr NewObject(IntPtr clazz, IntPtr jmethod, params JValue[] parms);
La traduzione tra le due chiamate è ragionevolmente semplice. In C avresti:
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;
}
L'equivalente C# sarà:
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;
}
Una volta che un'istanza di Oggetto Java è contenuta in un intPtr, è probabile che si voglia eseguire un'operazione con essa. A tale scopo, è possibile usare metodi JNIEnv, ad esempio JNIEnv.CallVoidMethod(), ma se è già presente un wrapper C# analogico, è consigliabile creare un wrapper sul riferimento JNI. A tale scopo, è possibile usare il metodo di estensione 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>();
È anche possibile usare il metodo 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);
Inoltre, tutte le funzioni JNI sono state modificate rimuovendo il JNIEnv*
parametro presente in ogni funzione JNI.
Riepilogo
La gestione diretta con JNI è un'esperienza terribile che dovrebbe essere evitata a tutti i costi. Purtroppo, non è sempre evitabile; spero che questa guida fornirà assistenza quando si raggiungeranno i casi Java non associati con Mono per Android.