יום שלישי, מרץ 15

NDK


NDK - Native Development Kit. זוהי חבילה המאפשרת שלוב מודולים  הכתובים בשפת C או C++ באפליקציה.  המודולים האלה יתקמפלו ויהיו בסופו של התהליך חלק מקובץ ה-APK.   מדוע זה דרוש? אפשר להצביע על שתי סיבות (לפחות): 
  1. ביצועים - משימות שדורשות פעולת מעבד אינטנסיבית, למשל מסנני DSP, עשויות להתבצע ביעילות רבה יותר כשהמימוש יהיה בשפת C. בכל אופן, שיפור ביצועים ע"י מימוש בשפת C אינו ודאי תמיד.
  2. שימוש בחבילות מוכנות כתובת בשפת C - המצבור הבלתי נדלה של חבילות תוכנה חופשית או לא חופשית שממומש ב-C/Linux הוא בהחלט אטרקטיבי. 
אציין שניתן לפתח על גבי הפלטפורמה ב-C/C++ גם בדרך נוספת: לעקוף את מכונת ה-Dalvik, ולפתח אך ורק מעל הלינוקס. מה החסרונות כאן? 
  1. אין חיבור ל-JAVA. 
  2. צריך קומפיילר מתאים למעבד - בינתיים מדובר ב-ARM. ליתר דיוק צריך Cross Compiler, כלומר קומפיילר עבור ARM שעובד בסביבת INTEL.
    •  הבשורה טובה - חברת CodeSourcery מציעה Cross Compiler עבור ARM לסביבת Windows או Linux, ואולי גם OSX - אני לא זוכר. מדובר ב- Sourcery G++ Lite Edition for ARM. 
    • הבשורה הפחות טובה - המחיר מתחיל ב-300$ עבור הרשיון הפשוט ביותר, ומגיע עד כמה אלפי דולרים.  נתתי להם פרסומת חינם, אבל נחזור ל-NDK החינמי והטוב.
 חבילת ה-NDK החלה להיתמך החל מגרסה 1.5, ומדי גרסת אנדרואיד היא משתנה ומתרחבת . בגרסה 2.3 נוספו תוספות משמעותיות  כמו תמיכה ב-Life Cycle Callbacks כלומר המתודות onCreate, onResume, onPause וכו ניתנות למימוש ב-C גם כן.
מה לא נתמך?
Services  ו-Content Providers לא ניתנות למימוש ב-NDK (בינתיים).


ה"תפירה" בין חבילת ה-C לחבילת ה-Java נעשית ע"י ה-JNI = Java Native Interface
נקודה חשובה לציון בהקשר של NDK- כלל ה-WOCA עבור שפת C כלומר  : "Write Once Compile Anywhere "  תופס גם כאן. משמעות הדבר - דרושה קומפילציה שונה עבור כל סוג של מעבד. זה שונה מהתהליך שבגאווה, שם הכלל הוא - WORA  כלומר Write Once Run Anywhere.  כרגע יש בשוק שני סוגים, והם אפילו בני דודים: 
  1. ארכיטקטורת ARMv5TE
  2. ארכיטקטורת ARMv7-A  עם מעבדי ה-Cortex Ax.

ARM7 תומך בסט הפקודות של ARM5 עם תוספות (בהן ARM5 לא תומך ואם רוצים להשתמש בהו דרושה הבחנה בין המעבדים).

בדוגמאות המצורפות לחבילת ה-NDK (תחת samples),  מודגמת הבחנה בין מעבדים. אולי אטפל בנושא בפוסט נוסף על NDK - נראה...

צפויים להגיע מעבדים מבית אינטל שיגרמו לעניין להיות מורכב יותר. תארו לכם חנות אפליקציה, כשכל לקוח צריך להכיר את המעבד שלו ולהוריד אפליקציה בהתאם. אבל זה לא עד כדי כך נורא, היות שקובץ ה-apk  יכול להכיל את הקומפילציות של כל מעבדי היעד בבת אחת.
נראה את תהליך הבניה ואת הקובץ Application.mk  בו מוגדרים מעבדי היעד, בהמשך.

התקנת ה-NDK פשוטה ביותר ומתבצעת בשני שלבים פשוטים:
  1. הורדת החבילה בהתאם לפלטפורמת הפיתוח - חלונות לינוקס או OSX.
  2. פריסת הקבצים הדחוסים ומיקום החבילה - לא משנה היכן.
הנה הקישור לביצוע ההתקנה הנ"ל. http://developer.android.com/sdk/ndk/index.html

ספריות C הנתמכות

לאחר פריסת חבילת ה-NDK הנ"ל, ניתן למצוא את הספריות הנתמכות תחת:
/android-ndk-r5b/platforms/
שם מסודרות הספריות עבור כל ה-API החל מ-API 3 עד API 9 כלומר עד גרסה 2.3. יש כמובן תאימות מלאה אחורה.
הנה צילום של ה-File Browser על המחשב שלי, עם ה-C Header Files של API 8. זה נמצא ב:
/android-ndk-r5b/platforms/android-8/arch-arm/usr/include/






נכנס לדוגמא לספריית ה-android (ראי בפינה השמאלית העליונה בצילום).
נמצא שם שלושה קבצים. המצלמה כבר בחוץ אז הנה עוד צילום:



נציץ לתוך שלושת הקבצים הנ"ל.
הקובץ הראשון:  api_level.h. 
הקובץ שם את הערך המתאים ל- __ANDROID_API__ :



#ifndef ANDROID_API_LEVEL_H
#define ANDROID_API_LEVEL_H

#define __ANDROID_API__ 8

#endif /* ANDROID_API_LEVEL_H */





הקובץ השני:  log.h.  מכיל את ההגדרות (signitures) של פונקציות ה-logger. בדוגמא נשתמש בהפונקציה המודגשת בכחול.
הנה תוכן הקובץ:





#include <stdarg.h>

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Android log priority values, in ascending priority order.
 */
typedef enum android_LogPriority {
    ANDROID_LOG_UNKNOWN = 0,
    ANDROID_LOG_DEFAULT,    /* only for SetMinPriority() */
    ANDROID_LOG_VERBOSE,
    ANDROID_LOG_DEBUG,
    ANDROID_LOG_INFO,
    ANDROID_LOG_WARN,
    ANDROID_LOG_ERROR,
    ANDROID_LOG_FATAL,
    ANDROID_LOG_SILENT,     /* only for SetMinPriority(); must be last */
} android_LogPriority;

/*
 * Send a simple string to the log.
 */
int __android_log_write(int prio, const char *tag, const char *text);

/*
 * Send a formatted string to the log, used like printf(fmt,...)
 */
int __android_log_print(int prio, const char *tag,  const char *fmt, ...)
#if defined(__GNUC__)
    __attribute__ ((format(printf, 3, 4)))
#endif
    ;

/*
 * A variant of __android_log_print() that takes a va_list to list
 * additional parameters.
 */
int __android_log_vprint(int prio, const char *tag,
                         const char *fmt, va_list ap);

/*
 * Log an assertion failure and SIGTRAP the process to have a chance
 * to inspect it, if a debugger is attached. This uses the FATAL priority.
 */
void __android_log_assert(const char *constnd, const char *tag,
 const char *fmt, ...)    
#if defined(__GNUC__)
    __attribute__ ((noreturn))
    __attribute__ ((format(printf, 3, 4)))
#endif
    ;

#ifdef __cplusplus
}
#endif

#endif /* _ANDROID_LOG_H */










הקובץ השלישי bitmap.h התווסף רק ב-2.2,  כך שמגרסה זו מתאפשר טיפול ב-bitmap בפונקציות C.  הוא מכיל הגדרה של 3 פונקציות - ראה הדגשה בכחול. נדגים שימוש בשלושתן. הנה תוכן הקובץ:



#ifndef ANDROID_BITMAP_H
#define ANDROID_BITMAP_H

#include <stdint.h>
#include <jni.h>

#ifdef __cplusplus
extern "C" {
#endif

#define ANDROID_BITMAP_RESUT_SUCCESS            0
#define ANDROID_BITMAP_RESULT_BAD_PARAMETER     -1
#define ANDROID_BITMAP_RESULT_JNI_EXCEPTION     -2
#define ANDROID_BITMAP_RESULT_ALLOCATION_FAILED -3

enum AndroidBitmapFormat {
    ANDROID_BITMAP_FORMAT_NONE      = 0,
    ANDROID_BITMAP_FORMAT_RGBA_8888 = 1,
    ANDROID_BITMAP_FORMAT_RGB_565   = 4,
    ANDROID_BITMAP_FORMAT_RGBA_4444 = 7,
    ANDROID_BITMAP_FORMAT_A_8       = 8,
};

typedef struct {
    uint32_t    width;
    uint32_t    height;
    uint32_t    stride;
    int32_t     format;
    uint32_t    flags;      // 0 for now
} AndroidBitmapInfo;

/**
 * Given a java bitmap object, fill out the AndroidBitmap struct for it.
 * If the call fails, the info parameter will be ignored
 */
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
                          AndroidBitmapInfo* info);

/**
 * Given a java bitmap object, attempt to lock the pixel address.
 * Locking will ensure that the memory for the pixels will not move
 * until the unlockPixels call, and ensure that, if the pixels had been
 * previously purged, they will have been restored.
 *
 * If this call succeeds, it must be balanced by a call to
 * AndroidBitmap_unlockPixels, after which time the address of the pixels should
 * no longer be used.
 *
 * If this succeeds, *addrPtr will be set to the pixel address. If the call
 * fails, addrPtr will be ignored.
 */
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr);

/**
 * Call this to balanace a successful call to AndroidBitmap_lockPixels
 */
int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap);

#ifdef __cplusplus
}
#endif

#endif



עד כאן ספריות ה-C בהן נשתמש בדוגמא .
לפני שנעבור לדוגמא, נתאר את מרכיבי חבילת הפרויקט וארבעת שלבי הבניה.

שלב 1: יצירת הפרוייקט מתחילה כרגיל. (אני כרגיל מתייחס לסביבת ה-eclipse אך אין זה מזנה כמובן.)
שלב 2. יש להוסיף שתי ספריות: 
  • JNI - תכיל את:
    •  קבצי ה-C
    • קבצי ה.mk:
      • Android.mk, שהוא מעין קובץ makefile, שמגדיר את תהליך הבניה של חבילת ה-C.
      • Application.mk, קובץ אופציונלי, לצורך בניית החבילה למספר מעבדים.
  • libs - צריך לצור directory ריק. הוא יכיל את הספריות הדינמיות - shaerd libraries - שייוצרו בקומפילציה. ספריות אלה מכילות את המודולים בהם השתמש קוד ה-C , והן ישולבו לתוך ה-apk בשלב הקומפילציה השני. אגב, בניגוד לספריה סטטית, הקישור לספריה דינמית נעשה בזמן ריצה. בצילום עץ הקבצים למטה, ניתן להבחין בשתי ספריות שנוצרו תחת libs/, עבור כל אחד משני סוגי המעבדים. 
בתהליך הבניה יווצרו 3 קבצים נוספים: 
buold.xml
local.properties
proguard.cfg.
לא צריך להכיר אותם, לפחות בשלב זה.


הנה צילום של עץ הקבצים של הפרוייקט:






שלב 3: בניית חבילת ה-native. זה נעשה בעזרת הפקודה ndk-build. הקובץ הזה נמצא בספריית ה-ndk. ממולץ להכניס את הספריה ל-PATH.
 לפני כן יש לייצר את הקובץ build.xml עם הפקודה : android update project -p . -s
שלב 4: קומפילצית אנדרואיד רגילה. היא תכלול גם את הספריות הדינמיות שנוצרו בשלב הראשון.





קבצי ה-mk:
הנה תוכן Android.mk לדוגמא:


OCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := sanangeles

LOCAL_CFLAGS := -DANDROID_NDK \
                -DDISABLE_IMPORTGL

LOCAL_SRC_FILES := \
    importgl.c \
    demo.c \
    app-android.c \

LOCAL_LDLIBS := -lGLESv1_CM -ldl -llog

include $(BUILD_SHARED_LIBRARY)




ארבעת האלמנטים המעניינים בקובץ מודגשים בצבע: (מדובר ב-makefile , כך שמי שמכיר אין פה כל חידוש).
  1. LOCAL_MODULE: שם הספריה שתיוצר (בלי הקידומת lib והסיומת .so שיתוספו לשם הקובץ). 
  2. LOCAL_CFLAGS: זו רשימה אופציונלית, מייצרת #define שבתוך תוכנית ה-C אפשר להתייחס אליו ב #ifdef הפורמט: <D<flag name-.  
  3. LOCAL_SRC_FILES:  רשימת קבצי ה-C.
  4. LOCAL_LDLIBS: רשימת הספריות בהן נשתמש. כל ספריה עם קידומת l-.

בהמשך, לצורך המחשה נוספת, אציג את קובץ ה-Android.mk מתוך הדוגמא.


הנה תוכן ה- Application.mk:
APP_ABI := armeabi armeabi-v7a



אם אין צורך לתמוך במעבד נוסף מלבד ARMv5TE או במקרה של ARM7- להשתמש בפקודות שאינן נתמכות ע"י ARM5, אין צורך בשתי קומפילציות ובקיום Application.mk



דוגמא - הפיכת תמונה למונוכרומטית בעזרת פונקציית C


תאורהתהליך:

  • התמונה המקורית נמצאת ב- res/drawable. היא תשלף משם ע"י הג'אווה ותוצג על המסך.
  • בכל נגיעה בתמונה, היא תשלח לתוכנית ה-C, ושם יתבצע סינון שישאיר רק מרכיב צבע אחד מתוך צבעי הבסיס,ה-RGB.
  • תמונה שסוננה ע"י ה-C מועברת בסוף התהליך חזרה ל-JAVA שמציג אותה על המסך. 
כך זה נראה על המסך:

התמונה המקורית.






לאחר נגיעה על המסך, התמונה באדום:






עוד נגיעה על התמונה, יוצג הרכיב הירוק:



נגיעה נוספת תגרום להצגת רכיב הצבע הכחול:





נעבור להסתכל על הקוד.


נלך מלמעלה למטה, נתחיל ב-layout. אין כאן שום דבר מיוחד, מלבד ImageView widget.
main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
  
    >
<ImageView android:id="@+id/imageview"
    android:layout_width="wrap_content
    android:layout_height="wrap_content
   android:gravity="center"
      />

</LinearLayout>







בקובץ המניפסט אין שום דבר מיוחד גם כן, מלבד NdkBmpActivity   שהוא ה-Java class היחידי .
הנה ה-manifest.xml:



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.ahs.ndkbmp"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name"
   >
    >
        <activity android:name=".NdkBmpActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
       
        
    </application>
    <uses-sdk android:minSdkVersion="8" />

</manifest> 



והנה הגאווה:



  1. import android.app.Activity;
  2. import android.graphics.Bitmap;
  3. import android.graphics.Bitmap.Config;
  4. import android.graphics.BitmapFactory;
  5. import android.os.Bundle;
  6. import android.view.MotionEvent;
  7. import android.view.View;
  8. import android.widget.ImageView;
  9.  
  10. public class NdkBmpActivity extends Activity {
  11. protected ImageView imageView = null;
  12. final private int NUM_OF_COLOR = 3;
  13. public native int bmplay(int colorCommmand, Bitmap bitmapcolor, Bitmap gray, String outputTitleText);
  14. static {
  15. System.loadLibrary("bmplay");
  16. }
  17.    private Bitmap    bitmapSource;
  18.      int inputColor;
  19.      @Override
  20.    public void onCreate(Bundle savedInstanceState) {
  21.      super.onCreate(savedInstanceState);
  22.        setContentView(R.layout.main);
  23.        BitmapFactory.Options bitMapFactory = new BitmapFactory.Options();
  24.         bitMapFactory.inPreferredConfig = Bitmap.Config.RGB_565;
  25.        bitmapSource = BitmapFactory.decodeResource( getResources() ,R.drawable.bg_android, bitMapFactory);
  26.        imageView = (ImageView) this.findViewById(R.id.imageview);
  27.          imageView.setImageBitmap(bitmapSource);
  28.        imageView.setOnTouchListener(new View.OnTouchListener(){
  29.      String outputText;
  30.          public boolean onTouch(View view, MotionEvent me){
  31.          int bmpWidth = bitmapSource.getWidth();
  32.          int bmpHeight = bitmapSource.getHeight(); 
  33.          Bitmap bmpTarget = Bitmap.createBitmap(bmpWidth, bmpHeight, Config.RGB_565);
  34.          inputColor= (inputColor+1)%NUM_OF_COLOR;
  35.          bmplay(inputColor,bitmapSource,bmpTarget, outputText);
  36.          imageView.setImageBitmap(bmpTarget);  
  37.         return true;
  38.          }});
  39.       }




נתמקד תחילה באלמנטים הקשורים ל-NDK. מדובר בשלושה אלמנטים:
  1. ה-signiture של פונקציית ה-C - בשורה 13.  התוספת היחודית כאן הוא התוספת native לפני שם הפונקציה. (מה המונח העברי של function signiture?).
  2. הפקודה לטעינת ספרית  ה-NDK - בשורה 14. הערה: שם הספריה נקבע בקובץ Android.mk.
  3. הקריאה לפונקציה של ה-NDK - שורה 36.
זהו. מלבד שלושת האלמנטים הנ"ל, מדובר ב-Activity רגיל.


נכנס לקוד:  

  • המתודה היחידה היא ה-onCreate.
  •  חלקה הראשון של המתודה מכין את קובץ התמונה.
  •  החלק השני מגדיר את  ה-onTouch callback שיופעל עם כל נגיעה בתמונה. כפי שהזכרתי בתאור הכללי, כל נגיעה בתמונה תפעיל את תוכנית ה-C.

הנה החלק הראשון של onCreate שוב - קוד מאד דומה הצגנו בפוסט של זהוי פנים:




  1.         setContentView(R.layout.main);
  2.         BitmapFactory.Options bitMapFactory = new BitmapFactory.Options();
  3.         bitMapFactory.inPreferredConfig = Bitmap.Config.RGB_565;
  4.         bitmapSource = BitmapFactory.decodeResource( getResources() ,R.drawable.bg_android, bitMapFactory);
  5.         imageView = (ImageView) this.findViewById(R.id.imageview);
  6.          imageView.setImageBitmap(bitmapSource);




שורה 1: פריסת ה-layout.
שורה 2: יצירת אובייקט מסוג  BitmapFactory.Options. משמש להגדרת אופציות שונות אותן נרצה לקבוע עבור ה-bitmap. ראה שורה 3.
שורה 3: העדפה של פורמט 565. התוכנית שלנו למעשה תכיר רק פורמט זה, בו כל פיקסל מיוצג ע"י 16 ביט: 5 עבור האדום, 6 עבור הירוק ו-5 עבור הכחול. הערה - לא ננקטו אמצעי הגנה למקרה שה-input אינו בפורמט המתאים. באפליקציה יש צורך לבדוק את כל מצבי הכשל.
שורה 4: יצירת אובייקט ה-bitmap. התמונה  המקורית נמצאת בקובץ bg_android.jpeg.
 שימו לב, getResources מביא instance של ה-resources שדרוש עבור טעינת התמונה משם.
שורה 5: שליפת  את הווידגט כלומר view מתוך ה-R class.
שורה 6: הצגת התמונה.

גמרנו את החלק הראשון, הוא חלק ההכנות. נשאר כעת רק להגדיר את ה-onTouch callback. הנה הקטע שוב:




  1.      imageView.setOnTouchListener(new View.OnTouchListener(){
  2.      String outputText;
  3.          public boolean onTouch(View view, MotionEvent me){
  4.             int bmpWidth = bitmapSource.getWidth();
  5.             int bmpHeight = bitmapSource.getHeight(); 
  6.             Bitmap bmpTarget = Bitmap.createBitmap(bmpWidthbmpHeight, Config.RGB_565);
  7.             inputColor= (inputColor+1)%NUM_OF_COLOR;
  8.             bmplay(inputColor,bitmapSource,bmpTarget, outputText);
  9.            imageView.setImageBitmap(bmpTarget);  
  10.         return true;
  11.          }});



שורות 1-6: יצירת ה-bitmap שישמש את פונקציית ה-C לבניית התמונה החדשה (bmpTarget).
שורה 4-5: קביעת המימדים של ה-bmpTarget על פי התמונה המקורית - bitmapSource.
שורה 6: יצירת ה-bitmap בפורמט RGB_565.


שורה 7: זה הפרמטר שישלח לפונקציית ה-C ויקבע את הצבע של התמונה: 0-2 ייצר אדום, ירוק, כחול.
שורה 8: קריאה לפונקצית ה- C, ה-bmplay.  היא מקבלת ארבעה פרמטרים:
  1. inputColor: מקבל ערכים 0-2, על פיהם יקבע צבע התמונה.
  2. bitmapSource: זוהי התמונה המקורית. ה-input.
  3. bmpTarget: - זוהי תבנית ה-bmp של התמונה המונוכרומטית  שתתקבל כ-output.
  4. outputText - למעשה לא בשימוש. אבל כבר השארתי את זה בתור דחליל.

שורה 9: הצגת התמונה שהתקבלה - המונוכרומטית.

עד כאן הג'אווה.

נעבור לפונקציית ה-C:

bmp_play.c:




  1. #include <jni.h>
  2. #include <android/log.h>
  3. #include <android/bitmap.h>


  4. #define  LOG_TAG    "bmplay"
  5. #define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
  6. #define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

  7. typedef struct {
  8.     unsigned int  red : 5;
  9.     unsigned int  green : 6;
  10.     unsigned int  blue : 5;
  11.     }rgb565;
  12.  
  13.     JNIEXPORT jstring JNICALL Java_com_ahs_ndkbmp_NdkBmpActivity_bmplay(JNIEnv * env, jobject  obj, jint commandIndex, jobject srcBmp,jobject targetBmp, jstring outputText)
  14. {
  15.     AndroidBitmapInfo srcBmpInfo;
  16.     void*               srcPixelPtr; 
  17.     AndroidBitmapInfo   targetBmpInfo;
  18.     void*               targetPixelPtr;
  19.     int lineIndx;
  20.     int             colIndex;
  21.     #define RED 0
  22.     #define GREEN 1
  23.     #define BLUE 2
  24.     #define NUM_OF_COLORS 3
  25.     
  26.     AndroidBitmap_getInfo(env, srcBmp, &srcBmpInfo);
  27.     AndroidBitmap_getInfo(env, targetBmp, &targetBmpInfo);
  28.     if (ANDROID_BITMAP_FORMAT_RGB_565 != srcBmpInfo.format){
  29.         LOGE("Error!! Format must be RGB_565.");
  30.        return;
  31.     }
  32.     AndroidBitmap_lockPixels(env, srcBmp, &srcPixelPtr);
  33.     AndroidBitmap_lockPixels(env, targetBmp, &targetPixelPtr);
  34. //    int commandIndex = rand()%NUM_OF_COLORS;
  35.     for (lineIndx=0;lineIndx<srcBmpInfo.height;lineIndx++){
  36.      rgb565 * srcMovingPixelPtr = (rgb565 *) srcPixelPtr;
  37.      rgb565 * targetMovingPixelPtr = (rgb565 *) targetPixelPtr;
  38.      for (colIndex=0;colIndex<srcBmpInfo.width;colIndex++){
  39.      switch(commandIndex){
  40.      case RED:
  41.        targetMovingPixelPtr->red = srcMovingPixelPtr->red;
  42.          targetMovingPixelPtr->green = 0;
  43.          targetMovingPixelPtr->blue = 0;
  44.      break;
  45.      case GREEN:
  46.        targetMovingPixelPtr->red = 0;
  47.          targetMovingPixelPtr->green = srcMovingPixelPtr->green;
  48.          targetMovingPixelPtr->blue = 0;
  49.     break;
  50.      case BLUE:
  51.        targetMovingPixelPtr->red = 0;
  52.          targetMovingPixelPtr->green = 0;
  53.          targetMovingPixelPtr->blue = srcMovingPixelPtr->blue;
  54.      break;
  55.      }
  56.      srcMovingPixelPtr++;
  57.      targetMovingPixelPtr++;
  58.      }
  59.      srcPixelPtr = (char *)srcPixelPtr + srcBmpInfo.stride;
  60. targetPixelPtr = (char *) targetPixelPtr + targetBmpInfo.stride;
  61.      }
  62.     AndroidBitmap_unlockPixels(env, srcBmp);
  63.     AndroidBitmap_unlockPixels(env, targetBmp);
  64.        return(outputText);
  65. }



תחילה אצהיר בזאת שהקוד הנ"ל בהחלט לא כתוב בצורה אופטימלית או יעילה מבחינת ביצועים או כל בחינה אחרת. 
 נתרכז באלמנטים המיוחדים ל-JNI כלומר Java Native Interface.



Header Files שורות 1-3:

  1. jni.h - אותו צריך תמיד להכליל. מכיל בין היתר מיפוי בין types ב-c ל-ג'אווה.
  2. log.h הוצגה כבר למעלה. כוללת את ספריית ה-logger.
  3. bitmap.h - גם היא הוצגה והפונקציות בהן נשתמש הודגשו. נקרה את השימוש בהן בקרוב.
 שורות 6-8: מקרואים (Macros) שימושיים עבור הלוגר  מומלץ לנוחיות כותב התוכנה.

שורה 16:
שם הפונקציה, השם בנוי לפי סטנדרט ה-JNI. כאן יש מספר קונבנציות שחשוב להזכיר:

 JNIEXPORT jstring JNICALL Java_com_ahs_ndkbmp_NdkBmpActivity_bmplay(JNIEnv * env, jobject  obj, jint commandIndex, jobject srcBmp,jobject targetBmp, jstring outputText)
{


נסקור את מרכיבי השורה הנ"ל: 

1. TYPE: 

 JNIEXPORT jstring JNICALL:
הפונקציה מחזירה string. (מפוי ה-JNI של string הוא jstring ) הערה: את המיפויים בין Primitive Types  לבין JNI Types הנ"ל אפשר למצוא ב-jni.h.
שני הפרמטרים האחרים בשורה הנ"ל - חייבים להיות.

2. קידומת שם הפונקציה: בנויה מ:
  • java'
  • שם ה-package,
  •  שם ה-Activity ושם הפונקציה עצמה.
 החלקים מופרדים בקוים תחתיים. אזהרה: לשם הפונקציה המקורי אסור  לכלול קוים תחתיים!!! 

3. רשימת הפרמטרים

שני הפרמטרים הראשונים משותפים לכל הפונקציות של ה- JNI: 


א. JNIEnv * env - מצביע לסביבת הגאווה. אפשר למצוא את ההגדרה שלו ה-jni.h. הוא ישמש כפרמטר בפונקציות JNI כפי שנראה בהמשך.

ב.  jobject  obj - זה מצביע על this. 


ארבעת הפרמטרים האחרונים תואמים את ה-signature שראינו בקובץ הג'אווה, אלא ששמות ה-types ממופים לשמות בJNI:

  • int ממופה ל-jint.
  • string ל-jstring.
  • ה- Bitmap type מופה ל-object שהוא למעשה void* - ראה jni.h.




עד כאן שורת שם הפונקציה והפרמטרים. כעת נתבונן בתוכנית עצמה.


הלופ העיקרי של התוכנית, שורות 38 -64, סורק את השורות, ובהתאם ל-commandIndex, מוחק שני צבעים ומשאיר צבע אחד כמו שהוא.- ה-switch בשורה 42.


נשים לב לפונקציות של ספריית Bitmap. בדוגמא משתמשת  בכל שלוש הפונקציות הקיימות בספריה, שהן:

1. שליפת האינפורמציה מה-bitmap:


    AndroidBitmap_getInfo(env, srcBmp, &srcBmpInfo);

האינפורמציה כוללת:
פורמט - אנו מצפים לRGB_565  בלבד.
מימדי אורך ורוחב של התמונה.
stride - גודל הקפיצה בזיכרון בין השורות.



2. נעילת הפיקסלים וקביעת pointer על ה-pixel buffer. צריך להשאיר את זה במצב נעול עד שפעולת ה-pointerעל ה-buffer מסתיימת:


    AndroidBitmap_lockPixels(env, srcBmp, &srcPixelPtr);


3. שחרור הנעילה:

    AndroidBitmap_unlockPixels(env, srcBmp);


שימו לב שבכל שלושת הפונקציות, הפרמטר הראשון הוא ה-env שהזכרנו במסגרת הדיון על הפרמטרים של הפונקציה הראשית. הוא מצביע על סביבת הגאווה. 







תהליך הבניה\קומפילציה


שלב ראשון - בניית חבילת ה-C. התוצרים נשמרים בספריית libs. כתבתי סקריפט פשוט בשם my_script.. הוא נמצא בספריה הראשית - ראה למטה קישור לקבצי הפרויקט.
יש בו שתי פקודות:

android update project -p . -s
ndk-build



את הבנייה אני מבצע כרגע מתוך ה-shell ( בחלונות - חלון command), ולא מתוך ה-eclipse. אני חושב שאפשר לבנות הכל אוטומטית ב-eclipse. "כמעט" הצלחתי לעשות את זה, שרפתי כמה שעות על העניין, ובינתיים עזבתי את זה. אשמח לשמוע עצות בנושא.


ועוד חוב קטן - הנה קובץ הAndroid.mk:






LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := bmplay
LOCAL_SRC_FILES := bmp_play.c
LOCAL_LDLIBS    := -llog -ljnigraphics

include $(BUILD_SHARED_LIBRARY)









למעשה כבר הצגתי קובץ Android.mk למעלה, אך בואו נתעכב על הכנסת הספריות - ראה הדגשה בצהוב.
בפרויקט השתמשנו בשתי ספריות: logger ו-bmp.


אבל השאלה שצריכה להשאל היא - איך אפשר לדעת מה שם הספריה של  ה-API? במקרה הנ"ל למשל, איך נדע ש-jnigraphics היא הספריה שכוללת את ה-Bitmap API?  
התשובה: צריך לחפש בקובץ: android-ndk-r5b/docs/STABLE-APIS.html.
שם מפורטים ה-API ושמות הספריות שלהם.


שלב שני  - בניית הגאוה כרגיל.









לסיום, אפשר למצוא תעוד על ה-NDK בספריית docs תחת ספריית ה-NDK. אפשר למצוא דוגמאות מצוינות תחת samples + הסברים נוספים באתר המפתחים הרשמי.







2 תגובות:

  1. תודה רבה, עזרתי לי מאוד.

    השבמחק
  2. היי רונן,
    כרגיל עוד מדריך מעולה.
    הנה מדריך לאיך לקמפל את הקוד JNI ישירות מה ECLIPSE. לי זה עבד טוב.
    http://mobilepearls.com/labs/ndk-builder-in-eclipse/

    השבמחק