讀古今文學網 > 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)