דגימת הפעילות המותאמת נמצאת במסגרת
רמה בסיסית של דוגמאות 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); } }