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)