名著阅读 > Android程序设计:第2版 > 本地Activity >

本地Activity

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)