Kod biblioteki dla interfejsów API Androida Zawiera Android Game Development Kit.

Kod biblioteki to narzędzie wiersza poleceń, które generuje kod otokowy interfejsów API Androida napisanych w języku Java. Możesz użyć tej w natywnych aplikacjach na Androida, aby wywoływać interfejsy API w Javie bez konieczności ręcznego utwórz interfejs natywny Java (JNI). To narzędzie może uprościć programowanie Aplikacje na Androida napisane głównie w języku C lub C++.

Narzędzie działa, generując kod C dla symboli publicznych zawartych w Pliki Java Archive (JAR) lub klasy zdefiniowane w narzędziu . Kod wygenerowany przez narzędzie nie zastępuje Javy interfejsy API; ale stanowi pomost między kodem C i Javą. Bez ruchu z aplikacji wymaga, aby opakowane przez Ciebie biblioteki Javy były uwzględnione w projekcie.

Pobierz

Pobierz archiwum opakowania biblioteki i rozpakuj jego zawartość w katalogu. przez Ciebie wybrane.

Składnia

Narzędzie do opakowań biblioteki ma taką składnię wiersza poleceń:

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]
Parametr Opis
-i jar-file-to-be-wrapped Plik JAR służący do generowania kodu opakowań C. Możesz określić wiele plików JAR, na przykład:
-i first_library.jar -i second_library.jar...
-o output-path Lokalizacja w systemie plików dla wygenerowanego kodu.
-c config-file Ścieżka systemowa plików do pliku konfiguracji opakowania biblioteki. Więcej informacji: zapoznaj się z sekcją Konfiguracja.
-fa allow-list-file Ścieżka do pliku filtra, w której możesz określić symbole używane przez narzędzie zawijać. Szczegółowe informacje znajdziesz w sekcji Filtrowanie.
-fb block-list-file Ścieżka do pliku filtra zawierającego symbole wykluczone z opakowania. Dla: Więcej informacji znajdziesz w sekcji Filtry.
--skip_deprecated_symbols Powoduje pominięcie narzędzia kodu @Wycofano symboli.

Plik konfiguracji opakowania

Konfiguracja otoki biblioteki to plik JSON, który umożliwia kontrolowanie procesu generowania kodu. Plik ma następującą strukturę.

{
  // 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.
      ]
    },
  ]
}

Filtruj pliki

Warto wykluczyć niektóre symbole z plików JAR, które chcesz pakować. Ty możesz określić w konfiguracji plik filtra, aby wykluczyć symbole. Plik filtra to prosty plik tekstowy, w którym każdy wiersz definiuje symbol do zawijania. Użycie filtra plików tę składnię:

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

Oto przykładowy plik filtra:

# 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

Podaj konfigurację plik filtra określający symbole, które są dozwolone za pomocą parametru -fa, a zablokowane symbole – za pomocą -fb . Oba parametry można stosować jednocześnie. Jeśli oba filtry są podany, symbol zostanie opakowany, gdy jest on zdefiniowany w pliku filtra zezwalania i nie ma go w pliku filtra blokowego.

Przykładowy scenariusz

Załóżmy, że musisz opakować plik JAR ChatLibrary.jar zawierający klasa:

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

Twój projekt C wymaga wygenerowania natywnego opakowania dla tego pliku JAR, co pozwala włączyć natywna aplikacja na Androida, aby móc ją wywoływać w czasie działania. Wygeneruj ten kod za pomocą za pomocą tego polecenia:

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

Poprzednie polecenie generuje kod źródłowy C w katalogu ./generated_code Wygenerowany plik chat_manager.h zawiera kod podobny do to, co pozwoli Ci wywoływać bibliotekę w projekcie:

#include "java/lang/string.h"

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

Szczegółowy przykładowy scenariusz znajdziesz w przewodniku po kodzie biblioteki.

Szczegóły narzędzia

W sekcjach poniżej znajdziesz szczegółowe informacje o kodzie biblioteki funkcji.

Struktura katalogów wyjściowych

Wszystkie pliki źródłowe i pliki nagłówka C znajdują się w podkatalogach, które odzwierciedlają nazwa pakietu opakowanej klasy Java. Na przykład kod otokowy dla atrybutu określony plik JAR java.lang.Integer jest generowany w katalogu ./java/lang/integer.[h/cc]

Działaniem tych danych wyjściowych możesz sterować za pomocą configuration.

Cykl życia obiektu

Obiekty Java są w kodzie C reprezentowane przez nieprzezroczyste wskaźniki, zwane otokami. Opakowanie zarządza referencją JNI dla odpowiedniego obiektu Java. Opakowanie może tworzone w tych sytuacjach:

  • Opakowując istniejące odwołanie JNI przez wywołanie funkcji MyClass_wrapJniReference(jobject jobj) Funkcja nie przejmuje własności podanego pliku referencyjnego, ale tworzy własny globalne odniesienie JNI.
  • Przez utworzenie nowego obiektu, co odpowiada wywołaniu konstruktora w Java: MyClass_construct()
  • Zwracając z funkcji nowy kod, na przykład: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

Musisz zniszczyć wszystkie kody, które nie są już używane. W tym celu zadzwoń do specjalnego destroy() funkcja MyClass_destroy(MyClass* instance).

Funkcje, które zwracają kody, przydzielają dla nich nową pamięć na każde wywołanie nawet jeśli kody reprezentują tę samą instancję Java.

Jeśli na przykład metoda Java Singleton.getInstance() zawsze zwraca tej samej instancji, równoważna funkcja po stronie C utworzy nową instancję kodu towarzyszącego tej samej instancji 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.

Obsługa klas bez odwołań

Gdy w podanym pliku JAR nie można znaleźć klasy, kod biblioteki tworzy podstawowa implementacja składająca się z nieprzejrzystego wskaźnika i następujące metody:

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

Szczegóły generowania kodu

Po uruchomieniu kod biblioteki generuje kod C na podstawie symboli publicznych w pliki JAR dodane przez Ciebie do narzędzia. Wygenerowany kod C może powodować różnice z opakowanego kodu Javy. Na przykład C nie obsługuje funkcji takich jak OOP, typów ogólnych, przeciążania metod i innych funkcji języka Java.

Wygenerowany kod C odzwierciedlający te sytuacje może się różnić od typu jest typowy dla programistów w języku C. Przykłady w sekcjach poniżej pokazują, o tym, jak narzędzie może wygenerować kod C z kodu w Javie. Uwaga: fragmenty kodu obejmują kod w języku C/C++ i Java w skrócie. Te fragmenty mają tylko pokazać, jak narzędzie generuje kod dostosowany do danej sytuacji.

Zajęcia

W języku C klasy są przedstawiane jako wskaźniki nieprzejrzyste:

Kod C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

Przypadki nieprzezroczystych wskaźników są nazywane elementami zawijającymi. Narzędzie do tworzenia opakowań generuje dodatkowe funkcje pomocnicze dla poszczególnych klas. W poprzednim przykładzie MyClass, zostaną wygenerowane te funkcje:

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

Konstruktorki

Klasy z konstruktorami publicznymi lub domyślnymi są reprezentowane za pomocą specjalnych funkcje:

Kod C/C++

MyClass* MyClass_construct(String* data);

Java

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

Metody

Metody są przedstawione jako funkcje normalne. Nazwa funkcji zawiera pierwotną nazwę zajęć. Funkcje reprezentujące niestatyczne metody instancji mają jako pierwszego parametru wskaż strukturę reprezentującą obiekt Java, w którym funkcja jest wywoływana. To podejście jest analogiczne do this wskaźnik.

Kod 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) { ... }
}

Zajęcia wewnętrzne

Klasy wewnętrzne są reprezentowane bardzo podobne do zwykłych klas, z wyjątkiem nazwy klasy odpowiadająca struktura C zawiera połączone łańcuchowo nazwy klas zewnętrznych:

Kod C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

Metody klasy wewnętrznej

Metody klasy wewnętrznej są przedstawione w ten sposób:

Kod 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) { ... }
  }
}

Typy ogólne

Kod biblioteki nie opakowuje bezpośrednio typów ogólnych. Zamiast tego narzędzie generuje tylko kody na potrzeby instancji typu ogólnego.

Jeśli na przykład w interfejsie API istnieje klasa MyGeneric<T>, a są dwie wystąpienia tej klasy, takie jak MyGeneric<Integer> czy MyGeneric<String>, zostaną wygenerowane kody dla tych 2 wystąpień. Ten oznacza, że nie można tworzyć nowych instancji typu MyGeneric<T> za pomocą funkcji konfiguracji różnych typów. Zobacz ten przykład:

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

Wdrażanie interfejsów

Zaimplementuj interfejs w języku C, wywołując funkcję implementInterface() i podając dla każdej metody interfejsu. Można wdrażać tylko interfejsy w ten sposób; klas i klas abstrakcyjnych nie są obsługiwane. Zobacz następujący przykład:

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

Przykład zastosowania:

void onAction1() {
  // Handle action 1
}

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

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

Ograniczenia

Narzędzie do kodowania biblioteki jest w wersji beta. Możesz zobaczyć: Ograniczenia:

Nieobsługiwane konstrukcje Java

Kod biblioteki w wersji beta nie obsługuje tych konstrukcji:

  • Przeciążenie metod

    W języku C nie można zadeklarować 2 funkcji o tej samej nazwie. Jeśli klasa używa przeciążania metodą, wygenerowany kod C się nie skompiluje. obejściem problemu jest użycie tylko jednej metody z wystarczającym zestawem parametrów. Pozostałe funkcje można odfiltrować przy użyciu filtrów. Ten dotyczy też konstruktorów.

  • Metody oparte na szablonie

  • Pola inne niż static final int i static final String

  • Tablice

Potencjalne konflikty nazw

Ze względu na to, w jaki sposób klasy Java są reprezentowane w kodzie C, może być tam dostępna nazwa w bardzo rzadkich przypadkach. Na przykład klasa Foo<Bar> i element wewnętrzny klasa Bar w klasie Foo jest reprezentowana tym samym symbolem w C: typedef struct Foo_Bar_ Foo_Bar;

Pomoc

Jeśli zauważysz problem z kodem biblioteki, daj nam znać.

Przeglądaj błędy Zgłoś błąd
Inżynieria
Dokumentacja