Android API 库包装器   Android 游戏开发套件 的一部分。

库包装器是一个命令行工具 (CLI),用于为用 Java 编写的 Android API 生成 C 语言包装器代码。你可以在原生 Android 应用中使用此代码来调用 Java API,而无需手动创建 Java 本地接口 或 JNI。此工具可以简化主要用 C 或 C++ 编写的 Android 应用的开发。

该工具的工作原理是为你提供的 Java 归档 (JAR) 文件中包含的公共符号或工具配置文件中定义的类(或两者)生成 C 代码。该工具生成的代码不会替换 Java API;相反,它充当 C 代码和 Java 之间的桥梁。你的应用仍然需要将你包装的 Java 库包含在你的项目中。

下载

下载 库包装器归档文件并将其内容解压缩到您选择的目录中。

语法

库包装器工具具有以下命令行语法

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]
参数 描述
-i jar-file-to-be-wrapped 要生成 C 包装器代码的 JAR 文件。可以指定多个 JAR,例如
-i first_library.jar -i second_library.jar...
-o output-path 生成代码的文件系统位置。
-c config-file 库包装器配置文件的文件系统路径。有关详细信息,请参阅 配置部分
-fa allow-list-file 筛选器文件的路径,你可以在其中指定要包装的符号。有关详细信息,请参阅 筛选器 部分。
-fb block-list-file 筛选器文件的路径,其中包含要排除在包装范围之外的符号。有关详细信息,请参阅 筛选器 部分。
--skip_deprecated_symbols 指示包装器工具跳过 @Deprecated 符号。

包装器配置文件

库包装器配置是一个 JSON 文件,允许你控制代码生成过程。该文件使用以下结构。

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

筛选器文件

排除计划包装的 JAR 文件中的一些符号可能很有用。你可以在配置中指定一个筛选器文件来排除符号。筛选器文件是一个简单的文本文件,其中每一行都定义一个要包装的符号。筛选器文件使用以下语法

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

以下是一个示例筛选器文件

# 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

你使用 -fa 参数为 配置 提供一个筛选器文件,指定允许的符号,并使用 -fb 参数指定阻止的符号。这两个参数可以同时使用。如果提供了两个筛选器,则当某个符号在允许筛选器文件中定义且不在阻止筛选器文件中时,将对其进行包装。

示例场景

假设你需要包装 JAR 文件 ChatLibrary.jar,其中包含以下类

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

你的 C 项目需要你为此 JAR 生成一个原生包装器,以便你的原生 Android 应用在运行时调用它。使用以下命令使用库包装器生成此代码

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

上述命令将 C 源代码生成到目录 ./generated_code 中。生成的 chat_manager.h 文件包含类似于以下内容的代码,使你能够在项目中调用库

#include "java/lang/string.h"

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

有关深入的示例场景,请参阅 库包装器指南

工具详细信息

以下部分提供库包装器功能的详细信息。

输出目录结构

所有 C 源文件和头文件都位于反映包装的 Java 类包名称的子目录中。例如,为指定的 JAR java.lang.Integer 生成的包装器代码位于目录 ./java/lang/integer.[h/cc] 中。

你可以使用工具的 配置 文件控制此输出行为。

对象生命周期

Java 对象在 C 代码中表示为不透明指针,称为包装器。包装器管理相应 Java 对象的 JNI 引用。包装器可以在以下情况下创建

  • 通过调用函数 MyClass_wrapJniReference(jobject jobj) 包装现有的 JNI 引用。该函数不会获取提供的引用的所有权,而是创建自己的全局 JNI 引用。
  • 通过创建一个新对象,这等效于在 Java 中调用构造函数:MyClass_construct()
  • 通过从函数返回一个新的包装器,例如:Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

当不再使用所有包装器时,你需要销毁它们。为此,请调用专用的 destroy() 函数 MyClass_destroy(MyClass* instance)

返回包装器的函数为每次调用分配新的内存,即使包装器代表相同的 Java 实例也是如此。

例如,当 Java 方法 Singleton.getInstance() 始终返回相同的实例时,C 端的等效函数将为相同的 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.

处理未引用的类

当在提供的 JAR 中找不到类时,库包装器会创建一个基本实现,该实现由一个不透明指针和以下方法组成

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

代码生成详细信息

运行时,库包装器会根据你提供的 JAR 文件中的公共符号生成 C 代码。生成的 C 代码可能与包装的 Java 代码有所不同。例如,C 不支持 OOP、泛型、方法重载或其他 Java 功能等特性。

反映这些情况的生成的 C 代码可能与 C 开发人员期望的代码类型不同。以下部分中的示例提供了有关工具如何从 Java 代码生成 C 的上下文。注意:在代码片段中,以下示例包含 C/C++ 和 Java 代码片段。这些片段仅用于演示工具如何为每个给定情况生成代码。

类在 C 中表示为 不透明指针

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

不透明指针的实例被称为包装器。包装器工具为每个类生成额外的支持函数。对于前面的示例类MyClass,将生成以下函数

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

构造函数

具有公共或默认构造函数的类使用特殊函数表示

C/C++

MyClass* MyClass_construct(String* data);

Java

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

方法

方法表示为普通函数。函数名称包含原始类名。表示非静态实例方法的函数的第一个参数是指向表示 Java 对象的结构体的指针,该函数代表该对象调用。这种方法类似于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) { ... }
}

内部类

内部类与普通类的表示方式非常接近,只是相应的 C 结构体的名称包含外部类的链式名称

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

内部类方法

内部类方法表示如下

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

泛型类型

库包装器不会直接包装泛型类型。相反,该工具仅为泛型类型实例化生成包装器。

例如,当 API 中存在类MyGeneric<T>,并且此类有两个实例化,例如MyGeneric<Integer>MyGeneric<String>时,将为这两个实例化生成包装器。这意味着您无法使用不同的类型配置创建MyGeneric<T>类型的新实例。请参阅以下示例

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

实现接口

通过调用implementInterface()并为每个接口方法提供回调函数来实现 C 接口。仅以这种方式实现接口;不支持类和抽象类。请参阅以下示例

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

用法示例

void onAction1() {
  // Handle action 1
}

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

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

限制

库包装器工具处于测试阶段。您可能会遇到以下限制

不支持的 Java 结构

库包装器测试版不支持以下结构

  • 方法重载

    C 语言不允许声明两个同名的函数。如果类使用方法重载,生成的 C 代码将无法编译。解决方法是仅使用一个具有足够参数集的方法。可以使用过滤器过滤掉其余函数。这同样适用于构造函数。

  • 模板方法

  • static final intstatic final String之外的字段

  • 数组

潜在的名称冲突

由于 Java 类在 C 代码中的表示方式,在极少数情况下可能会发生名称冲突。例如,类Foo<Bar>Foo类中的内部类Bar在 C 中由相同的符号表示:typedef struct Foo_Bar_ Foo_Bar;

支持

如果您发现库包装器存在问题,请告知我们。

浏览错误 提交错误
工程
文档