Kod biblioteki dla interfejsów API Androida Część pakietu Android Game Development Kit.

Kod biblioteki to narzędzie wiersza poleceń, które generuje kod otoki języka C na potrzeby interfejsów API Androida napisanych w Javie. Możesz używać tego kodu w natywnych aplikacjach na Androida do wywoływania interfejsów API Java bez konieczności ręcznego tworzenia interfejsu natywnego języka Java (JNI). To narzędzie upraszcza tworzenie aplikacji na Androida napisanych głównie w C lub C++.

Narzędzie generuje kod C dla symboli publicznych zawartych w podanych przez Ciebie plikach Java Archive (JAR) lub klas zdefiniowanych w pliku konfiguracji narzędzia albo obu tych metod. Kod wygenerowany przez narzędzie nie zastępuje interfejsów API Java. Działa jako most między kodem C i Javą. Twoja aplikacja nadal wymaga opakowanych bibliotek Java, aby zostały uwzględnione w projekcie.

Pobierz

Pobierz archiwum opakowań biblioteki i rozpakuj jego zawartość do wybranego katalogu.

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 Description
-i jar-file-to-be-wrapped Plik JAR do wygenerowania kodu otoki C. Można 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 wygenerowanego kodu.
-c config-file Ścieżka systemowa plików do pliku konfiguracji opakowania biblioteki. Szczegółowe informacje znajdziesz w sekcji Konfiguracja.
-fa allow-list-file Ścieżka do pliku filtra, w której możesz określić symbole do zawijania przez narzędzie. Szczegółowe informacje znajdziesz w sekcji Filtr.
-fb block-list-file Ścieżka do pliku filtra zawierającego symbole wykluczone z zawijania. Szczegółowe informacje znajdziesz w sekcji Filtr.
--skip_deprecated_symbols Powoduje, że narzędzie opakowujące pomija symbole @Cofnij.

Plik konfiguracji otoki

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

Przydatne może być wykluczenie niektórych symboli z plików JAR, które chcesz spakować. Możesz określić w konfiguracji plik filtra do wykluczenia symboli. Plik filtra to prosty plik tekstowy, w którym każdy wiersz zawiera symbol do zawijania. Pliki filtrowania mają taką 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

W konfiguracji musisz podać plik filtra, który określa symbole dozwolone w parametrze -fa oraz zablokowane symbole – parametr -fb. Oba te parametry można stosować jednocześnie. Jeśli podasz oba filtry, symbol zostanie zapakowany, gdy zostanie zdefiniowany w pliku filtra zezwalającego, a nie ma go w pliku filtra blokowego.

Przykładowy scenariusz

Załóżmy, że musisz zapakować plik JAR ChatLibrary.jar zawierający tę klasę:

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

Twój projekt w języku C wymaga wygenerowania natywnego kodu dla tego pliku JAR, który umożliwi natywnej aplikacji na Androida wywoływanie go w czasie działania. Wygeneruj ten kod za pomocą otoki biblioteki za pomocą tego polecenia:

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

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

#include "java/lang/string.h"

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

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

Szczegóły narzędzia

Poniższe sekcje zawierają szczegółowe informacje o funkcjach kodu biblioteki.

Struktura katalogu wyjściowego

Wszystkie pliki źródłowe i nagłówki C znajdują się w podkatalogach, które odzwierciedlają nazwę pakietu opakowanej klasy Java. Na przykład kod otoki wskazanego pliku JAR java.lang.Integer jest generowany w katalogu ./java/lang/integer.[h/cc].

Możesz kontrolować to działanie wyjściowe za pomocą pliku konfiguracji narzędzia.

Cykl życia obiektu

Obiekty Java są reprezentowane w kodzie C jako nieprzezroczyste wskaźniki, nazywane kodami. Kod zarządza odwołaniem do JNI odpowiedniego obiektu Java. Otoka można utworzyć 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 odwołania, ale tworzy swoje własne globalne odwołanie do JNI.
  • Tworząc nowy obiekt, który jest odpowiednikiem wywołania konstruktora w języku 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. Aby to zrobić, wywołaj dedykowaną funkcję destroy() MyClass_destroy(MyClass* instance).

Funkcje, które zwracają kod, przydzielają do każdego wywołania nową pamięć, nawet jeśli kody reprezentują tę samą instancję Javy.

Jeśli np. metoda Java Singleton.getInstance() zawsze zwraca tę samą instancję, odpowiadająca jej funkcja po stronie C utworzy nowe wystąpienie otoki dla tej samej instancji Javy:

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, do których nie ma odwołań

Jeśli w udostępnionym pliku JAR nie można znaleźć klasy, kod libarary tworzy podstawową implementację składającą się z nieprzejrzystego wskaźnika i tych metod:

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

Szczegóły generowania kodu

Po uruchomieniu kod biblioteki generuje kod C na podstawie symboli publicznych w plikach JAR udostępnionych do narzędzia. Wygenerowany kod C może różnić się od opakowanego kodu Java. Na przykład język C nie obsługuje takich funkcji jak OOP, typy ogólne, przeciążenie metod i inne funkcje Java.

Wygenerowany kod C odzwierciedlający te sytuacje może różnić się od typu kodu, którego oczekują programiści C. Przykłady w kolejnych sekcjach pokazują kontekst tego, jak narzędzie może generować C z kodu Java. Uwaga: we fragmencie kodu poniższe przykłady obejmują fragmenty kodu w języku C/C++ i Java. Mają one na celu wyłącznie przedstawienie, jak narzędzie generuje kod w danej sytuacji.

Zajęcia

W klasie C klasy są przedstawiane za pomocą wskaźników nieprzezroczystych:

Kod C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

Wystąpienia wskaźników nieprzezroczystych są określane jako kody. Narzędzie opakowujące generuje dodatkowe funkcje pomocy dla poszczególnych klas. W poprzednim przykładzie w klasie MyClass generowane są 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);

Konstruktorzy

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

Kod C/C++

MyClass* MyClass_construct(String* data);

Java

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

Metody

Metody są przedstawiane jako funkcje normalne. Nazwa funkcji zawiera pierwotną nazwę klasy. Funkcje reprezentujące niestatyczne metody instancji mają jako pierwszy parametr wskaźnik do struktury reprezentującej obiekt Java, w imieniu którego jest wywoływana funkcja. To podejście jest podobne do wskaźnika this.

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

Klasy wewnętrzne

Klasy wewnętrzne są reprezentowane podobnie do zwykłych klas, ale nazwa odpowiedniej struktury C zawiera powiązane łańcuchowo nazwy klas zewnętrznych:

Kod C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

Metody klas wewnętrznych

Metody klas wewnętrznych są reprezentowane jako:

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

Rodzaje ogólne

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

Jeśli na przykład w interfejsie API istnieje klasa MyGeneric<T> i istnieją 2 instancje tej klasy, takie jak MyGeneric<Integer> i MyGeneric<String>, generowane są kody tych 2 instancji. Oznacza to, że nie możesz tworzyć nowych instancji typu MyGeneric<T> za pomocą różnych konfiguracji typów. Przyjrzyj się temu przykładowi:

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 C, wywołując implementInterface() i udostępniając funkcję wywołania zwrotnego dla każdej metody interfejsu. W ten sposób można implementować tylko interfejsy. Klasy i klasy abstrakcyjne nie są obsługiwane. Zobacz ten 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 opakowań biblioteki jest w wersji beta. Możesz natrafić na te ograniczenia:

Nieobsługiwane konstrukcje Java

Wersja beta otoki biblioteki nie obsługuje tych konstrukcji:

  • Przeciążenie metod

    Język C nie umożliwia deklarowania 2 funkcji o tej samej nazwie. Jeśli klasa wykorzystuje przeciążenie metod, wygenerowany kod C nie zostanie skompilowany. Aby obejść ten problem, użyj tylko jednej metody z wystarczającym zestawem parametrów. Pozostałe funkcje można odfiltrować za pomocą filtrów. Dotyczy to też konstruktorów.

  • Metody oparte na szablonach

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

  • Tablice

Potencjalne konflikty nazw

Ze względu na sposób reprezentowania klas Java w kodzie C w bardzo rzadkich przypadkach mogą występować konflikty nazw. Na przykład klasa Foo<Bar> i klasa wewnętrzna Bar w klasie Foo są reprezentowane w języku C przez ten sam symbol: 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