دليل برنامج تضمين المكتبة

يوضِّح هذا الدليل كيفية استخدام برنامج تضمين مكتبة Android API. تُنشئ أداة سطر أوامر برنامج تضمين المكتبة رمز برنامج تضمين بلغة C لواجهات برمجة تطبيقات Java Android، ما يتيح لك دمج مكتبات Java في تطبيقات Android C/C++ الأصلية. لمزيد من التفاصيل حول برنامج تضمين المكتبة، يُرجى الاطّلاع على برنامج تضمين المكتبة لواجهات برمجة تطبيقات Android.

يوضّح هذا الدليل المفصّل كيفية استخدام أداة التضمين لدمج مكتبة Java في تطبيق Android أصلي. مثلاً، يتناول هذا الدليل عملية دمج مكتبة الإشعارات في حزمة androidx.core.app. راجِع القسم إنشاء إشعار لمعرفة المزيد من المعلومات حول هذه المكتبة.

المتطلّبات الأساسية

يفترض هذا الدليل أن لديك مشروعًا حاليًا على Android أصليًا. كما أنها تستخدم نظام إنشاء Gradle. إذا لم يكن لديك مشروع حالي، يمكنك إنشاء مشروع جديد في "استوديو Android" باستخدام نموذج Native C++.

يستخدم المثال الوارد في هذا الدليل جذر الدليل my_project/. يقع الرمز الأصلي في my_project/app/src/main/cpp/، وهو الدليل التلقائي لمشاريع Android Studio.

إذا لم تكن لديك أداة تضمين المكتبة، يمكنك تنزيل الحزمة وفكّ ضغطها إلى الدليل الذي تختاره. تتطلب أداة CLI هذه بيئة وقت تشغيل Java (JRE).

إنشاء رمز برمجي أصلي

عند دمج مكتبة Java، يمكنك استخدام أداة برنامج تضمين لإنشاء برنامج تضمين للرموز البرمجية الأصلية. تتمثل الخطوة الأولى في تهيئة برنامج تضمين.

إنشاء إعدادات برنامج تضمين

يمكنك إنشاء ملفات إعداد برنامج تضمين المكتبة للتحكم في ناتج منشئ الرمز الأصلي. تتيح لك إحدى ميزات هذا الملف تحديد الفئات والطرق لإنشاء تعليمات برمجية لبرنامج تضمين.

نظرًا لعدم توفّر العديد من الطرق التي تتيح تضمين التفاف في مكتبة الإشعارات، يمكنك تحديدها مباشرةً في القسم 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)"
      ]
    }
  ]
}

في النموذج السابق، أنت تعلن مباشرةً عن فئات Java والطرق التي تتطلب رمز برنامج تضمين أصلي.

تشغيل برنامج تضمين المكتبة

بعد تحديد ملف إعداد برنامج تضمين، ستكون جاهزًا لاستخدام الأداة لإنشاء رمز برنامج تضمين أصلي. افتح وحدة طرفية حيث قمت باستخراج برنامج تضمين المكتبة وتشغيل الأمر التالي:

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

في النموذج السابق، يمكنك استخدام المَعلمة -c لتحديد موقع إعدادات برنامج التغليف، والمَعلمة -o لتحديد دليل الرموز الذي تم إنشاؤه. بعد تشغيل الأداة، من المفترض أن يكون لديك الآن الرمز الذي تم إنشاؤه لاستدعاء واجهة برمجة التطبيقات المستندة إلى Java للإشعارات من تطبيقك الأصلي.

تنفيذ الإشعارات الأصلية

في هذا القسم، يمكنك دمج مكتبة إشعارات Android في تطبيقك الأصلي باستخدام رمز برنامج تضمين الذي أنشأته. تتمثل الخطوة الأولى في تحديث مورد gradle.build على مستوى التطبيق لمشروعك (my_project/app/gradle.build).

تعديل درجات الحرارة في "gradle.build"

  1. "مبادرة أخبار Google" هي مكتبة دعم مطلوبة من خلال رمز برنامج التضمين الذي تم إنشاؤه. يجب أن تشير جميع المشروعات التي تستخدم التعليمة البرمجية التي تم إنشاؤها إلى هذه المكتبة. للإشارة إلى هذه المكتبة، أضِف السطر التالي إلى قسم dependencies في build.gradle:

    implementation 'com.google.android.gms:play-services-gni-native-c:1.0.0-beta2'
    
  2. لتفعيل دعم prefab، أضِف الرمز التالي إلى قسم 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. أضِف مكتبة "مبادرة أخبار Google" إلى ملف 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():

    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 = 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():

    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 =
           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. قم بتحديث دالة ShowNativeNotification() التي أنشأتها سابقًا لاستدعاء CreateNotificationChannel(). أضِف الرمز التالي إلى نهاية ShowNativeNotification():

    C

    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(). من المفترض أن يظهر إشعار بسيط في أعلى شاشة جهاز الاختبار.

إنشاء برامج تضمين من ملفات JAR

في المثال السابق، قمت يدويًا بتعريف فئات وطرق Java التي تتطلب رمزًا برمجيًا أصليًا في ملف تهيئة برنامج تضمين. بالنسبة إلى السيناريوهات التي تحتاج فيها إلى الوصول إلى أقسام كبيرة من واجهة برمجة التطبيقات، من الأفضل توفير واحد أو أكثر من ملفات JAR للمكتبة إلى أداة التضمين. ينشئ برنامج التضمين بعد ذلك برامج تضمين لجميع الرموز العامة التي يعثر عليها في JAR.

يوضح المثال التالي واجهة برمجة تطبيقات Notification API بالكامل من خلال توفير مكتبة JAR.

الحصول على ملفات JAR المطلوبة

واجهة برمجة التطبيقات Notification API هي جزء من حزمة androidx.core، وهي متاحة في مستودع Google Maven. نزِّل ملف المكتبة aar وفكّ ضغطه في دليل من اختيارك. حدِّد موقع ملف classes.jar.

يحتوي ملف classes.jar على العديد من الفئات خارج مكتبة الإشعارات المطلوبة. إذا وفّرت برنامج تضمين المكتبة مع classes.jar فقط، تنشئ الأداة رمزًا برمجيًا أصليًا لكل فئة في JAR، وهي طريقة غير فعّالة وغير ضرورية لمشروعنا. لحل هذه المشكلة، قدِّم ملف فلتر إلى إعداد برنامج تضمين بغرض قصر إنشاء الرمز على فئات إشعارات 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/.

الدعم

إذا واجهتك مشكلة في برنامج تضمين المكتبة، يُرجى إبلاغنا.

تصفُّح الأخطاء الإبلاغ عن خطأ
الهندسة
المستندات