Android實戰技巧之三十五 瞭解native activity
1.native activity的意義
很多人覺得Android的Fwk提供的支援足夠好了,既然Google不推薦用Ndk開發為什麼又放寬Ndk的限制而推出可以無Java開發Android App呢?我的理解是不同的技術實現會有其適合的場景。 Ndk的適用場景官方給出三點:1.平臺間的App移植 2.複用現有庫 3.對軟體效能要求較高的場合比如遊戲等。那麼native activity在十分適合遊戲領域,比如cocos-2dx對其的使用。
2.初步瞭解native activity
藉助SDK提供的NativeActivity類,我們可以建立完全的本地activity而無須編寫Java程式碼。 需要注意的是,即使是無Java程式碼編寫的應用仍然是跑(執行)在自己的虛擬機器中以此與其他應用隔離無不影響。你可以通過JNI的方式呼叫Fwk層的API,有些像sensor、輸入事件等操作可以直接呼叫本地介面(native interfaces)來完成(linc注:這樣才高效嘛)。
有兩種方式可以實現native activity。 1)native_activity.h 2)android_native_app_glue 由於第二種方法啟用另一個執行緒處理回撥和輸入事件,Ndk的例子中就採用了這個實現方式。
3.Ndk自帶的例子
這個程式主要演示根據sensor的檢測結果在整個螢幕上繪製不同的顏色。 AndroidManifest.xml 為了正常使用native activity,我們需要把API級別定在9及以上。
<uses-sdk android:minSdkVersion="9" />
- 1
由於我們只使用native code,將android:hasCode設為false。
<application android:label="@string/app_name" android:hasCode="false">
- 1
宣告NativeActivity類
<activity android:name="android.app.NativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden"> <!-- Tell NativeActivity the name of or .so --> <meta-data android:name="android.app.lib_name" android:value="native-activity" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
注意meta-data中的lib_name(native-activity)要與Android.mk中LOCAL_MODULE相同。
Android.mk 強調模組名稱和原始檔如下:
LOCAL_MODULE := native-activityLOCAL_SRC_FILES := main.c
- 1
- 2
外部庫的依賴 例子中用到了如下幾個外部庫,log、android(為NDK提供的標準Android支援API)、EGL(圖形API)以及OpenGL ES(android用的OpenGL,依賴EGL) 書寫約定為上述庫字首為-l,如下:
LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM
- 1
注意: 實際的庫檔名約定為字首為lib,字尾為.so。比如log庫檔名實際為liblog.so。 這些庫的實際位置:
<ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib /
- 1
比如liblog.so:
$ locate liblog.so/opt/android-ndk-r10b/platforms/android-12/arch-arm/usr/lib/liblog.so/opt/android-ndk-r10b/platforms/android-12/arch-mips/usr/lib/liblog.so/opt/android-ndk-r10b/platforms/android-12/arch-x86/usr/lib/liblog.so/opt/android-ndk-r10b/platforms/android-13/arch-arm/usr/lib/liblog.so...
- 1
- 2
- 3
- 4
- 5
- 6
靜態庫 本例用android_native_app_glue管理NativeActivity生命週期事件:
LOCAL_STATIC_LIBRARIES := android_native_app_glue
- 1
我們需要告知編譯系統去build這個static library,加上如下語句:
$(call import-module,android/native_app_glue)
- 1
原始碼 主要原始碼檔案只有一個,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>
- 1
- 2
- 3
- 4
- 5
- 6
程式的入口是android_main,通過android_native_app_glue調入並傳入一個預定義state結構來管理NativeActivity的回撥。
void android_main(struct android_app* state)
- 1
結構android_app的定義參見/sources/android/native_app_glue/android_native_app_glue.h
接下來程式通過glue庫來處理事件佇列,參考如下程式碼:
struct engine engine; // 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;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
準備sensor
// Prepare to monitor accelerometer 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);
- 1
- 2
- 3
- 4
- 5
- 6
處理訊息迴圈
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; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
佇列為空時,呼叫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); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
編譯 首先通過NDK編譯出so檔案,在根目錄下直接執行ndk-build即可:
$ ndk-build[armeabi-v7a] Compile thumb : native-activity <= main.c[armeabi-v7a] Compile thumb : android_native_app_glue <= android_native_app_glue.c[armeabi-v7a] StaticLibrary : libandroid_native_app_glue.a[armeabi-v7a] SharedLibrary : libnative-activity.so[armeabi-v7a] Install : libnative-activity.so => libs/armeabi-v7a/libnative-activity.so
- 1
- 2
- 3
- 4
- 5
- 6
上述只摘錄出armeabi-v7a一個平臺的編譯log,由於在Application.mk中沒有指明特定平臺,編譯系統會編譯出其他armeabi、x86和mips平臺的so。
然後再編譯apk 我嘗試將工程向AS中匯入,發現沒能配置好gradle編譯環境,無法編譯。 接著我就藉助ant來編譯,參考如下步驟:
$ android update project -p .Updated and renamed default.properties to project.propertiesUpdated local.propertiesNo project name specified, using Activity name 'NativeActivity'.If you wish to change it, edit the first line of build.xml.Added file ./build.xmlAdded file ./proguard-project.txt$ ant debug...[echo] Debug Package: /opt/android-ndk-r10b/samples/native-activity/bin/NativeActivity-debug.apk...BUILD SUCCESSFULTotal time: 3 seconds
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
最後 安裝執行吧!
adb install /opt/android-ndk-r10b/samples/native-activity/bin/NativeActivity-debug.apk
- 1