NDK では C/C++ のコードをそのまま使うことが出来ます。
Windows 上で作っていた lib やアプリケーションを移植し Android 上で
走らせています。
実際に動作するのですが少々問題があって、正しく起動する場合もあれば
アイコンをタッチした瞬間に落ちる場合があります。
Activity が終了しても Process イメージは残り、再び onCreate した時に
常駐している Native コードが再利用される可能性があるからです。
bss/data セグメントが初期化されていないことが原因でしょう。
static 変数は初期値に戻らず global オブジェクトのコンストラクタも走りません。
NVIDIA の資料でアプリ本体を別の dll に分離する手法が紹介されていたので
試してみました。
・GameSauce 2010: Fast and Pretty: Making Responsive, Quality 3D Content on Android
Java Application からロードされるダミー (Thunk) dll を作り、その中で
自分でアプリ本体の dll を load/unload します。
確実に Unload できる点がポイントです。
NDK で複数の Dynamic Link Library (~.so) を build するには下記のようにします。
# Andriod.mak LOCAL_PATH:= $(call my-dir) #-------------------------------------- include $(CLEAR_VARS) LOCAL_SRC_FILES := android_main.cpp ~ LOCAL_MODULE := libappmain LOCAL_CFLAGS := LOCAL_C_INCLUDES:= LOCAL_LDLIBS := -llog -lGLESv2 include $(BUILD_SHARED_LIBRARY) #-------------------------------------- #-------------------------------------- include $(CLEAR_VARS) LOCAL_SRC_FILES := jniproxy.cpp LOCAL_MODULE := libjniproxy LOCAL_CFLAGS := LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) #--------------------------------------
ndk-build で 2つの dll ファイル libappmain.so, libjniproxy.so が作られます。
include $(CLEAR_VARS) は LOCAL_~ の変数を再定義可能にします。
Java 側でロードする dll は libjniproxy.so だけです。
// appmain.java package jp.flatlib.appmain; public class appmain { static { System.loadLibrary( "jniproxy" ); } public static native void init(); public static native void quit(); public static native int render(); ~ }
jniproxy.cpp は libappmain.so のロード管理や呼び出しを行います。
// jniproxy.cpp #include#include #include template void auto_load( T& a, void* ptr ) { a= reinterpret_cast ( ptr ); } #define get_proc_0( image, func_name ) auto_load( proc_##func_name, dlsym( image, #func_name ) ) #define get_proc( func_name ) get_proc_0( LoadApplication, func_name ) //----------------------------------------------------------------------------- static void JNICALL (*proc_app_init)( JNIEnv* env, jobject obj ); static void JNICALL (*proc_app_quit)( JNIEnv* env, jobject obj ); static jint JNICALL (*proc_app_render)( JNIEnv* env, jobject obj ); ~ //----------------------------------------------------------------------------- static void* LoadApplication= NULL; static void InitializeApplication() { // lib 読み込み LoadApplication= dlopen( "/data/data/jp.flatlib.appmain/lib/libappmain.so", RTLD_LAZY ); if( !LoadApplication ){ ~ error } // API 取り出し get_proc( app_init ); get_proc( app_quit ); get_proc( app_render ); ~ } static void FinalizeApplication() { if( LoadApplication ){ dlclose( LoadApplication ); LoadApplication= NULL; } } extern "C" { //----------------------------------------------------------------------------- JNIEXPORT void JNICALL Java_jp_flatlib_appmain_appmain_init( JNIEnv* env, jobject obj ) { if( !LoadApplication ){ InitializeApplication(); proc_app_init( env, obj ); } } JNIEXPORT void JNICALL Java_jp_flatlib_appmain_appmain_quit( JNIEnv* env, jobject obj ) { if( LoadApplication ){ proc_app_quit( env, obj ); FinalizeApplication(); } } JNIEXPORT jint JNICALL Java_jp_flatlib_appmain_appmain_render( JNIEnv* env, jobject obj ) { return LoadApplication ? proc_app_render( env, obj ) : 0; } ~ //----------------------------------------------------------------------------- };
元の android_main.cpp では jni 名を app_~ に変更しておきます。
// android_main.cpp ~ extern "C" { JNIEXPORT void JNICALL app_init( JNIEnv* env, jobject obj ) { } JNIEXPORT void JNICALL app_quit( JNIEnv* env, jobject obj ) { } JNIEXPORT jint JNICALL app_render( JNIEnv* env, jobject obj ) { } ~ };
アプリケーション側で dll の読み込みと終了タイミングを指定します。
スレッドの違いに注意が必要です。
例えば GL API は異なるスレッドから呼び出すことが出来ませんが、
GLSurfaceView.Renderer は別スレッドで動作しています。
Activity から呼び出す場合は GLSurfaceView.queueEvent() を使います。
mView.queueEvent( new Runnable(){ public void run(){ appmain.quit(); } });
現在 Activity の onPause() と GLSurfaceView.Renderer の
onSurfaceChanged() を使っています。
dll の分離はうまくいっており、比較的簡単な追加コードだけで確実に初期化が
行われるようになりました。libjniproxy.so は 4KB 程度。
安定して動いています。
その代わり onPause()/onResume() で unload/load となるので、
端末を Sleep させたり別のアプリに切り替えただけでアプリケーションの
ndk 部は再起動とほぼ同様の状態となります。
関連エントリ
・Android アプリケーションとプロセス