מדריך wrapper של הספרייה

במדריך הזה נסביר איך להשתמש ב-wrapper של ספריית Android API. הספרייה כלי שורת הפקודה wrapper יוצר קוד wrapper בשפת C ל-Android ב-Java ממשקי API שמאפשרים לשלב ספריות Java באפליקציות מקוריות של C/C++ ל-Android. לפרטים נוספים על ה-wrapper של הספרייה, ראו Library wrapper לממשקי API של Android

המדריך המפורט הזה מדגים איך להשתמש בכלי ה-wrapper כדי לשלב את ספריית Java באפליקציה מקורית ל-Android. לדוגמה, מדריך זה עוסק שילוב ספריית ההתראות של חבילת androidx.core.app. למידע נוסף על הספרייה הזו, אפשר לעיין בקטע יצירת התראה.

דרישות מוקדמות

במדריך הזה יוצאים מנקודת הנחה שיש לכם פרויקט מקורי של Android. כמו כן משתמש במערכת ה-build של Gradle. אם אין לך פרויקט, יוצרים פרויקט עיצוב חדש ב-Android Studio באמצעות תבנית ה-Native C++.

הקוד לדוגמה במדריך הזה משתמש בתיקיית השורש של הספרייה my_project/. מודעות מותאמות הקוד נמצא ב-my_project/app/src/main/cpp/, ספריית ברירת המחדל פרויקטים של Android Studio.

אם עדיין לא התקנתם את כלי ה-wrapper של הספרייה, מורידים ומחלצים את הקובץ חבילה לספרייה לבחירתכם. בכלי ה-CLI הזה נדרש זמן ריצה של Java סביבה (JRE).

יצירת קוד מקורי

במהלך שילוב ספריית Java, צריך להשתמש בכלי wrapper כדי ליצור wrapper של קוד מקורי. השלב הראשון הוא הגדרת ה-wrapper.

יצירת הגדרה של wrapper

אתם יוצרים קובצי תצורה של wrapper ספרייה כדי לשלוט הוא הפלט של מחולל הקוד המקורי. אחת התכונות של הקובץ הזה מאפשרת את המחלקות והשיטות ליצירת קוד wrapper.

מאחר שאין הרבה שיטות שאפשר לגלוש בספריית ההתראות, אפשר להגדיר אותם ישירות בקטע custom_classes. חדש משאב אחד (config.json) בכל מקום בפרויקט כדי להגדיר את ה-methods. לדוגמה, אפשר ליצור את 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 נדרש קוד wrapper מקורי.

הרצת ה-wrapper של הספרייה

כשקובץ התצורה של wrapper מוגדר, אתם מוכנים להשתמש בכלי כדי ליצור ב-Native 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 מבוסס Java להתראות מאפליקציית הנייטיב שלך.

הטמעת התראות מותאמות

בקטע הזה, תשלבו את ספריית ההתראות של Android מותאמת אישית באמצעות קוד wrapper שנוצר. השלב הראשון הוא לעדכן את משאב gradle.build ברמת האפליקציה של הפרויקט (my_project/app/gradle.build).

עדכון של gradle.build

  1. GNI הוא ספריית תמיכה שנדרשת על ידי קוד ה-wrapper שנוצר. כל הפרויקטים באמצעות הקוד שנוצר אמור להפנות לספרייה הזו. כדי לעיין בספרייה הזו: צריך להוסיף את השורה הבאה לקטע 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. הוספה של ספריית 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. הגדרת ערכים קבועים ספציפיים להתראות, וההתראה הפונקציות של ה-handler: 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(). קובץ אמורה להופיע בחלק העליון של המסך של מכשיר הבדיקה.

יצירת wrappers מ-JAR

בדוגמה הקודמת הגדרת באופן ידני מחלקות Java שיטות שדורשות קוד מקורי בקובץ תצורה של wrapper. בתרחישים שבהם צריכים לגשת לחלקים גדולים של ממשק API, כך שיהיה יעיל יותר לספק יותר קובצי JAR של ספריות לכלי wrapper. לאחר מכן ה-wrapper יוצר wrappers עבור את כל הסמלים הציבוריים שהוא מוצא ב-JAR.

בדוגמה הבאה המערכת כוללת את כל ה-Notifications API באמצעות אספקת ספרייה JAR.

קבלת ה-JAR הנדרשים

Notification API הוא חלק מהחבילה androidx.core, שזמינה דרך המאגר של Google Maven. מורידים את קובץ aar בספרייה ופורקים אותו כדי ספרייה לבחירתכם. מאתרים את הקובץ classes.jar.

הקובץ classes.jar מכיל הרבה כיתות מעבר להתראות הנדרשות שלנו לספרייה. אם אתם מספקים את ה-wrapper של הספרייה רק עם classes.jar, הכלי יוצר קוד נייטיב לכל כיתה ב-JAR, והוא לא יעיל אין בו צורך בפרויקט שלנו. כדי לפתור את הבעיה, צריך לספק קובץ סינון הגדרה של wrapper להגבלה של יצירת קוד להתראה של JAR הסוגים.

הגדרת מסנן הרשאה

קובצי סינון הם קובצי טקסט פשוט שאתם מספקים ל-wrapper של הספרייה הגדרה אישית. הם מאפשרים להגדיר אילו מחלקות לכלול (או לא לכלול) מקובצי JAR שסופקו ל-wrapper של הספרייה.

בפרויקט, יוצרים קובץ בשם allowed-symbols.txt ומדביקים השורה הבאה:

androidx.core.app.NotificationCompat*

כשהוא משמש כמסנן הרשאה, הקוד הקודם מציין שרק סמלים ששמה מתחיל ב-androidx.core.app.NotificationCompat מקובצות.

הרצת ה-wrapper של הספרייה

פותחים טרמינל לספריית JAR ומריצים את הפקודה הבאה:

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

הפקודה לדוגמה שלמעלה יוצרת קוד wrapper למחלקות המסוננות לספרייה generated-jar/.

תמיכה

אם יש בעיה עם ה-wrapper של הספרייה, נשמח לדעת.

עיון בבאגים דיווח על באג
הנדסה
מסמכי תיעוד