דוגמה: פעילות מותאמת

דגימת הפעילות המותאמת נמצאת במסגרת רמה בסיסית של דוגמאות NDK, בתיקייה native-activity. זוהי דוגמה פשוטה מאוד לייצוג נייטיב לחלוטין ללא קוד מקור ב-Java. בהיעדר מקור Java, מהדר Java עדיין יוצר stub של קובץ הפעלה כדי שהמכונה הווירטואלית תוכל לפעול. ה-stub משמש כ-wrapper של התוכנית המקורית האמיתית, שנמצאת ב-.so חדש.

האפליקציה עצמה פשוט מציגה צבע על כל המסך, ואז משנה את הצבע באופן חלקי בתגובה לתנועה שהוא מזהה.

AndroidManifest.xml

עבור אפליקציה עם קוד נייטיב בלבד, אסור לציין רמת API של Android נמוכה מ-9, שהוכנסה מחלקת המסגרת NativeActivity.

<uses-sdk android:minSdkVersion="9" />

בשורה הבאה מוצהר על android:hasCode בתור false, כי באפליקציה הזו יש רק בלי Java.

<application android:label="@string/app_name"
android:hasCode="false">

בשורה הבאה מוצהר על המחלקה NativeActivity.

<activity android:name="android.app.NativeActivity"

לבסוף, במניפסט מצוין android:value בתור השם של הספרייה המשותפת שרוצים שפותח, פחות הקובץ lib הראשוני והתוסף .so. הערך הזה חייב להיות זהה ל- השם של LOCAL_MODULE ב-Android.mk.

<meta-data android:name="android.app.lib_name"
        android:value="native-activity" />

Android.mk

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

LOCAL_MODULE    := native-activity

לאחר מכן הוא מצהיר על השם של קובץ קוד המקור המקורי.

LOCAL_SRC_FILES := main.c

לאחר מכן, הוא מציג את רשימת הספריות החיצוניות שבהן מערכת ה-build תשתמש בתהליך הפיתוח של הקובץ הבינארי. האפשרות -l (קישור נגד) מופיעה לפני כל שם ספרייה.

  • log היא ספריית רישום ביומן.
  • android כולל את ממשקי ה-API הרגילים לתמיכה ב-Android עבור NDK. מידע נוסף על ממשקי ה-API שנתמכים ב-Android וב-NDK. ראו Android NDK Native ממשקי API.
  • EGL תואם לחלק הספציפי לפלטפורמה של ה-API הגרפי.
  • GLESv1_CM תואמת ל-OpenGL ES, גרסת OpenGL ל-Android. הספרייה הזו תלוי ב-EGL.

לכל ספרייה:

  • שם הקובץ בפועל מתחיל ב-lib ומסתיים ב- תוסף .so. לדוגמה, השם בפועל של הקובץ ספריית log היא liblog.so.
  • הספרייה נמצאת בספרייה הבאה, הרמה הבסיסית (root) של NDK: <ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/
LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM

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

LOCAL_STATIC_LIBRARIES := android_native_app_glue

השורה האחרונה מנחה את מערכת ה-build ליצור את הספרייה הסטטית הזו. הסקריפט ndk-build מציב את הספרייה המובנית (libandroid_native_app_glue.a) בספרייה obj שנוצר בתהליך ה-build. ניתן לקבל מידע נוסף על android_native_app_glue לראות את הכותרת android_native_app_glue.h ואת קובץ המקור .cשלה.

$(call import-module,android/native_app_glue)

מידע נוסף על הקובץ Android.mk זמין בכתובת Android.mk

Main.c

הקובץ הזה מכיל למעשה את כל ה-progam.

הספריות הבאות מתאימות לספריות, גם המשותפות וגם סטטיות, שנספר ב-Android.mk.

#include <EGL/egl.h>
#include <GLES/gl.h>


#include <android/sensor.h>
#include <android/log.h>
#include <android_native_app_glue>

הספרייה android_native_app_glue מפעילה את הפונקציה הבאה: ומעבירים לו מבנה מצב מוגדר מראש. הוא משמש גם כ-wrapper מפשט את הטיפול ב-NativeActivity קריאות חוזרות (callback).

void android_main(struct android_app* state) {

בשלב הבא, התוכנית מטפלת באירועים שממתינים בתור ספריית התגים. האירוע ה-handler תואם למבנה המצב.

struct engine engine;



// Suppress link-time optimization that removes unreferenced code
// to make sure glue isn't stripped.
app_dummy();


memset(&engine, 0, sizeof(engine));
state->userData = &engine;
state->onAppCmd = engine_handle_cmd;
state->onInputEvent = engine_handle_input;
engine.app = state;

האפליקציה מתכוננת להתחיל לעקוב אחר החיישנים, באמצעות ממשקי API ב-sensor.h.

    engine.sensorManager = ASensorManager_getInstance();
    engine.accelerometerSensor =
                    ASensorManager_getDefaultSensor(engine.sensorManager,
                        ASENSOR_TYPE_ACCELEROMETER);
    engine.sensorEventQueue =
                    ASensorManager_createEventQueue(engine.sensorManager,
                        state->looper, LOOPER_ID_USER, NULL, NULL);

בשלב הבא מתחילה לולאה, שבה האפליקציה מודדת את המערכת כדי לאתר הודעות (אירועי חיישנים). היא שולחת הודעות אל android_native_app_glue, שבודק אם הם תואמים כל אירוע onAppCmd שהוגדר ב-android_main. כאשר מתבצעת התאמה, ההודעה נשלחת ל-handler לביצוע הפעלה.

while (1) {
        // Read all pending events.
        int ident;
        int events;
        struct android_poll_source* source;


        // If not animating, we will block forever waiting for events.
        // If animating, we loop until all events are read, then continue
        // to draw the next frame of animation.
        while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL,
                &events,
                (void**)&source)) >= 0) {


            // Process this event.
            if (source != NULL) {
                source->process(state, source);
            }


            // If a sensor has data, process it now.
            if (ident == LOOPER_ID_USER) {
                if (engine.accelerometerSensor != NULL) {
                    ASensorEvent event;
                    while (ASensorEventQueue_getEvents(engine.sensorEventQueue,
                            &event, 1) > 0) {
                        LOGI("accelerometer: x=%f y=%f z=%f",
                                event.acceleration.x, event.acceleration.y,
                                event.acceleration.z);
                    }
                }
            }


        // Check if we are exiting.
        if (state->destroyRequested != 0) {
            engine_term_display(&engine);
            return;
        }
    }

כאשר התור ריק והתוכנה יוצאת מלולאת הסקר, הפונקציה מפעילה OpenGL כדי לצייר את המסך.

    if (engine.animating) {
        // Done with events; draw next animation frame.
        engine.state.angle += .01f;
        if (engine.state.angle > 1) {
            engine.state.angle = 0;
        }


        // Drawing is throttled to the screen update rate, so there
        // is no need to do timing here.
        engine_draw_frame(&engine);
    }
}