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 int
和static final String
之外的字段数组
潜在的名称冲突
由于 Java 类在 C 代码中的表示方式,在极少数情况下可能会发生名称冲突。例如,类Foo<Bar>
和Foo
类中的内部类Bar
在 C 中由相同的符号表示:typedef struct Foo_Bar_ Foo_Bar;
支持
如果您发现库包装器存在问题,请告知我们。
浏览错误 | 提交错误 |
---|---|
工程 | bug_report |
文档 | bug_report |