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 intstatic final String 之外的字段

  • 数组

潜在名称冲突

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

支持

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

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