@cxm-2016
2016-11-21T04:14:10.000000Z
字数 8788
阅读 6071
OpenGL-ES
版本:1
作者:陈小默
声明:禁止商业,禁止转载
上一篇:OpenGL-ES 3.0学习指南(三)——JNI操作Bitmap
通常情况下,使用Java编写的Android程序就已经能够满足绝大部分应用场景。不过在上一节我们可以看出,在大量数据处理上来说,Java等语言的效率还是不如更贴近底层的C/C++的。于是Android为对性能要求较高的应用,比如游戏等,保留了使用Native方法的Activity。
native_activity.h是整个NativeActivity的核心文件,该文件定义了Activity的生命周期回调以及事件队列等基本要素。
typedef struct ANativeActivityCallbacks {//第一部分void (*onStart)(ANativeActivity* activity);void (*onResume)(ANativeActivity* activity);void* (*onSaveInstanceState)(ANativeActivity* activity, size_t* outSize);void (*onPause)(ANativeActivity* activity);void (*onStop)(ANativeActivity* activity);void (*onDestroy)(ANativeActivity* activity);/*** 窗口改变时回调。对于游戏应用相当有用,当应用失去焦点时,应该暂停游戏。*/void (*onWindowFocusChanged)(ANativeActivity* activity, int hasFocus);void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect);void (*onConfigurationChanged)(ANativeActivity* activity);//第二部分/*** 可绘制的窗口被创建,可以通过ANativeWindow对象的缓冲区绘制图案。*/void (*onNativeWindowCreated)(ANativeActivity* activity, ANativeWindow* window);/*** 窗口尺寸改变。在不可分屏的设备上无意义。*/void (*onNativeWindowResized)(ANativeActivity* activity, ANativeWindow* window);/*** 窗口重绘。*/void (*onNativeWindowRedrawNeeded)(ANativeActivity* activity, ANativeWindow* window);/*** 绘制窗口被销毁时回调。*/void (*onNativeWindowDestroyed)(ANativeActivity* activity, ANativeWindow* window);//第三部分/*** 输入事件队列被创建时回调。*/void (*onInputQueueCreated)(ANativeActivity* activity, AInputQueue* queue);/*** 输入事件队列被销毁时回调。*/void (*onInputQueueDestroyed)(ANativeActivity* activity, AInputQueue* queue);//第四部分void (*onLowMemory)(ANativeActivity* activity);} ANativeActivityCallbacks;
上面贴出了完整的NativeActivity的回调方法结构体声明,其中主要包含了四个部分:第一部分与普通Activity相同,不再介绍。
第二部分
这一部分方法是绘制图像相关的生命周期方法。ANativeWindow是一个窗口缓冲区对象,后面会进行详细介绍,我们可以使用这个对象进行绘图操作。
第三部分
该部分是事件相关生命周期,在此期间我们可以对获取到的事件进行处理。注意:该方法在主线程中运行,如果我们需要处理事件队列,必须开启一个子线程。
第四部分
第四部分只包含一个回调,就是低内存警告。这是考虑到了NativeActivity的应用场景而设立的。比如在低内存场景下,游戏应用必须能够做出相应的反应。
/*** 该结构体定义了android.app.NativeActivity.对象。该对象由Android框架创建并启动。*/typedef struct ANativeActivity {/*** 声明周期回调方法结构体指针。注意,我们不能修改callbacks的指向,因为这是* 由框架所指定的,但我们可以向其中赋值。*/struct ANativeActivityCallbacks* callbacks;/*** Java虚拟机对象。*/JavaVM* vm;/*** JNI上下文对象,只能在主线程中被访问。*/JNIEnv* env;/*** 这就是NativeActivity对象句柄。** 注意: 这是一个错误的变量名称(我就想知道这货被扣了多少钱的工资)。 该变量本来应该被命名为* 'activity' 而不是 'clazz'。*/jobject clazz;/*** 应用内数据目录路径名。*/const char* internalDataPath;/*** 外部设备存储目录路径名。*/const char* externalDataPath;/*** SDK版本。*/int32_t sdkVersion;/*** 这是一个用户专属变量,不受框架控制,用户可以将一些数据存储到此处,方便在其他地方使用。*/void* instance;/*** Asset Manager对象。用户可以通过此对象访问应用的assets文件。*/AAssetManager* assetManager;/*** 指向应用内OBB文件的目录。如果应用没有OBB文件,此路径可能不存在。*/const char* obbPath;} ANativeActivity;
typedef void ANativeActivity_createFunc(ANativeActivity* activity,void* savedState, size_t savedStateSize);extern ANativeActivity_createFunc ANativeActivity_onCreate;
native_activity.h定义了ANativeActivity_createFunc函数,这个函数是NativeActivity的入口函数,同时也是生命周期中的onCreate方法。ANativeActivity_onCreate声明了入口函数的名称,我们在写入口函数时,其名称必须是ANativeActivity_onCreate。
/*** 与普通Activity中finish()方法相同,当调用此函数时,将会销毁此activity,注意,这个* 函数可以在任何线程被调用。*/void ANativeActivity_finish(ANativeActivity* activity);/*** 更改当前Activity中窗口的图像格式,目前可选 WINDOW_FORMAT_RGBA_8888、WINDOW_FORMAT_RGBX_8888、* WINDOW_FORMAT_RGB_565 ,具体参数请查阅<native_window.h>。此函数可以在任意线程被调用。*/void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format);/*** 添加或移除当前Activity的窗口标记,比如AWINDOW_FLAG_FULLSCREEN等,具体参数请参考<window.h>* 此函数可以在任何线程被调用。*/void ANativeActivity_setWindowFlags(ANativeActivity* activity,uint32_t addFlags, uint32_t removeFlags);enum {/*** 显示软键盘,被打开的键盘可以自动隐藏。*/ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,/*** 显示软键盘,被打开的键盘不能自动隐藏,除非调用相应的隐藏方法。*/ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,};/*** 在当前Activity上打开软键盘。 该函数可以在任何线程被调用。*/void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags);enum {/*** 通知表示可以隐藏软键盘。*/ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,/*** 强制隐藏软键盘。*/ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,};/*** 隐藏当前Activity上打开的软键盘。该函数可以在任何线程被调用。*/void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags);
在Google的官方示例中,其使用了一个胶水层
<android_native_app_glue.h>,在这一层里对NativeActivity进行了封装,然而,这里的封装结果并没有使得NativeActivity的结果清晰,反而使人摸不着头脑。所以我们抛弃这个胶水层,直接使用native_activity.h实现。
创建一个native-activity.h头文件,在其中声明生命周期以及其他必要的函数。
#ifndef NDK_NATIVE_ACTIVITY_H#define NDK_NATIVE_ACTIVITY_H#include <android/native_activity.h>/** 绑定声明周期函数到activity*/void bindLifeCycle(ANativeActivity *activity);/** NativeActivity的入口函数*/void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize);/** 处理事件队列的线程函数。*/void *looper(void *args);void onStart(ANativeActivity *activity);void onResume(ANativeActivity *activity);void *onSaveInstanceState(ANativeActivity *activity, size_t *outSize);void onPause(ANativeActivity *activity);void onStop(ANativeActivity *activity);void onDestroy(ANativeActivity *activity);void onWindowFocusChanged(ANativeActivity *activity, int hasFocus);void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow *window);void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow *window);void onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue);void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue);void onConfigurationChanged(ANativeActivity *activity);void onLowMemory(ANativeActivity *activity);#endif //NDK_NATIVE_ACTIVITY_H
接下来我们需要创建一个native-activity.cpp源文件,然后定义声明中方法,并对声明周期方法进行绑定。声明周期的定义如下,在其中使用LOG打印函数名。
#define LOG_TAG "native-activity"#include "JniUtil.h"#include "native-activity.h"...voidonStart(ANativeActivity *activity) {ALOGE("onStart");}...
接下来我们定义绑定声明周期的函数:
voidbindLifeCycle(ANativeActivity *activity) {activity->callbacks->onStart = onStart;activity->callbacks->onResume = onResume;activity->callbacks->onSaveInstanceState = onSaveInstanceState;activity->callbacks->onPause = onPause;activity->callbacks->onStop = onStop;activity->callbacks->onDestroy = onDestroy;activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;activity->callbacks->onInputQueueCreated = onInputQueueCreated;activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;activity->callbacks->onConfigurationChanged = onConfigurationChanged;activity->callbacks->onLowMemory = onLowMemory;}
接下来实现Activity的入口函数,并在其中调用bindLifeCycle函数
voidANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize) {ALOGE("onCreate");bindLifeCycle(activity);}
通过上面的步骤,这个NativeActivity其实已经可以正常运行了。但是,如果我们需要能够正常接收事件的话,就需要开启一个线程。于是我们需要实现looper函数。
static bool isLoop = false;static pthread_t loopID;void *looper(void *args) {ANativeActivity *activity = (ANativeActivity *) args;AInputQueue *queue = (AInputQueue *) activity->instance;AInputEvent *event = NULL;while (isLoop) {if (!AInputQueue_hasEvents(queue))//判断队列中是否有未处理事件continue;AInputQueue_getEvent(queue, &event);//从队列中获取一个事件switch (AInputEvent_getType(event)) {//判断事件类型case AINPUT_EVENT_TYPE_MOTION: {//触摸事件类型switch (AMotionEvent_getAction(event)) {case AMOTION_EVENT_ACTION_DOWN:{//触摸按下事件float x = AMotionEvent_getX(event, 0);//获得x坐标float y = AMotionEvent_getY(event, 0);//获得y坐标ALOGE("X:%f,Y:%f", x, y);//输出坐标break;}case AMOTION_EVENT_ACTION_UP:{//触摸抬起事件break;}}break;}case AINPUT_EVENT_TYPE_KEY: {//按键事件类型switch (AKeyEvent_getAction(event)) {case AKEY_EVENT_ACTION_DOWN: {//按键按下事件switch (AKeyEvent_getKeyCode(event)) {case AKEYCODE_BACK: {//返回键ANativeActivity_finish(activity);//退出Activitybreak;}}break;}case AKEY_EVENT_ACTION_UP: {//按键抬起事件break;}}}}AInputQueue_finishEvent(queue, event, 1);//将事件从事件队列中移除}return args;}
接下来我们只用在事件队列创建完成后开启线程,然后在事件队列销毁前退出线程循环即可,如果对线程不太了解的,可以参考C++多线程编程(二)——线程操作。
voidonInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) {ALOGE("onInputQueueCreated");isLoop = true;activity->instance = (void *) queue;pthread_create(&loopID, NULL, looper, activity);}voidonInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) {ALOGE("onInputQueueDestroyed");isLoop = false;}
在配置完CMakeList之后,我们需要修改应用的清单配置文件
<activityandroid:name="android.app.NativeActivity"android:configChanges="orientation|keyboardHidden"android:label="@string/app_name"><meta-dataandroid:name="android.app.lib_name"android:value="ndk-lib" /><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>
这里activity的名称是固定的android.app.NativeActivity,meta-data以key-value形式描述该Activity参数,比如name="android.app.lib_name"和value="ndk-lib"指明了这个NativeActivity所在的本地库的库名为ndk-lib,另外,该Activity必须被指定为MainActivity且需要自动启动。