پوشش کتابخانه برای API های اندروید بخشی از کیت توسعه بازی اندروید .

بسته بندی کتابخانه یک ابزار خط فرمان (CLI) است که کد بسته بندی زبان C را برای API های اندرویدی که به زبان جاوا نوشته شده اند تولید می کند. می‌توانید از این کد در برنامه‌های اندرویدی بومی برای فراخوانی APIهای جاوا بدون نیاز به ایجاد رابط بومی جاوا یا JNI استفاده کنید. این ابزار می‌تواند توسعه برنامه‌های اندرویدی را که عمدتاً به زبان C یا C++ نوشته شده‌اند، ساده کند.

این ابزار با تولید کد C برای نمادهای عمومی موجود در فایل‌های بایگانی جاوا (JAR) که ارائه می‌کنید، یا کلاس‌های تعریف‌شده در فایل پیکربندی ابزار یا هر دو، کار می‌کند. کد تولید شده توسط ابزار جایگزین API های جاوا نمی شود. در عوض، به عنوان پلی بین کد C و جاوا عمل می کند. برنامه شما همچنان به کتابخانه‌های جاوای شما نیاز دارد که در پروژه شما گنجانده شود.

دانلود کنید

بایگانی بسته بندی کتابخانه را دانلود کنید و محتوای آن را در فهرست انتخابی خود باز کنید.

نحو

ابزار کتابخانه wrapper دارای دستور خط فرمان زیر است:

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 فایل JAR برای تولید کد بسته بندی C. ممکن است چندین 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 به ابزار wrapper دستور می‌دهد از علامت‌های منسوخ @ رد شود.

فایل پیکربندی Wrapper

پیکربندی بسته بندی کتابخانه یک فایل 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 ایجاد کنید، تا برنامه اندروید بومی شما بتواند آن را در طول زمان اجرا فراخوانی کند. این کد را با استفاده از پوشش کتابخانه با دستور زیر ایجاد کنید:

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

دستور قبلی کد منبع C را در دایرکتوری تولید می کند ./generated_code generated_code. فایل ایجاد شده chat_manager.h حاوی کدی شبیه به زیر است که به شما امکان می دهد کتابخانه پروژه خود را فراخوانی کنید:

#include "java/lang/string.h"

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

برای یک سناریوی نمونه عمیق، راهنمای بسته بندی کتابخانه را ببینید.

جزئیات ابزار

بخش های زیر اطلاعات دقیقی از عملکرد لفاف کتابخانه ارائه می دهد.

ساختار دایرکتوری خروجی

همه فایل های منبع و هدر C در زیر شاخه هایی قرار دارند که نام بسته کلاس جاوا پیچیده شده را منعکس می کنند. به عنوان مثال، کد بسته بندی برای JAR مشخص شده java.lang.Integer در دایرکتوری ./java/lang/integer.[h/cc] ایجاد می شود.

شما می توانید این رفتار خروجی را با استفاده از فایل پیکربندی ابزار کنترل کنید.

چرخه حیات شی

اشیاء جاوا در کد C به عنوان نشانگرهای مات نشان داده می شوند که به آنها wrapper می گویند. یک wrapper یک مرجع JNI را برای یک شی جاوا مربوطه مدیریت می کند. یک Wrapper را می توان در حالات زیر ایجاد کرد:

  • با بسته بندی یک مرجع JNI موجود با فراخوانی تابع MyClass _wrapJniReference(jobject jobj) . تابع مالکیت مرجع ارائه شده را نمی گیرد، بلکه مرجع JNI جهانی خود را ایجاد می کند.
  • با ایجاد یک شی جدید، که معادل فراخوانی یک سازنده در جاوا است: MyClass _construct()
  • با برگرداندن یک پوشش جدید از یک تابع، به عنوان مثال: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

شما باید همه لفاف ها را زمانی که دیگر استفاده نمی شوند از بین ببرید. برای انجام این کار، تابع اختصاصی destroy() MyClass _destroy( MyClass * instance) را فراخوانی کنید.

توابعی که wrapper ها را برمی گرداند برای هر تماس یک حافظه جدید برای آنها اختصاص می دهد، حتی اگر wrapper ها همان نمونه جاوا را نشان دهند.

به عنوان مثال، زمانی که روش جاوا Singleton.getInstance() همیشه همان نمونه را برمی گرداند، تابع معادل در سمت C یک نمونه جدید از یک wrapper برای همان نمونه جاوا ایجاد می کند:

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 ارائه شده پیدا کرد، wrapper کتابخانه یک پیاده سازی اساسی متشکل از یک اشاره گر مات و روش های زیر ایجاد می کند:

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

جزئیات تولید کد

هنگام اجرا، پوشش کتابخانه کد C را بر اساس نمادهای عمومی موجود در فایل(های) JAR که ابزار ارائه می کنید تولید می کند. کد C تولید شده ممکن است تفاوت هایی با کد جاوا پیچیده داشته باشد. به عنوان مثال، C از ویژگی هایی مانند OOP، انواع عمومی، بارگذاری بیش از حد روش یا سایر ویژگی های جاوا پشتیبانی نمی کند.

کد C تولید شده منعکس کننده این موقعیت ها ممکن است با نوع کد مورد انتظار توسعه دهندگان C متفاوت باشد. مثال‌های بخش‌های زیر زمینه‌ای را در مورد چگونگی تولید C از کد جاوا ارائه می‌کنند. نکته: در قطعه کد، نمونه های زیر شامل کدهای C/C++ و جاوا هستند. این قطعه‌ها صرفاً برای نشان دادن نحوه تولید کد برای هر موقعیت خاص توسط ابزار طراحی شده‌اند.

کلاس ها

کلاس ها به صورت اشاره گرهای مات در C نشان داده می شوند:

C/C++

typedef struct MyClass_ MyClass;

جاوا

public class MyClass { ... }

نمونه‌هایی از اشاره‌گرهای مات به‌عنوان بسته‌بندی ارجاع داده می‌شوند. ابزار wrapper توابع پشتیبانی اضافی را برای هر کلاس ایجاد می کند. برای کلاس مثال قبلی 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);

جاوا

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

روش ها

روش ها به عنوان توابع عادی نشان داده می شوند. نام یک تابع حاوی نام کلاس اصلی است. توابعی که روش‌های نمونه غیراستاتیک را نشان می‌دهند به عنوان اولین پارامتر یک اشاره‌گر به ساختاری دارند که یک شی جاوا را نشان می‌دهد، که از طرف آن تابع فراخوانی می‌شود. این رویکرد مشابه 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);

جاوا

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;

جاوا

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

روش های کلاس داخلی

متدهای کلاس داخلی به صورت زیر نمایش داده می شوند:

C/C++

bool MyClass_InnerClass_setValue(MyClass_InnerClass* my_class_inner_class_instance, int32_t value);

جاوا

public class MyClass {
  public class InnerClass {
    public boolean setValue(int value) { ... }
  }
}

انواع ژنریک

لفاف کتابخانه به طور مستقیم انواع عمومی را بسته بندی نمی کند. در عوض، این ابزار فقط برای نمونه‌های نوع عمومی، پوشش‌هایی تولید می‌کند.

به عنوان مثال، هنگامی که یک کلاس MyGeneric<T> در یک API وجود دارد، و دو نمونه از این کلاس، مانند 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);

جاوا

public class Result<T> {
  public T getResult();
}

public class DataProcessor {
  public Result<Integer> processIntegerData();
  public Result<Float> processFloatData();
}

پیاده سازی رابط ها

یک رابط C را با فراخوانی implementInterface() و ارائه یک تابع callback برای هر روش رابط پیاده سازی کنید. فقط رابط ها ممکن است به این روش پیاده سازی شوند. کلاس ها و کلاس های انتزاعی پشتیبانی نمی شوند. مثال زیر را ببینید:

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

جاوا

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

محدودیت ها

ابزار بسته بندی کتابخانه در بتا است. ممکن است با محدودیت های زیر مواجه شوید:

ساختارهای جاوا پشتیبانی نشده

بسته بندی بتای کتابخانه از ساختارهای زیر پشتیبانی نمی کند:

  • اضافه بار روش

    زبان C اجازه اعلان دو تابع با نام یکسان را نمی دهد. اگر کلاس از روش اضافه بار استفاده کند، کد C تولید شده کامپایل نمی شود. راه حل این است که فقط از یک روش با مجموعه ای از پارامترهای کافی استفاده کنید. عملکردهای باقی مانده را می توان با استفاده از فیلترها فیلتر کرد. این در مورد سازنده ها نیز صدق می کند.

  • روش های قالب بندی شده

  • فیلدهایی غیر از static final int و static final String

  • آرایه ها

برخورد نام بالقوه

به دلیل نحوه نمایش کلاس های جاوا در کد C، ممکن است در موارد بسیار نادری تداخل نامی وجود داشته باشد. به عنوان مثال، یک کلاس Foo<Bar> و یک کلاس داخلی Bar در داخل یک کلاس Foo با نماد یکسان در C نشان داده می شوند: typedef struct Foo_Bar_ Foo_Bar;

پشتیبانی کنید

اگر مشکلی در بسته بندی کتابخانه پیدا کردید، لطفاً به ما اطلاع دهید.

مرور اشکالات یک اشکال را ثبت کنید
مهندسی
مستندات