サンプル: native-activity

サンプル native-activity は、NDK のインストール ルートの下の samples/native-activity 内にあります。これは、Java ソースコードを含まない、純粋なネイティブ アプリケーションのシンプルなサンプルです。Java ソースはありませんが、Java コンパイラは仮想マシンが実行するための実行可能なスタブを作成します。このスタブは実際のネイティブ プログラム(.so ファイルにある)のラッパーの役割を果たします。

このアプリ自体は、画面全体を単純に一色に塗った後、検出した動きに応じて一部の色を変える処理をします。

AndroidManifest.xml

ネイティブ コードのみのアプリでは、NativeActivity フレームワーク クラスを導入した Android API レベル 9 以降を指定する必要があります。

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

このアプリはネイティブ コードのみで Java を含まないため、下記の行では android:hasCodefalse と宣言しています。

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

下記の行は NativeActivity クラスを宣言しています。

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

最後に、マニフェストで android:value を指定します。ここには、ビルドされる共有ライブラリの名前から接頭辞 lib と拡張子 .so を削除したものを指定します。この値は、Android.mk 内の LOCAL_MODULE の名前と同じにする必要があります。

    <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: NDK 用の Android 標準サポート API を含んでいます。Android と NDK がサポートする API の詳細については、Android NDK ネイティブ API をご覧ください。
  • EGL: グラフィック API のプラットフォーム固有の部分に該当します。
  • GLESv1_CM: Android 向けの OpenGL である OpenGL ES に該当します。このライブラリは 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 をご覧ください。

main.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 ライブラリは次の関数を呼び出し、定義済みの state 構造体を渡します。また、NativeActivity コールバックの処理を簡素化するラッパーとしても機能します。

    void android_main(struct android_app* state) {
    

次に、このプログラムは glue ライブラリがキューに入れたイベントを処理します。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 内の API を使用して、センサーのモニタリングを開始する準備を行います。

        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 にメッセージを送信します。android_native_app_glue は、受信したメッセージが android_main で定義された onAppCmd イベントと一致するかどうかを確認します。一致すると、処理を実行するためにハンドラにメッセージが送信されます。

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