راهنمای لفاف کتابخانه

این راهنما نحوه استفاده از بسته بندی کتابخانه API Android را شرح می دهد. ابزار خط فرمان کتابخانه wrapper کد پوشش زبان C را برای APIهای Java Android تولید می کند و به شما امکان می دهد کتابخانه های جاوا را در برنامه های C/C++ اندروید بومی ادغام کنید. برای جزئیات بیشتر در مورد بسته بندی کتابخانه، بسته بندی کتابخانه برای API های Android را ببینید.

این راهنمای گام به گام نحوه استفاده از ابزار wrapper را برای ادغام کتابخانه جاوا در یک برنامه اندرویدی بومی نشان می دهد. به عنوان مثال، این راهنما یکپارچه سازی کتابخانه اعلان بسته androidx.core.app را پوشش می دهد. برای اطلاعات بیشتر در مورد این کتابخانه به ایجاد اعلان مراجعه کنید.

پیش نیازها

این راهنما فرض می کند که شما یک پروژه اندروید بومی موجود دارید. همچنین از سیستم ساخت Gradle استفاده می کند. اگر پروژه موجود ندارید، با استفاده از قالب Native C++ یک پروژه جدید در Android Studio ایجاد کنید.

کد مثال در این راهنما از دایرکتوری root my_project/ استفاده می کند. کد اصلی در my_project/app/src/main/cpp/ ، دایرکتوری پیش‌فرض پروژه‌های Android Studio قرار دارد.

اگر از قبل ابزار بسته بندی کتابخانه را ندارید، بسته را دانلود کرده و در دایرکتوری مورد نظر خود از حالت فشرده خارج کنید. این ابزار CLI به Java Runtime Environment (JRE) نیاز دارد.

کد بومی را تولید کنید

هنگام ادغام یک کتابخانه جاوا، از ابزار wrapper برای تولید یک کد بومی استفاده کنید. اولین مرحله پیکربندی wrapper است.

پیکربندی wrapper را ایجاد کنید

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

از آنجایی که روش های زیادی برای بسته بندی برای کتابخانه اعلان ها وجود ندارد، می توانید آنها را مستقیماً در بخش custom_classes تعریف کنید. یک منبع config.json جدید در هر نقطه از پروژه خود برای تعریف متدها ایجاد کنید. به عنوان مثال، می‌توانید my_project/library_wrapper/config.json را ایجاد کنید و پیکربندی نمونه زیر را جای‌گذاری کنید:

{
  "custom_classes": [
    {
      "class_name": "class java.lang.CharSequence"
    },
    {
      "class_name": "class java.lang.Object",
      "methods": [
        "java.lang.String toString()"
      ]
    },
    {
      "class_name": "class java.lang.String"
    },
    {
      "class_name": "class android.content.Context",
      "methods": [
        "java.lang.Object getSystemService(java.lang.String name)"
      ]
    },
    {
      "class_name": "class android.app.Notification"
    },
    {
      "class_name": "class android.app.NotificationManager",
      "methods": [
        "void createNotificationChannel(android.app.NotificationChannel channel)"
      ]
    },
    {
      "class_name": "class android.app.NotificationChannel",
      "methods": [
        "NotificationChannel(java.lang.String id, java.lang.CharSequence name, int importance)",
        "void setDescription(java.lang.String description)"
      ]
    },
    {
      "class_name": "class androidx.core.app.NotificationCompat"
    },
    {
      "class_name": "class androidx.core.app.NotificationCompat$Builder",
      "methods": [
        "Builder(android.content.Context context, java.lang.String channelId)",
        "androidx.core.app.NotificationCompat$Builder setContentText(java.lang.CharSequence text)",
        "androidx.core.app.NotificationCompat$Builder setContentTitle(java.lang.CharSequence title)",
        "androidx.core.app.NotificationCompat$Builder setSmallIcon(int icon)",
        "androidx.core.app.NotificationCompat$Builder setPriority(int pri)",
        "android.app.Notification build()"
      ]
    },
    {
      "class_name": "class androidx.core.app.NotificationManagerCompat",
      "methods": [
        "static androidx.core.app.NotificationManagerCompat from(android.content.Context context)",
        "void notify(int id, android.app.Notification notification)"
      ]
    }
  ]
}

در نمونه قبلی، شما مستقیماً کلاس‌ها و متدهای جاوا را که به کد Wrapper بومی نیاز دارند، اعلام می‌کنید.

بسته بندی کتابخانه را اجرا کنید

با تعریف فایل پیکربندی wrapper، شما آماده استفاده از ابزار برای تولید کد Wrapper بومی هستید. یک ترمینال را در جایی که wrapper کتابخانه را استخراج کرده اید باز کنید و دستور زیر را اجرا کنید:

java -jar lw.jar \
  -o "my_project/app/src/main/cpp/native_wrappers" \
  -c "my_project/library_wrapper/config.json"

در نمونه قبلی از پارامتر -c برای تعیین محل پیکربندی wrapper و پارامتر -o برای تعریف دایرکتوری کد تولید شده استفاده می‌کنید. پس از اجرای ابزار، اکنون باید کد تولید شده مورد نیاز برای فراخوانی API اعلان‌های مبتنی بر جاوا را از برنامه اصلی خود داشته باشید.

اعلان های بومی را پیاده سازی کنید

در این بخش، کتابخانه اعلان‌های اندروید را با استفاده از کد بسته‌بندی تولید شده خود در برنامه بومی خود ادغام می‌کنید. اولین قدم این است که منبع gradle.build در سطح برنامه پروژه خود را به روز کنید ( my_project/app/gradle.build ).

gradle.build را به روز کنید

  1. GNI یک کتابخانه پشتیبانی است که توسط کد پوشش تولید شده مورد نیاز است. تمام پروژه هایی که از کد تولید شده استفاده می کنند باید به این کتابخانه ارجاع دهند. برای ارجاع به این کتابخانه، خط زیر را به بخش dependencies build.gradle اضافه کنید:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. برای فعال کردن پشتیبانی از پیش ساخته ، کد زیر را به بخش android اضافه کنید:

    buildFeatures {
      prefab true
    }
    
  3. برای پیکربندی cmake ، از پیکربندی cmake زیر در قسمت android/defaultConfig استفاده کنید:

    externalNativeBuild {
      cmake {
          arguments '-DANDROID_STL=c++_shared'
      }
    }
    

پیکربندی تکمیل شده build.gradle شما باید شبیه موارد زیر باشد:

android {
    ...

    buildFeatures {
        prefab true
    }

    defaultConfig {
        ...

        externalNativeBuild {
            cmake {
                arguments '-DANDROID_STL=c++_shared'
            }
        }
    }
}

dependencies {
    ...
    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    ...
}

CMakeLists اصلاح کنید

  1. کتابخانه GNI را به CMakeLists.txt پروژه خود اضافه کنید ( my_project/app/src/main/cpp/CMakeLists.txt ) با افزودن خط زیر در سطح بالای فایل:

    find_package(com.google.android.gms.gni.c REQUIRED CONFIG)
    
  2. خط زیر را به قسمت target_link_libraries اضافه کنید:

    PUBLIC com.google.android.gms.gni.c::gni_shared
    
  3. با افزودن خط زیر در سطح بالای فایل، یک مرجع به کد تولید شده اضافه کنید:

    file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")
    
  4. این خطوط را نزدیک به انتهای فایل اضافه کنید:

    include_directories(./native_wrappers/c)
    include_directories(./native_wrappers/cpp)
    

منبع CMakeLists.txt به روز شده شما باید شبیه نمونه زیر باشد:

cmake_minimum_required(VERSION 3.18.1)

project("my_project")

file(GLOB_RECURSE native_wrappers CONFIGURE_DEPENDS "native_wrappers/*.cpp" "native_wrappers/*.cc")

add_library(
        my_project
        SHARED
        native-lib.cpp
        ${native_wrappers}
        )

find_library(
        log-lib
        log)

find_package(com.google.android.gms.gni.c REQUIRED CONFIG)

target_link_libraries(
        my_project
        PUBLIC com.google.android.gms.gni.c::gni_shared
        ${log-lib})

include_directories(./native_wrappers/c)
include_directories(./native_wrappers/cpp)

پیاده سازی منطق اطلاع رسانی

  1. فایل منبعی را که می‌خواهید قابلیت‌های اعلان را در آن پیاده‌سازی کنید، باز کنید یا ایجاد کنید. در این فایل، فایل هدر gni.h را وارد کنید و یک تابع ShowNativeNotification() جدید تعریف کنید:

    #include "gni/gni.h"
    
    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
      // Get the JavaVM from the JNIEnv.
      JavaVM *java_vm;
      env->GetJavaVM(&java_vm);
    
      // Initialize the GNI runtime. This function needs to be called before any
      // call to the generated code.
      GniCore_init(java_vm, main_activity);
    }
    
  2. مقادیر ثابت مخصوص اعلان را تعریف کنید، و توابع کنترل کننده اعلان ها CharSequenceFromCString() و CreateNotification() :

    سی

    const int32_t IMPORTANCE_HIGH = 4;  // NotificationManager.IMPORTANCE_HIGH
    const int32_t PRIORITY_MAX = 2;  // NotificationCompat.PRIORITY_MAX
    const int32_t NOTIFICATION_ID = 123;  // User defined notification id.
    
    // Convert a C string into CharSequence.
    CharSequence *CharSequenceFromCString(const char *text) {
       String *string = String_fromCString(text);
       // Cast String to CharSequence. In Java, a String implements CharSequence.
       CharSequence *result = GNI_CAST(CharSequence, String, string);
       // Casting creates a new object, so it needs to be destroyed as normal.
       String_destroy(string);
       return result;
    }
    
    // Create a notification.
    Notification *
    CreateNotification(Context *context, String *channel_id,
                       const char *title, const char *content,
                       int32_t icon_id) {
       // Convert C strings to CharSequence.
       CharSequence *title_chars = CharSequenceFromCString(title);
       CharSequence *content_chars = CharSequenceFromCString(content);
    
       // Create a NotificationCompat.Builder and set all required properties.
       NotificationCompat_Builder *notification_builder =
           NotificationCompat_Builder_construct(context, channel_id);
       NotificationCompat_Builder_setContentTitle(notification_builder,
                                                  title_chars);
       NotificationCompat_Builder_setContentText(notification_builder,
                                                 content_chars);
       NotificationCompat_Builder_setSmallIcon(notification_builder, icon_id);
       NotificationCompat_Builder_setPriority(notification_builder,
                                              PRIORITY_MAX);
    
       // Build a notification.
       Notification *notification =
           NotificationCompat_Builder_build(notification_builder);
    
       // Clean up allocated objects.
       NotificationCompat_Builder_destroy(notification_builder);
       CharSequence_destroy(title_chars);
       CharSequence_destroy(content_chars);
    
       return notification;
    }
    

    C++

    const int32_t IMPORTANCE_HIGH = 4;  // NotificationManager.IMPORTANCE_HIGH
    const int32_t PRIORITY_MAX = 2;  // NotificationCompat.PRIORITY_MAX
    const int32_t NOTIFICATION_ID = 123;  // User defined notification id.
    
    // Convert a C string into CharSequence.
    CharSequence *CharSequenceFromCString(const char *text) {
       String *string = String_fromCString(text);
       // Cast String to CharSequence. In Java, a String implements CharSequence.
       CharSequence *result = new CharSequence(string->GetImpl());
       // Casting creates a new object, so it needs to be destroyed as normal.
       String::destroy(string);
       return result;
    }
    
    // Create a notification.
    Notification&
    CreateNotification(Context *context, String *channel_id, const char *title,
                       const char *content, int32_t icon_id) {
       // Convert C strings to CharSequence.
       CharSequence *title_chars = CharSequenceFromCString(title);
       CharSequence *content_chars = CharSequenceFromCString(content);
    
       // Create a NotificationCompat.Builder and set all required properties.
    
       NotificationCompat::Builder *notification_builder = new NotificationCompat::Builder(*context, *channel_id);
       notification_builder->setContentTitle(*title_chars);
       notification_builder->setContentText(*content_chars);
       notification_builder->setSmallIcon(icon_id);
       notification_builder->setPriority(PRIORITY_MAX);
    
       // Build a notification.
       Notification& notification = notification_builder->build();
    
       // Clean up allocated objects.
       NotificationCompat::Builder::destroy(notification_builder);
       CharSequence::destroy(title_chars);
       CharSequence::destroy(content_chars);
    
       return notification;
    }
    

    برخی از توابع کتابخانه اعلان ها CharSequence به جای String می گیرند. تابع CharSequenceFromCString() تبدیل بین این اشیاء را امکان پذیر می کند. تابع CreateNotification() از نسخه پیچیده Java NotificationCompat.Builder برای ایجاد یک اعلان استفاده می کند.

  3. برای ایجاد یک کانال اعلان با چسباندن تابع زیر، CreateNotificationChannel() منطق را اضافه کنید:

    سی

    void CreateNotificationChannel(Context *context, String *channel_id) {
       CharSequence *channel_name = CharSequenceFromCString("channel name");
       String *channel_description = String_fromCString("channel description");
       String *system_service_name = String_fromCString("notification");
       NotificationChannel *channel =
           NotificationChannel_construct(channel_id, channel_name,
                                         IMPORTANCE_HIGH);
       NotificationChannel_setDescription(channel, channel_description);
    
       Object *notification_manager_as_object =
           Context_getSystemService(context, system_service_name);
       NotificationManager *notification_manager =
           GNI_CAST(NotificationManager, Object,
                    notification_manager_as_object);
    
       NotificationManager_createNotificationChannel(notification_manager,
                                                     channel);
    
       CharSequence_destroy(channel_name);
       String_destroy(channel_description);
       String_destroy(system_service_name);
       NotificationChannel_destroy(channel);
       Object_destroy(notification_manager_as_object);
       NotificationManager_destroy(notification_manager);
    }
    

    C++

    void CreateNotificationChannel(Context *context, String *channel_id) {
       CharSequence *channel_name = CharSequenceFromCString("channel name");
       String *channel_description = String_fromCString("channel description");
       String *system_service_name = String_fromCString("notification");
       NotificationChannel *channel =
           new NotificationChannel(*channel_id, *channel_name, IMPORTANCE_HIGH);
       channel->setDescription(*channel_description);
    
       Object& notification_manager_as_object =
           context->getSystemService(*system_service_name);
       NotificationManager *notification_manager =
           new NotificationManager(notification_manager_as_object.GetImpl());
    
       notification_manager->createNotificationChannel(*channel);
    
       CharSequence::destroy(channel_name);
       String::destroy(channel_description);
       String::destroy(system_service_name);
       NotificationChannel::destroy(channel);
       Object::destroy(&notification_manager_as_object);
       NotificationManager::destroy(notification_manager);
    }
    
  4. برای فراخوانی CreateNotificationChannel() ShowNativeNotification() را که قبلا ایجاد کرده بودید به روز کنید. کد زیر را به انتهای ShowNativeNotification() اضافه کنید:

    سی

    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
     // ...
    
     // Create a Context object by wrapping an existing JNI reference.
     Context *context = Context_wrapJniReference(main_activity);
    
     // Create a String object.
     String *channel_id = String_fromCString("new_messages");
    
     // Create a notification channel.
     CreateNotificationChannel(context, channel_id);
    
     // Create a notification with a given title, content, and icon.
     Notification *notification =
         CreateNotification(context, channel_id, "My Native Notification",
                            "Hello!", icon_id);
    
     // Create a notification manager and use it to show the notification.
     NotificationManagerCompat *notification_manager =
         NotificationManagerCompat_from(context);
     NotificationManagerCompat_notify(notification_manager, NOTIFICATION_ID,
                                      notification);
    
     // Destroy all objects.
     Context_destroy(context);
     String_destroy(channel_id);
     Notification_destroy(notification);
     NotificationManagerCompat_destroy(notification_manager);
    }
    

    C++

    void ShowNativeNotification(JNIEnv *env, jobject main_activity, int icon_id) {
       // Get the JavaVM from the JNIEnv.
       JavaVM *java_vm;
       env->GetJavaVM(&java_vm);
    
       // Initialize the GNI runtime. This function needs to be called before any
       // call to the generated code.
       GniCore::Init(java_vm, main_activity);
    
       // Create a Context object by wrapping an existing JNI reference.
       Context *context = new Context(main_activity);
    
       // Create a String object.
       String *channel_id = String_fromCString("new_messages");
    
       // Create a notification channel.
       CreateNotificationChannel(context, channel_id);
    
       // Create a notification with a given title, content, and icon.
       Notification& notification =
           CreateNotification(context, channel_id, "My Native Notification",
                              "Hello!", icon_id);
    
       // Create a notification manager and use it to show the notification.
       NotificationManagerCompat& notification_manager =
           NotificationManagerCompat::from(*context);
       notification_manager.notify(NOTIFICATION_ID, notification);
    
       // Destroy all objects.
       Context::destroy(context);
       String::destroy(channel_id);
       Notification::destroy(&notification);
       NotificationManagerCompat::destroy(&notification_manager);
    }   
  5. با منطق شما تعریف شده است، با فراخوانی ShowNativeNotification() در یک مکان مناسب در پروژه خود، یک اعلان را راه اندازی کنید.

برنامه را اجرا کنید

کدی را که ShowNativeNotification() را فراخوانی می کند کامپایل و اجرا کنید. یک اعلان ساده باید در بالای صفحه نمایش دستگاه آزمایشی شما ظاهر شود.

تولید لفاف ها از JARs

در مثال قبلی، کلاس‌ها و روش‌های جاوا را به صورت دستی تعریف کردید که به کد بومی در یک فایل پیکربندی wrapper نیاز دارند. برای سناریوهایی که نیاز به دسترسی به بخش‌های بزرگ یک API دارید، ارائه یک یا چند JAR کتابخانه به ابزار wrapper کارآمدتر است. سپس wrapper برای تمام نمادهای عمومی که در JAR پیدا می کند، wrapper ایجاد می کند.

مثال زیر کل Notifications API را با ارائه یک JAR کتابخانه ای پیچیده می کند.

JAR های مورد نیاز را دریافت کنید

Notification API بخشی از بسته androidx.core است که از مخزن Google Maven در دسترس است. فایل aar کتابخانه را دانلود کرده و آن را در فهرستی که انتخاب می کنید باز کنید. فایل classes.jar پیدا کنید.

فایل classes.jar شامل کلاس‌های زیادی فراتر از کتابخانه اعلان‌های مورد نیاز ما است. اگر پوشش کتابخانه را فقط classes.jar ارائه کنید، این ابزار کد بومی را برای هر کلاس در JAR تولید می کند که برای پروژه ما ناکارآمد و غیر ضروری است. برای حل این مشکل، یک فایل فیلتر به پیکربندی wrapper ارائه دهید تا تولید کد را به کلاس های اطلاع رسانی JAR محدود کنید.

فیلتر مجاز را تعریف کنید

فایل‌های فیلتر، فایل‌های متنی ساده‌ای هستند که شما به پیکربندی بسته‌بندی کتابخانه خود ارائه می‌دهید. آنها به شما این امکان را می‌دهند که تعریف کنید کدام کلاس‌ها را از فایل‌های JAR ارائه شده به پوشش کتابخانه شامل (یا حذف کنید).

در پروژه خود، یک فایل با عنوان allowed-symbols.txt ایجاد کنید و در خط زیر قرار دهید:

androidx.core.app.NotificationCompat*

هنگامی که به عنوان فیلتر مجاز استفاده می شود، کد قبلی مشخص می کند که فقط نمادهایی که نام آنها با androidx.core.app.NotificationCompat شروع می شود پیچیده شوند.

بسته بندی کتابخانه را اجرا کنید

یک ترمینال را در پوشه JAR باز کنید و دستور زیر را اجرا کنید:

java -jar lw.jar \
 -i classes.jar \
 -o "./generated-jar" \
 -c "./config.json" \
 -fa allowed-symbols.txt \
 --skip_deprecated_symbols

دستور نمونه قبلی کد پوششی را برای کلاس های فیلتر شده شما در دایرکتوری generated-jar/ تولید می کند.

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

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

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