Android 2.3(API level 9)和Android NDK版本5支持我們使用NativeActivity類來編寫完整的活動和應用,以便能夠訪問Android應用的整個生命週期。
為了利用該方法,在Android manifest文件中引用android.app.NativeActivity。注意,引用中需要包含hasCode屬性。如果應用中沒有Java代碼,該屬性應該設置成false(只是NativeActivity)。但是,在這個例子中,因為包含Java代碼,所以我們把該屬性值設置成true:
<!-- This .apk has Java code, so set hasCode to true which is the default. --> <!-- if this only had a native app (only the activity called \'android.app.NativeActivity\') --> <!-- then set to false --> <application android:icon=\"@drawable/icon\" android:label=\"@string/app_name\" android:hasCode=\"true\" > <activity android:name=\".NDKApp\" android:label=\"@string/app_name\"> <intent-filter> <action android:name=\"android.intent.action.MAIN\" /> <category android:name=\"android.intent.category.LAUNCHER\" /> </intent-filter> </activity> <activity android:name=\"android.app.NativeActivity\" android:label=\"SampleNativeActivity\" android:debuggable=\"true\" > <!-- here we declare what lib to reference --> <meta-data android:name=\"android.app.lib_name\" android: /> </activity> </application>
在這個例子中,使用頭文件android_native_app_glue.h而不使用native_activity.h頭文件,native_activity.h接口基於一組應用的回調函數,當某些事件發生時,Activity的main線程會調用這些回調函數。這表示回調函數不應該阻塞,是強制的。android_native_app_glue.h文件給出輔助庫,它包含不同的執行模式,它的方式是應用在不同的線程中實現自己的主要功能。該功能必須命名為android_main,創建應用時會調用它,並向其傳遞android_app對象。它提供了對應用或activity進行引用的機制,並能夠監聽不同的生命週期事件。
下面這個簡單的nativeactivity示例構建了一個Activity並負責監聽Motion事件。然後,會把Motion事件的x坐標和y坐標值發送給LogCat:
#include <jni.h> #include <android/log.h> #include <android_native_app_glue.h> // usage of log #define LOGINFO(x...) __android_log_print(ANDROID_LOG_INFO,\"SampleNativeActivity\",x) // handle commands static void custom_handle_cmd(struct android_app* app, int32_t cmd) { switch(cmd) { case APP_CMD_INIT_WINDOW: LOGINFO(\"App Init Window\"); break; } } // handle input static int32_t custom_handle_input(struct android_app* app, AInputEvent* event) { // we see a motion event and we log it if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { LOGINFO(\"Motion Event: x %f / y %f\", AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0)); return 1; } return 0; } // This is the function that application code must implement, // representing the main entry to the app. void android_main(struct android_app* state) { // Make sure glue isn\'t stripped. app_dummy; int events; // set up so when commands happen we call our custom handler state->onAppCmd = custom_handle_cmd; // set up so when input happens we call our custom handler state->onInputEvent = custom_handle_input; while (1) { struct android_poll_source* source; // we block for events while (ALooper_pollAll(-1, NULL, &events, (void**)&source) >= 0) { // Process this event. if (source != NULL) { source->process(state, source); } // Check if we are exiting. if (state->destroyRequested != 0) { LOGINFO(\"We are exiting\"); return; } } } }
以下是示例nativeactivity的Android.mk文件。注意它加載並指向android_native_app_glue模塊:
LOCAL_PATH := $(call my-dir) # this is our sample native activity include $(CLEAR_VARS) LOCAL_MODULE := sample_native_activity LOCAL_SRC_FILES := sample_nativeactivity.c LOCAL_LDLIBS := -llog -landroid LOCAL_STATIC_LIBRARIES := android_native_app_glue include $(BUILD_SHARED_LIBRARY) $(call import-module,android/native_app_glue)
以下是當用戶啟動應用時會調用的main Java Android activity。單擊按鈕會啟動我們提供的NativeActivity:
package com.oreilly.demo.android.pa.ndkdemo; import com.oreilly.demo.android.pa.ndkdemo.R; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; public class NDKApp extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); findViewById(R.id.nativeactivity).setOnClickListener( new View.OnClickListener { public void onClick(View v) { startActivity(new Intent(getBaseContext, android.app.NativeActivity.class)); // call nativeactivity } }); } }
如果你編譯並運行過該示例,會注意到啟動本地活動時;屏幕是空白的;如果查看LogCat,會出現各種日誌信息(尤其是當在屏幕上移動手指時)。但是,這不怎麼好玩。因此,為了使界面好看些,我們需要執行一些操作。接下來給這個示例使用了OpenGL ES,可以改變屏幕的顏色。
以下是OpenGL ES的本地源代碼。當顯示活動時,會把屏幕變成亮紅色。
#include <jni.h> #include <android/log.h> #include <android_native_app_glue.h> #include <EGL/egl.h> #include <GLES/gl.h> // usage of log #define LOGINFO(x...) __android_log_print(ANDROID_LOG_INFO,\"NativeWOpenGL\",x) struct eglengine { EGLDisplay display; EGLSurface surface; EGLContext context; }; // initialize the egl engine static int engine_init_display(struct android_app* app, struct eglengine* engine) { const EGLint attribs = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_NONE }; EGLint w, h, dummy, format; EGLint numConfigs; EGLConfig config; EGLSurface surface; EGLContext context; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, 0, 0); eglChooseConfig(display, attribs, &config, 1, &numConfigs); eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(app->window, 0, 0, format); surface = eglCreateWindowSurface(display, config, app->window, NULL); context = eglCreateContext(display, config, NULL, NULL); if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) { LOGINFO(\"eglMakeCurrent FAIL\"); return -1; } eglQuerySurface(display, surface, EGL_WIDTH, &w); eglQuerySurface(display, surface, EGL_HEIGHT, &h); engine->display = display; engine->context = context; engine->surface = surface; glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glEnable(GL_CULL_FACE); glShadeModel(GL_SMOOTH); glDisable(GL_DEPTH_TEST); return 0; } // draw to the screen static void engine_color_screen(struct eglengine* engine) { if (engine->display == NULL) { return; } glClearColor(255, 0, 0, 1); // let\'s make the screen all red glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(engine->display, engine->surface); } // when things need to be terminated static void engine_terminate(struct eglengine* engine) { if (engine->display != EGL_NO_DISPLAY) { eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (engine->context != EGL_NO_CONTEXT) { eglDestroyContext(engine->display, engine->context); } if (engine->surface != EGL_NO_SURFACE) { eglDestroySurface(engine->display, engine->surface); } eglTerminate(engine->display); } engine->display = EGL_NO_DISPLAY; engine->context = EGL_NO_CONTEXT; engine->surface = EGL_NO_SURFACE; } // handle commands static void custom_handle_cmd(struct android_app* app, int32_t cmd) { struct eglengine* engine = (struct eglengine*)app->userData; switch(cmd) { // things are starting up... let\'s initialize the engine and color the screen case APP_CMD_INIT_WINDOW: if (app->window != NULL) { engine_init_display(app, engine); engine_color_screen(engine); } break; case APP_CMD_TERM_WINDOW: // things are ending...let\'s clean up the engine engine_terminate(engine); break; } } // handle input static int32_t custom_handle_input(struct android_app* app, AInputEvent* event) { // we see a motion event and we log it if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { LOGINFO(\"Motion Event: x %f / y %f\", AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0)); return 1; } return 0; } // This is the function that application code must implement, // representing the main entry to the app. void android_main(struct android_app* state) { // Make sure glue isn\'t stripped. app_dummy; // here we add the eglengine to the app struct eglengine engine; memset(&engine, 0, sizeof(engine)); // set engine as userdata so we can reference state->userData = &engine; int events; // set up so when commands happen we call our custom handler state->onAppCmd = custom_handle_cmd; // set up so when input happens we call our custom handler state->onInputEvent = custom_handle_input; while (1) { struct android_poll_source* source; // we block for events while (ALooper_pollAll(-1, NULL, &events, (void**)&source) >= 0) { // Process this event. if (source != NULL) { source->process(state, source); } // Check if we are exiting. if (state->destroyRequested != 0) { LOGINFO(\"We are exiting\"); return; } } } }
sample_native_activity_opengl活動的Android.mk文件會加載EGL和GLESv1_CM庫:
LOCAL_PATH := $(call my-dir) # this is our sample native activity with opengl include $(CLEAR_VARS) LOCAL_MODULE := sample_native_activity_opengl LOCAL_SRC_FILES := sample_nativeactivity_opengl.c # loading the log , android, egl, gles libraries LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM LOCAL_STATIC_LIBRARIES := android_native_app_glue include $(BUILD_SHARED_LIBRARY) $(call import-module,android/native_app_glue)