Android API 库封装器 Android 游戏开发套件的一部分。
库封装器是一个命令行工具 (CLI),用于为用 Java 编写的 Android API 生成 C 语言封装器代码。您可以在原生 Android 应用中使用此代码调用 Java API,而无需手动创建 Java Native Interface 或 JNI。此工具可以简化主要用 C 或 C++ 编写的 Android 应用的开发。
此工具的工作原理是,为您提供的 Java Archive (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 |