Trình bao bọc thư viện cho API Android   Một phần trong Android Game Development Kit.

Trình bao bọc thư viện là một công cụ dòng lệnh (CLI) có tác dụng tạo mã bao bọc ở ngôn ngữ C cho các API Android được viết bằng Java. Bạn có thể sử dụng mã này trong ứng dụng Android gốc để gọi API Java mà không cần tạo Giao diện gốc Java hoặc JNI theo cách thủ công. Công cụ này có thể đơn giản hoá quá trình phát triển các ứng dụng Android được viết chủ yếu bằng ngôn ngữ C hoặc C++.

Công cụ này hoạt động bằng cách tạo mã C cho các ký hiệu công khai có trong tệp Lưu trữ Java (JAR) mà bạn cung cấp hoặc các lớp được xác định trong tệp cấu hình của công cụ hoặc cả hai. Mã do công cụ này chỉ đóng vai trò là cầu nối giữa mã C và Java chứ không thay thế được các API Java. Bạn vẫn phải đưa các thư viện Java đã gói vào dự án của ứng dụng.

Tải xuống

Tải xuống tệp lưu trữ trình bao bọc thư viện rồi giải nén nội dung của tệp đó vào thư mục bạn chọn.

Cú pháp

Công cụ bao bọc thư viện có cú pháp dòng lệnh sau:

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]
Tham số Mô tả
-i jar-file-to-be-wrapped Tệp JAR để tạo mã trình bao bọc C. Bạn có thể chỉ định nhiều tệp JAR, ví dụ:
-i first_library.jar -i second_library.jar...
-o output-path Vị trí của mã được tạo trong hệ thống tệp.
-c config-file Đường dẫn trong hệ thống tệp đến tệp cấu hình trình bao bọc thư viện. Để biết thông tin chi tiết, hãy xem phần Cấu hình.
-fa allow-list-file Một đường dẫn đến tệp bộ lọc nơi bạn có thể chỉ định các ký hiệu mà công cụ cần bao bọc. Để biết thông tin chi tiết, hãy xem mục Bộ lọc.
-fb block-list-file Đường dẫn đến tệp bộ lọc chứa các ký hiệu không cần bao bọc. Để biết thông tin chi tiết, vui lòng xem mục Bộ lọc.
--skip_deprecated_symbols Lệnh cho trình bao bọc bỏ qua các biểu tượng @Deprecated.

Tệp cấu hình trình bao bọc

Cấu hình trình bao bọc thư viện là một tệp JSON cho phép bạn kiểm soát quá trình tạo mã. Tệp này sử dụng cấu trúc sau.

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

Tệp bộ lọc

Đôi khi bạn cần loại trừ một số ký hiệu khỏi tệp JAR mà bạn định bao bọc. Để loại trừ các biểu tượng, bạn có thể chỉ định tệp bộ lọc trong cấu hình của mình. Tệp bộ lọc là tệp văn bản đơn giản, trong đó mỗi dòng xác định một ký hiệu cần bao bọc. Các tệp bộ lọc sử dụng cú pháp sau:

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

Sau đây là ví dụ về tệp bộ lọc:

# 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

Bạn cung cấp cho cấu hình một tệp bộ lọc, trong đó tham số -fa chỉ định những biểu tượng được phép và tham số -fb chỉ định những biểu tượng bị chặn. Bạn có thể sử dụng đồng thời cả hai tham số. Nếu bạn cung cấp cả hai bộ lọc, biểu tượng sẽ được bao bọc nếu xuất hiện trong tệp bộ lọc cho phép và không xuất hiện trong tệp bộ lọc chặn.

Tình huống ví dụ

Giả sử bạn cần bao bọc tệp JAR ChatLibrary.jar có chứa lớp sau:

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

Dự án C yêu cầu bạn phải tạo một trình bao bọc gốc cho tệp JAR này để cho phép ứng dụng Android gốc gọi trình bao bọc trong thời gian chạy. Tạo mã này bằng cách dùng trình bao bọc thư viện với lệnh sau:

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

Lệnh bên trên tạo mã nguồn C ở thư mục ./generated_code. Tệp chat_manager.h được tạo chứa mã có dạng như sau, cho phép bạn gọi thư viện trong dự án:

#include "java/lang/string.h"

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

Để biết tình huống ví dụ chi tiết, hãy xem bài viết Hướng dẫn về trình bao bọc thư viện.

Chi tiết về công cụ

Các phần sau đây cung cấp thông tin chi tiết về chức năng của trình bao bọc thư viện.

Cấu trúc thư mục đầu ra

Tất cả các tệp tiêu đề và mã nguồn C đều nằm trong các thư mục con phản ánh tên của lớp Java được bao bọc. Ví dụ: mã bao bọc của tệp JAR java.lang.Integer đã chỉ định sẽ được tạo cho thư mục ./java/lang/integer.[h/cc].

Bạn có thể kiểm soát hành vi đầu ra này bằng cách dùng tệp cấu hình của công cụ.

Vòng đời đối tượng

Các đối tượng Java được biểu diễn trong mã C dưới dạng con trỏ mờ, được gọi là trình bao bọc. Trình bao bọc quản lý đối số tham chiếu JNI cho đối tượng Java tương ứng. Bạn có thể tạo trình bao bọc trong các trường hợp sau:

  • Bằng cách bao bọc một đối số tham chiếu JNI hiện có bằng cách gọi hàm MyClass_wrapJniReference(jobject jobj). Hàm không có quyền sở hữu đối số chiếu được cung cấp, nhưng sẽ tạo đối số tham chiếu JNI toàn cục riêng.
  • Bằng cách tạo một đối tượng mới, tương đương với việc gọi một hàm khởi tạo trong Java: MyClass_construct()
  • Bằng cách trả về trình bao bọc mới từ một hàm, ví dụ: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

Bạn cần huỷ mọi trình bao bọc khi không dùng nữa. Để thực hiện việc này, hãy gọi hàm destroy() chuyên biệt MyClass_destroy(MyClass* instance).

Khi trả về trình bao bọc, các hàm sẽ phân bổ một bộ nhớ mới cho mỗi lệnh gọi, ngay cả khi trình bao bọc đại diện cho cùng một thực thể Java.

Ví dụ: khi phương thức Java Singleton.getInstance() luôn trả về cùng một thực thể, hàm tương đương ở phía C sẽ tạo một thực thể mới của trình bao bọc cho cùng một thực thể 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.

Xử lý các lớp không được tham chiếu

Khi không thể tìm thấy lớp trong tệp JAR đã cung cấp, trình bao bọc thư viện sẽ tạo một trường hợp triển khai cơ bản bao gồm một con trỏ mờ và các phương thức sau:

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

Thông tin chi tiết về quá trình tạo mã

Khi chạy, trình bao bọc thư viện sẽ tạo mã C dựa trên các ký hiệu công khai trong (các) tệp JAR bạn cung cấp cho công cụ. Mã C được tạo có thể có những sai khác so với mã Java được bao bọc. Ví dụ: C không hỗ trợ các tính năng như OOP, loại chung, nạp chồng phương thức hoặc các tính năng Java khác.

Mã C được tạo trong những tình huống này có thể khác với kiểu mã mà nhà phát triển C dự kiến. Các ví dụ trong những phần sau đây cung cấp ngữ cảnh về cách công cụ có thể tạo C từ mã Java. Lưu ý: Trong các đoạn mã, những ví dụ sau bao gồm đoạn mã C/C++ và mã Java. Các đoạn mã này chỉ nhằm mục đích minh hoạ cách công cụ này tạo mã cho từng trường hợp cụ thể.

Lớp

Các lớp được biểu thị dưới dạng con trỏ mờ trong C:

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

Các thực thể của con trỏ mờ được tham chiếu dưới dạng trình bao bọc. Công cụ bao bọc tạo ra các hàm hỗ trợ bổ sung cho từng lớp. Đối với lớp MyClass trong ví dụ trên, các hàm sau sẽ được tạo:

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

Hàm khởi tạo

Các lớp có hàm khởi tạo công khai hoặc mặc định sẽ được biểu thị bằng các hàm đặc biệt:

C/C++

MyClass* MyClass_construct(String* data);

Java

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

Phương thức

Các phương thức sẽ được biểu thị bằng hàm thông thường. Tên của hàm sẽ chứa tên lớp gốc. Các hàm biểu thị phương thức của thực thể không ở trạng thái tĩnh sẽ có tham số đầu tiên là con trỏ đến một cấu trúc biểu thị một đối tượng Java, đại diện cho hàm được gọi. Phương pháp này tương tự như con trỏ 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) { ... }
}

Lớp bên trong

Các lớp bên trong sẽ được biểu thị sát với lớp thông thường, ngoại trừ việc tên của cấu trúc C tương ứng chứa tên theo chuỗi của các lớp bên ngoài:

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

Phương thức của lớp bên trong

Các phương thức của lớp bên trong được biểu thị như sau:

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

Kiểu chung

Trình bao bọc thư viện không trực tiếp bao bọc các kiểu chung. Thay vào đó, công cụ này chỉ tạo trình bao bọc cho lệnh tạo thực thể kiểu chung.

Ví dụ: khi một lớp MyGeneric<T> tồn tại trong một API và lớp này có 2 thực thể, chẳng hạn như MyGeneric<Integer>MyGeneric<String>, thì trình bao bọc cho 2 thực thể đó sẽ được tạo. Điều này có nghĩa là bạn không thể tạo thực thể mới của kiểu MyGeneric<T> bằng các cấu hình khác nhau của kiểu. Hãy xem ví dụ sau:

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

Triển khai giao diện

Hãy triển khai giao diện C bằng cách gọi implementInterface() và cung cấp hàm callback cho từng phương thức giao diện. Bạn chỉ có thể triển khai giao diện theo cách này; lớp và lớp trừu tượng không được hỗ trợ. Hãy xem ví dụ sau:

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

Ví dụ về cách sử dụng:

void onAction1() {
  // Handle action 1
}

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

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

Các điểm hạn chế

Công cụ bao bọc thư viện đang ở giai đoạn thử nghiệm. Bạn có thể gặp phải những điểm hạn chế sau:

Cấu trúc Java không được hỗ trợ

Phiên bản beta của trình bao bọc thư viện không hỗ trợ các cấu trúc sau:

  • Nạp chồng phương thức

    Ngôn ngữ C không cho phép khai báo 2 hàm có cùng tên. Nếu lớp sử dụng tính năng nạp chồng phương thức, mã C tạo ra sẽ không được biên dịch. Giải pháp là chỉ sử dụng một phương thức có đủ bộ tham số. Bạn có thể lọc các hàm còn lại bằng bộ lọc. Điều này cũng áp dụng cho các hàm khởi tạo.

  • Phương thức theo mẫu

  • Các trường không phải static final intstatic final String

  • Mảng

Tình trạng xung đột về tên

Do cách biểu thị các lớp Java trong mã C, trường hợp xung đột về tên có thể xảy ra, nhưng rất hiếm. Ví dụ: một lớp Foo<Bar> và một lớp bên trong là Bar ở bên trong lớp Foo được biểu thị bằng cùng một ký hiệu trong C: typedef struct Foo_Bar_ Foo_Bar;

Hỗ trợ

Nếu bạn gặp vấn đề với trình bao bọc thư viện, vui lòng cho chúng tôi biết.

Duyệt xem các lỗi Báo cáo lỗi
Kỹ thuật
Tài liệu