النموذج: نشاط مدمج مع المحتوى

يتوفّر نموذج النشاط الأصلي ضمن جذر عيّنات NDK في المجلد native-activity. وهو مثال بسيط للغاية لتطبيق أصلي تمامًا، بدونه رمز مصدر Java. في حالة عدم وجود أي مصدر جافا، يظل المحول البرمجي لـ Java يُنشئ رمزًا قابلاً للتنفيذ لتشغيل الجهاز الافتراضي. يعمل التلميح كبرنامج تضمين للبرنامج الأصلي، الموجود في ملف .so.

يعرض التطبيق نفسه ببساطة لونًا على الشاشة بأكملها، ثم يغيّر اللون جزئيًا استجابةً للحركة التي يكتشفها.

ملف AndroidManifest.xml

أمّا التطبيق الذي يتضمّن رمزًا برمجيًا أصليًا فقط، فيجب ألا يحدّد مستوى واجهة برمجة تطبيقات 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

بعد ذلك، يسرد المكتبات الخارجية لنظام الإصدار لاستخدامها في إنشاء البرنامج الثنائي. يسبق الخيار -l (رابط مقابل) اسم كل مكتبة.

  • "log" هي مكتبة تسجيل.
  • يتضمَّن android واجهات برمجة تطبيقات دعم Android القياسية لـ NDK. ولمزيد من المعلومات حول واجهات برمجة التطبيقات التي يدعمها Android وNDK، يمكنك الاطّلاع على واجهات برمجة التطبيقات NDK Native Android.
  • يتوافق EGL مع الجزء الخاص بالنظام الأساسي من واجهة برمجة تطبيقات الرسومات.
  • يتوافق GLESv1_CM مع OpenGL ES، وهو إصدار OpenGL ES لنظام التشغيل Android. تعتمد هذه المكتبة على EGL.

لكل مكتبة:

  • ويبدأ اسم الملف الفعلي بـ lib، وينتهي بالامتداد .so. على سبيل المثال، اسم الملف الفعلي لمكتبة log هو liblog.so.
  • تتوفّر المكتبة في الدليل التالي، وهو جذر 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

يخبر السطر الأخير نظام الإصدار ببناء هذه المكتبة الثابتة. يضع النص البرمجي ndk-build المكتبة التي تم إنشاؤها (libandroid_native_app_glue.a) في دليل obj الذي تم إنشاؤه أثناء عملية الإصدار. لمزيد من المعلومات حول مكتبة android_native_app_glue، يمكنك الاطّلاع على عنوان android_native_app_glue.h الخاص بها وملف المصدر .cالمقابل لها.

$(call import-module,android/native_app_glue)

لمزيد من المعلومات حول ملف Android.mk، يمكنك الاطّلاع على Android.mk.

الرئيسي.c

يحتوي هذا الملف بشكل أساسي على البرنامج بأكمله.

يشتمل ما يلي على المكتبات المشتركة والثابتة العددية في 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 الدالة التالية، وتمرّر لها هيكل حالة محدّد مسبقًا. وهو أيضًا بمثابة برنامج تضمين يبسّط التعامل مع استدعاءات NativeActivity.

void android_main(struct android_app* state) {

بعد ذلك، يتعامل البرنامج مع الأحداث الموضوعة في قائمة انتظار مكتبة الغراء. يتبع معالج الحدث هيكل الحالة.

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;

يستعد التطبيق لبدء مراقبة أدوات الاستشعار باستخدام واجهات برمجة التطبيقات في 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. وعند حدوث تطابق، يتم إرسال الرسالة إلى المعالج للتنفيذ.

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);
    }
}