Wrapper della libreria per le API Android   Parte di Android Game Development Kit.

Il wrapper della libreria è uno strumento a riga di comando (CLI) che genera codice wrapper in linguaggio C per le API Android scritte in Java. Puoi utilizzare questo codice nelle app native per Android per chiamare le API Java senza dover creare manualmente una Java Native Interface o JNI. Questo strumento può semplificare lo sviluppo di app per Android scritte principalmente in C o C++.

Lo strumento funziona generando codice C per i simboli pubblici contenuti nei file Java Archive (JAR) che fornisci o nelle classi definite nel file di configurazione dello strumento o in entrambi. Il codice generato dallo strumento non sostituisce le API Java, ma funge da ponte tra il codice C e Java. La tua app richiede comunque che le librerie Java di cui esegui il wrapping siano incluse nel tuo progetto.

Scarica

Scarica l'archivio wrapper della libreria ed estraine i contenuti nella directory di tua scelta.

Sintassi

Lo strumento wrapper della libreria ha la seguente sintassi della riga di comando:

java -jar lw.jar \
  [-i jar-file-to-be-wrapped] \
  [-o output-path] \
  [-c config-file] \
  [-fa allow-list-file] \
  [-fb block-list-file] \
  [--skip_deprecated_symbols]
Parametro Descrizione
-i jar-file-to-be-wrapped File JAR per generare il codice wrapper C. È possibile specificare più file JAR, ad esempio:
-i first_library.jar -i second_library.jar...
-o output-path Posizione del file system per il codice generato.
-c config-file Percorso del file system al file di configurazione del wrapper della libreria. Per maggiori dettagli, consulta la sezione Configurazione.
-fa allow-list-file Un percorso a un file di filtri in cui puoi specificare i simboli da inserire nello strumento. Per maggiori dettagli, consulta la sezione Filtro.
-fb block-list-file Un percorso a un file di filtri contenente simboli esclusi dal wrapping. Per maggiori dettagli, consulta la sezione Filtro.
--skip_deprecated_symbols Indica allo strumento di wrapping di ignorare i simboli @Deprecated.

File di configurazione del wrapper

La configurazione del wrapper della libreria è un file JSON che ti consente di controllare il processo di generazione del codice. Il file utilizza la seguente struttura.

{
  // An array of type-specific configs. A type config is useful when a user wants to map
  // a Java type to a manually defined C type without generating the code. For example, when a developer
  // has their own implementation of the "java.lang.String" class, they can tell the generator to use it
  // instead of generating it.
  "type_configs": [
    {
      // [Required] Name of a fully qualified Java type.
      "java_type": "java.lang.String",
      // The C type that the java_type will be mapped to.
      "map_to": "MyOwnStringImplementation",
      // A header file that contains the declaration of the "map_to" type.
      "source_of_definition": "my_wrappers/my_own_string_implementation.h",
      // Controls if a value should be passed by pointer or value.
      "pass_by_value": false
    }
  ],
  // An array of package-specific configs.
  "package_configs": [
    {
      // [Required] A name of a Java package that this section regards. A wildchar * can be used at the
      // end of the package name to apply this config to all packages whose name starts with this value.
      "package_name": "androidx.core.app*",
      // A subdirectory relative to the root directory where the generated code will be located.
      "sub_directory": "androidx_generated/",
      // If true, the generated file structure reflects the package name. For example, files generated
      // for the package com.google.tools will be placed in the directory com/google/tools/.
      "file_location_by_package_name": true,
      // A prefix added to all class names from this package.
      "code_prefix": "Gen",
      // A prefix added to all generated file names from this package.
      "file_prefix": = "gen_"
    }
  ],
  // An array of manually defined classes for wrapping. Defining classes manually is useful when a
  // jar file with desired classes are not available or a user needs to wrap just a small part of an SDK.
  "custom_classes": [
    {
      // [Required] A fully-qualified Java class name. To define inner class, use symbol "$", for example
      // "class com.example.OuterClass$InnerClass".
      "class_name": "class java.util.ArrayList<T>",
      // List of methods.
      "methods": [
        "ArrayList()", // Example of a constructor.
        "boolean add(T e)", // Example of a method that takes a generic parameter.
        "T get(int index)", // Example of a method that returns a generic parameter.
        "int size()" // Example of parameterless method.
      ]
    },
  ]
}

Filtra i file

Potrebbe essere utile escludere alcuni simboli dai file JAR che prevedi di eseguire il wrapping. Puoi specificare un file di filtro nella configurazione per escludere i simboli. Un file di filtri è un semplice file di testo in cui ogni riga definisce un simbolo da racchiudere. I file di filtro utilizzano la seguente sintassi:

java-symbol-name java-jni-type-signature

Di seguito è riportato un esempio di file di filtro:

# Class filter
java.util.ArrayList Ljava.util.ArrayList;

# Method filter
java.util.ArrayList.lastIndexOf (Ljava.lang.Object;)I

# Field filter
android.view.KeyEvent.KEYCODE_ENTER I

Fornisci la configurazione di un file di filtro che specifica i simboli consentiti utilizzando il parametro -fa e i simboli bloccati utilizzando il parametro -fb. Entrambi i parametri possono essere utilizzati contemporaneamente. Se vengono forniti entrambi i filtri, un simbolo verrà incluso quando è definito nel file di filtro consentito e non è presente nel file di filtro bloccato.

Scenario di esempio

Supponiamo di dover eseguire il wrapping del file JAR ChatLibrary.jar contenente la seguente classe:

public class ChatManager {
  public static void sendMessage(int userId, String message) {...}
}

Il tuo progetto C richiede la generazione di un wrapper nativo per questo file JAR, consentendo alla tua app Android nativa di chiamarlo durante l'esecuzione. Genera questo codice utilizzando il wrapper della libreria con il seguente comando:

java -jar lw.jar -i ChatLibrary.jar -o ./generated_code/

Il comando precedente genera il codice sorgente C nella directory ./generated_code. Il file generato chat_manager.h contiene un codice simile al seguente, che ti consente di chiamare la libreria nel tuo progetto:

#include "java/lang/string.h"

typedef struct ChatManager_ ChatManager;
void ChatManager_sendMessage(int32_t user_id, String* message);

Per uno scenario di esempio dettagliato, consulta la guida al wrapper della libreria.

Dettagli dello strumento

Le seguenti sezioni forniscono informazioni dettagliate sulle funzionalità del wrapper della libreria.

Struttura della directory di output

Tutti i file di origine e di intestazione C si trovano in sottodirectory che riflettono il nome del pacchetto della classe Java sottoposta a wrapping. Ad esempio, il codice wrapper per il file JAR specificato java.lang.Integer viene generato nella directory ./java/lang/integer.[h/cc].

Puoi controllare questo comportamento di output utilizzando il file di configurazione dello strumento.

Ciclo di vita degli oggetti

Gli oggetti Java sono rappresentati nel codice C come puntatori opachi, chiamati wrapper. Un wrapper gestisce un riferimento JNI per un oggetto Java corrispondente. Un wrapper può essere creato nei seguenti scenari:

  • Eseguendo il wrapping di un riferimento JNI esistente chiamando la funzione MyClass_wrapJniReference(jobject jobj). La funzione non acquisisce la proprietà del riferimento fornito, ma crea un proprio riferimento JNI globale.
  • Creando un nuovo oggetto, che equivale a chiamare un costruttore in Java: MyClass_construct()
  • Restituendo un nuovo wrapper da una funzione, ad esempio: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

Devi eliminare tutti i wrapper quando non vengono più utilizzati. Per farlo, chiama la funzione destroy() dedicata MyClass_destroy(MyClass* instance).

Le funzioni che restituiscono wrapper allocano una nuova memoria per ogni chiamata, anche se i wrapper rappresentano la stessa istanza Java.

Ad esempio, quando il metodo Java Singleton.getInstance() restituisce sempre la stessa istanza, la funzione equivalente sul lato C crea una nuova istanza di un wrapper per la stessa istanza Java:

Singleton* singleton_a = Singleton_getInsance();
Singleton* singleton_b = Singleton_getInsance();

// singleton_a and singleton_b are different pointers, even though they represent the same Java instance.

Gestire i corsi senza riferimenti

Quando non è possibile trovare una classe in un file JAR fornito, il wrapper della libreria crea un'implementazione di base costituita da un puntatore opaco e dai seguenti metodi:

  • wrapJniReference()
  • getJniReference()
  • destroy()

Dettagli della generazione di codice

Quando viene eseguito, il wrapper della libreria genera codice C in base ai simboli pubblici nei file JAR che fornisci allo strumento. Il codice C generato potrebbe presentare differenze rispetto al codice Java sottoposto a wrapping. Ad esempio, C non supporta funzionalità come la programmazione orientata agli oggetti, i tipi generici, l'overload dei metodi o altre funzionalità Java.

Il codice C generato che riflette queste situazioni potrebbe differire dal tipo di codice previsto dagli sviluppatori C. Gli esempi nelle sezioni seguenti forniscono il contesto su come lo strumento può generare C dal codice Java. Nota: negli snippet di codice, gli esempi seguenti includono snippet di codice C/C++ e Java. Questi snippet hanno il solo scopo di dimostrare come lo strumento genera il codice per ogni situazione data.

Classi

Le classi sono rappresentate come puntatori opachi in C:

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

Le istanze di puntatori opachi sono indicate come wrapper. Lo strumento wrapper genera funzioni di supporto aggiuntive per ogni classe. Per la classe MyClass dell'esempio precedente, vengono generate le seguenti funzioni:

// Wraps a JNI reference with MyClass. The 'jobj' must represent MyClass on the Java side.
MyClass* MyClass_wrapJniReference(jobject jobj);

// Return JNI reference associated with the 'MyClass' pointer.
jobject MyClass_getJniReference(const MyClass* object);

// Destroys the object and releases underlying JNI reference.
void MyClass_destroy(const MyClass* object);

Costruttori

Le classi con costruttori pubblici o predefiniti sono rappresentate utilizzando funzioni speciali:

C/C++

MyClass* MyClass_construct(String* data);

Java

public class MyClass {
  public MyClass(String data) { ... }
}

Metodi

I metodi sono rappresentati come funzioni normali. Il nome di una funzione contiene il nome della classe originale. Le funzioni che rappresentano metodi di istanza non statici hanno come primo parametro un puntatore a una struttura che rappresenta un oggetto Java, per conto del quale viene chiamata la funzione. Questo approccio è analogo al puntatore this.

C/C++

Result* MyClass_doAction(const MyClass* my_class_instance, int32_t action_id, String* data);
int32_t MyClass_doAction(int32_t a, int32_t b);

Java

public class MyClass {
  public Result doAction(int actionId, String data) { ... }
  public static int doCalculations(int a, int b) { ... }
}

Classi interne

Le classi interne sono rappresentate in modo simile alle classi normali, tranne per il fatto che il nome della struttura C corrispondente contiene i nomi concatenati delle classi esterne:

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

public class MyClass {
  public class InnerClass {...}
}

Metodi della classe interna

I metodi della classe interna sono rappresentati come segue:

C/C++

bool MyClass_InnerClass_setValue(MyClass_InnerClass* my_class_inner_class_instance, int32_t value);

Java

public class MyClass {
  public class InnerClass {
    public boolean setValue(int value) { ... }
  }
}

Tipi generici

Il wrapper della libreria non esegue il wrapping diretto dei tipi generici. Lo strumento genera invece solo wrapper per istanze di tipo generico.

Ad esempio, quando in un'API esiste una classe MyGeneric<T> e ci sono due istanze di questa classe, ad esempio MyGeneric<Integer> e MyGeneric<String>, vengono generati wrapper per queste due istanze. Ciò significa che non puoi creare nuove istanze di tipo MyGeneric<T> utilizzando configurazioni di tipo diverse. Vedi il seguente esempio:

C/C++

// result.h

typedef struct Result_Integer_ Result_Integer;
typedef struct Result_Float_ Result_Float;

Integer* Result_Integer_getResult(const Result_Integer* instance);
Float* Result_Float_getResult(const Result_Float* instance);

// data_processor.h

typedef struct DataProcessor_ DataProcessor;

Result_Integer* DataProcessor_processIntegerData(const DataProcessor* instance);
Result_Float* DataProcessor_processFloatData(constDataProcessor* instance);

Java

public class Result<T> {
  public T getResult();
}

public class DataProcessor {
  public Result<Integer> processIntegerData();
  public Result<Float> processFloatData();
}

Implementare le interfacce

Implementa un'interfaccia C chiamando implementInterface() e fornendo una funzione di callback per ogni metodo dell'interfaccia. Solo le interfacce possono essere implementate in questo modo; le classi e le classi astratte non sono supportate. Vedi il seguente esempio:

C/C++

// observer.h

typedef struct Observer_ Observer;
typedef void (*Observer_onAction1Callback)();
typedef void (*Observer_onAction2Callback)(int32_t data);

Observer* Observer_implementInterface(
Observer_onAction1Callback observer_on_action1_callback,
Observer_onAction2Callback observer_on_action2_callback);

Java

public interface Observer {
  void onAction1();
  void onAction2(int data);
}

public class Subject {
  public void registerObserver(Observer observer);
}

Esempio di utilizzo:

void onAction1() {
  // Handle action 1
}

void onAction2(int32_t data) {
  // Handle action 2
}

Observer* observer = Observer_implementInterface(onAction1, onAction2);
Subject_registerObserver(subject, observer);

Limitazioni

Lo strumento wrapper della libreria è in versione beta. Potresti riscontrare le seguenti limitazioni:

Costrutti Java non supportati

La versione beta del wrapper della libreria non supporta i seguenti costrutti:

  • Overloading del metodo

    Il linguaggio C non consente di dichiarare due funzioni con lo stesso nome. Se la classe utilizza l'overload del metodo, il codice C generato non verrà compilato. La soluzione alternativa consiste nell'utilizzare un solo metodo con un insieme sufficiente di parametri. Le funzioni rimanenti possono essere filtrate utilizzando i filtri. Ciò vale anche per i costruttori.

  • Metodi basati su modelli

  • Campi diversi da static final int e static final String

  • Array

Potenziali collisioni di nomi

A causa della modalità di rappresentazione delle classi Java nel codice C, in casi molto rari potrebbero verificarsi conflitti di nomi. Ad esempio, una classe Foo<Bar> e una classe interna Bar all'interno di una classe Foo sono rappresentate dallo stesso simbolo in C: typedef struct Foo_Bar_ Foo_Bar;

Assistenza

Se riscontri un problema con il wrapper della libreria, comunicacelo.

Sfogliare i bug Segnala un bug
Ingegneria
Documentazione