@cxm-2016
2016-11-21T12:14:10.000000Z
字数 8788
阅读 5413
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"
...
void
onStart(ANativeActivity *activity) {
ALOGE("onStart");
}
...
接下来我们定义绑定声明周期的函数:
void
bindLifeCycle(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函数
void
ANativeActivity_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);//退出Activity
break;
}
}
break;
}
case AKEY_EVENT_ACTION_UP: {//按键抬起事件
break;
}
}
}
}
AInputQueue_finishEvent(queue, event, 1);//将事件从事件队列中移除
}
return args;
}
接下来我们只用在事件队列创建完成后开启线程,然后在事件队列销毁前退出线程循环即可,如果对线程不太了解的,可以参考C++多线程编程(二)——线程操作。
void
onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) {
ALOGE("onInputQueueCreated");
isLoop = true;
activity->instance = (void *) queue;
pthread_create(&loopID, NULL, looper, activity);
}
void
onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) {
ALOGE("onInputQueueDestroyed");
isLoop = false;
}
在配置完CMakeList之后,我们需要修改应用的清单配置文件
<activity
android:name="android.app.NativeActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name">
<meta-data
android: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必须被指定为Main
Activity且需要自动启动。