Wrapper library untuk API Android   Bagian dari Android Game Development Kit.

Wrapper library adalah alat command line (CLI) yang menghasilkan kode wrapper bahasa C untuk API Android yang ditulis dalam Java. Anda dapat menggunakan kode ini di aplikasi Android native untuk memanggil API Java tanpa perlu membuat Antarmuka Native Java atau JNI secara manual. Alat ini dapat menyederhanakan aplikasi Android pengembangan yang ditulis terutama dalam C atau C++.

Alat ini berfungsi dengan membuat kode C untuk simbol publik yang terdapat dalam file Java Archive (JAR) yang Anda berikan, atau class yang ditentukan di file konfigurasi alat, atau keduanya. Kode yang dihasilkan oleh alat tersebut tidak menggantikan API Java; tetapi berfungsi sebagai jembatan antara kode C dan Java. Aplikasi Anda tetap memerlukan library Java yang Anda gabungkan untuk disertakan dalam project.

Download

Download arsip wrapper library dan ekstrak kontennya ke direktori pilihan Anda.

Sintaksis

Alat wrapper library memiliki sintaksis command line berikut:

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]
Parameter Deskripsi
-i jar-file-to-be-wrapped File JAR untuk menghasilkan kode wrapper C. Beberapa JAR dapat ditentukan, misalnya:
-i first_library.jar -i second_library.jar...
-o output-path Lokasi sistem file untuk kode yang dihasilkan.
-c config-file Jalur sistem file ke file konfigurasi wrapper library. Untuk mengetahui detailnya, lihat bagian Konfigurasi.
-fa allow-list-file Jalur ke file filter tempat Anda dapat menentukan simbol untuk digabungkan alat tersebut. Untuk mengetahui detailnya, lihat bagian Filter.
-fb block-list-file Jalur ke file filter yang berisi simbol yang dikecualikan dari penggabungan. Untuk mengetahui detailnya, lihat bagian Filter.
--skip_deprecated_symbols Instruksikan alat wrapper untuk melewati simbol @Deprecated.

File konfigurasi wrapper

Konfigurasi wrapper library adalah file JSON yang memungkinkan Anda mengontrol proses pembuatan kode. File tersebut menggunakan struktur berikut.

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

File filter

Sebaiknya kecualikan beberapa simbol dari file JAR yang akan digabungkan. Anda dapat menentukan file filter dalam konfigurasi untuk mengecualikan simbol. File filter adalah file teks sederhana yang setiap barisnya menetapkan simbol untuk digabungkan. File filter menggunakan sintaksis berikut:

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

Berikut adalah contoh file filter:

# 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

Anda memberikan file filter untuk konfigurasi yang menentukan simbol yang diizinkan menggunakan parameter -fa, dan simbol yang diblokir menggunakan parameter -fb. Kedua parameter dapat digunakan secara bersamaan. Jika kedua filter diberikan, simbol akan digabungkan saat ditetapkan dalam file filter izinkan dan tidak ada di file filter blokir.

Contoh skenario

Asumsikan bahwa Anda perlu menggabungkan file JAR ChatLibrary.jar yang berisi class berikut:

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

Project C mengharuskan Anda membuat wrapper native untuk JAR ini, sehingga aplikasi Android native dapat memanggilnya selama runtime. Buat kode ini menggunakan wrapper library dengan perintah berikut:

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

Perintah sebelumnya menghasilkan kode sumber C ke direktori ./generated_code. File chat_manager.h yang dihasilkan berisi kode yang mirip dengan berikut, yang memungkinkan Anda memanggil library di project Anda:

#include "java/lang/string.h"

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

Untuk contoh skenario yang mendalam, lihat Panduan wrapper library.

Detail alat

Bagian berikut memberikan informasi mendetail tentang fungsi wrapper library.

Struktur direktori output

Semua file sumber dan header C terletak di subdirektori yang mencerminkan nama paket class Java yang digabungkan. Misalnya, kode wrapper untuk JAR java.lang.Integer yang ditentukan akan dihasilkan ke direktori ./java/lang/integer.[h/cc].

Anda dapat mengontrol perilaku output ini menggunakan file konfigurasi alat.

Siklus proses objek

Objek Java direpresentasikan dalam kode C sebagai pointer buram, yang disebut wrapper. Wrapper mengelola referensi JNI untuk objek Java yang sesuai. Wrapper dapat dibuat dalam skenario berikut:

  • Dengan menggabungkan referensi JNI yang ada dengan memanggil MyClass_wrapJniReference(jobject jobj) fungsi. Fungsi ini tidak mengambil kepemilikan referensi yang diberikan, tetapi membuat referensi JNI globalnya sendiri.
  • Dengan membuat objek baru yang setara dengan memanggil konstruktor di Java: MyClass_construct()
  • Dengan menampilkan wrapper baru dari fungsi, misalnya: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

Anda perlu menghancurkan semua wrapper saat tidak digunakan lagi. Untuk melakukannya, panggil fungsi MyClass_destroy(MyClass* instance) khusus destroy().

Fungsi yang menampilkan wrapper mengalokasikan memori baru untuknya untuk setiap panggilan, meskipun wrapper mewakili instance Java yang sama.

Misalnya, jika metode Java Singleton.getInstance() selalu menampilkan instance yang sama, fungsi yang setara pada sisi C akan membuat instance baru wrapper untuk instance Java yang sama:

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.

Menangani class yang tidak direferensikan

Jika class tidak dapat ditemukan di JAR yang disediakan, wrapper library akan membuat implementasi dasar yang terdiri dari pointer buram dan metode berikut:

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

Detail pembuatan kode

Saat dijalankan, wrapper library menghasilkan kode C berdasarkan simbol publik dalam file JAR yang Anda berikan alatnya. Kode C yang dihasilkan mungkin menunjukkan perbedaan dari kode Java yang digabungkan. Misalnya, C tidak mendukung fitur seperti OOP, jenis generik, overloading metode, atau fitur Java lainnya.

Kode C yang dihasilkan yang mencerminkan situasi ini mungkin berbeda dari jenis kode yang diharapkan oleh developer C. Contoh di bagian berikut memberikan konteks tentang cara alat dapat menghasilkan C dari kode Java. Catatan: Dalam cuplikan kode, contoh berikut menyertakan cuplikan kode C/C++ dan Java. Cuplikan ini hanya dimaksudkan untuk menunjukkan bagaimana alat tersebut menghasilkan kode untuk setiap situasi tertentu.

Class

Class direpresentasikan sebagai pointer buram di C:

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

Instance pointer buram direferensikan sebagai wrapper. Alat wrapper menghasilkan fungsi dukungan tambahan untuk setiap class. Untuk class contoh MyClass sebelumnya, fungsi berikut dibuat:

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

Konstruktor

Class dengan konstruktor publik atau default direpresentasikan menggunakan fungsi khusus:

C/C++

MyClass* MyClass_construct(String* data);

Java

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

Metode

Metode direpresentasikan sebagai fungsi normal. Nama fungsi berisi nama class asli. Fungsi yang mewakili metode instance non-statis memiliki sebuah pointer sebagai parameter pertama pada sebuah struktur yang mewakili objek Java, dan fungsi dipanggil atas namanya. Pendekatan ini analog dengan pointer 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) { ... }
}

Class dalam

Class dalam direpresentasikan secara mirip dengan class normal, hanya saja nama struktur C yang sesuai berisi nama berantai class luar:

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

Metode class dalam

Metode class dalam direpresentasikan sebagai berikut:

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

Jenis generik

Wrapper library tidak secara langsung menggabungkan jenis generik. Sebagai gantinya, alat ini hanya membuat wrapper untuk pembuatan instance jenis generik.

Misalnya, jika terdapat class MyGeneric<T> di API, dan ada dua pembuatan instance class ini, seperti MyGeneric<Integer> dan MyGeneric<String>, wrapper untuk dua pembuatan instance tersebut akan dibuat. Artinya, Anda tidak dapat melakukan pembuatan instance baru dari jenis MyGeneric<T> menggunakan konfigurasi jenis yang berbeda. Lihat contoh berikut:

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

Mengimplementasikan antarmuka

Implementasikan antarmuka C dengan memanggil implementInterface() dan menyediakan fungsi callback untuk setiap metode antarmuka. Hanya antarmuka yang dapat diterapkan dengan cara ini; class dan class abstrak tidak didukung. Lihat contoh berikut:

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

Contoh penggunaan:

void onAction1() {
  // Handle action 1
}

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

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

Batasan

Alat wrapper library masih dalam versi beta. Anda mungkin mengalami pembatasan berikut:

Konstruksi Java yang tidak didukung

Wrapper library beta tidak mendukung konstruksi berikut:

  • Overloading metode

    Bahasa C tidak mengizinkan deklarasi dua fungsi dengan nama yang sama. Jika class menggunakan overload metode, kode C yang dihasilkan tidak akan dikompilasi. Solusinya adalah hanya menggunakan satu metode dengan kumpulan parameter yang memadai. Fungsi yang tersisa dapat difilter menggunakan filter. Hal ini juga berlaku untuk konstruktor.

  • Metode dengan template

  • Kolom selain static final int dan static final String

  • Array

Potensi nama yang sama

Karena cara class Java diwakili dalam kode C, mungkin akan terjadi potensi kesamaan nama, yang jarang terjadi. Misalnya, Foo<Bar> class dan Bar class dalam di dalam class Foo direpresentasikan oleh simbol yang sama dalam C: typedef struct Foo_Bar_ Foo_Bar;

Dukungan

Jika Anda menemukan masalah dengan wrapper library, harap beri tahu kami.

Cari bug Laporkan bug
Tim Teknis
Dokumentasi