[关闭]
@cxm-2016 2016-11-21T12:14:10.000000Z 字数 8788 阅读 5413

OpenGL-ES 3.0学习指南(四)——NativeActivity

OpenGL-ES

版本:1
作者:陈小默
声明:禁止商业,禁止转载

发布于作业部落简书


上一篇:OpenGL-ES 3.0学习指南(三)——JNI操作Bitmap



六、NativeActivity

通常情况下,使用Java编写的Android程序就已经能够满足绝大部分应用场景。不过在上一节我们可以看出,在大量数据处理上来说,Java等语言的效率还是不如更贴近底层的C/C++的。于是Android为对性能要求较高的应用,比如游戏等,保留了使用Native方法的Activity。

6.1 native_activity.h

native_activity.h是整个NativeActivity的核心文件,该文件定义了Activity的生命周期回调以及事件队列等基本要素。

6.1.1 生命周期回调

  1. typedef struct ANativeActivityCallbacks {
  2. //第一部分
  3. void (*onStart)(ANativeActivity* activity);
  4. void (*onResume)(ANativeActivity* activity);
  5. void* (*onSaveInstanceState)(ANativeActivity* activity, size_t* outSize);
  6. void (*onPause)(ANativeActivity* activity);
  7. void (*onStop)(ANativeActivity* activity);
  8. void (*onDestroy)(ANativeActivity* activity);
  9. /**
  10. * 窗口改变时回调。对于游戏应用相当有用,当应用失去焦点时,应该暂停游戏。
  11. */
  12. void (*onWindowFocusChanged)(ANativeActivity* activity, int hasFocus);
  13. void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect);
  14. void (*onConfigurationChanged)(ANativeActivity* activity);
  15. //第二部分
  16. /**
  17. * 可绘制的窗口被创建,可以通过ANativeWindow对象的缓冲区绘制图案。
  18. */
  19. void (*onNativeWindowCreated)(ANativeActivity* activity, ANativeWindow* window);
  20. /**
  21. * 窗口尺寸改变。在不可分屏的设备上无意义。
  22. */
  23. void (*onNativeWindowResized)(ANativeActivity* activity, ANativeWindow* window);
  24. /**
  25. * 窗口重绘。
  26. */
  27. void (*onNativeWindowRedrawNeeded)(ANativeActivity* activity, ANativeWindow* window);
  28. /**
  29. * 绘制窗口被销毁时回调。
  30. */
  31. void (*onNativeWindowDestroyed)(ANativeActivity* activity, ANativeWindow* window);
  32. //第三部分
  33. /**
  34. * 输入事件队列被创建时回调。
  35. */
  36. void (*onInputQueueCreated)(ANativeActivity* activity, AInputQueue* queue);
  37. /**
  38. * 输入事件队列被销毁时回调。
  39. */
  40. void (*onInputQueueDestroyed)(ANativeActivity* activity, AInputQueue* queue);
  41. //第四部分
  42. void (*onLowMemory)(ANativeActivity* activity);
  43. } ANativeActivityCallbacks;

上面贴出了完整的NativeActivity的回调方法结构体声明,其中主要包含了四个部分:第一部分与普通Activity相同,不再介绍。

第二部分
这一部分方法是绘制图像相关的生命周期方法。ANativeWindow是一个窗口缓冲区对象,后面会进行详细介绍,我们可以使用这个对象进行绘图操作。

第三部分
该部分是事件相关生命周期,在此期间我们可以对获取到的事件进行处理。注意:该方法在主线程中运行,如果我们需要处理事件队列,必须开启一个子线程。

第四部分
第四部分只包含一个回调,就是低内存警告。这是考虑到了NativeActivity的应用场景而设立的。比如在低内存场景下,游戏应用必须能够做出相应的反应。

6.1.2 NativeActivity对象

  1. /**
  2. * 该结构体定义了android.app.NativeActivity.对象。该对象由Android框架创建并启动。
  3. */
  4. typedef struct ANativeActivity {
  5. /**
  6. * 声明周期回调方法结构体指针。注意,我们不能修改callbacks的指向,因为这是
  7. * 由框架所指定的,但我们可以向其中赋值。
  8. */
  9. struct ANativeActivityCallbacks* callbacks;
  10. /**
  11. * Java虚拟机对象。
  12. */
  13. JavaVM* vm;
  14. /**
  15. * JNI上下文对象,只能在主线程中被访问。
  16. */
  17. JNIEnv* env;
  18. /**
  19. * 这就是NativeActivity对象句柄。
  20. *
  21. * 注意: 这是一个错误的变量名称(我就想知道这货被扣了多少钱的工资)。 该变量本来应该被命名为
  22. * 'activity' 而不是 'clazz'。
  23. */
  24. jobject clazz;
  25. /**
  26. * 应用内数据目录路径名。
  27. */
  28. const char* internalDataPath;
  29. /**
  30. * 外部设备存储目录路径名。
  31. */
  32. const char* externalDataPath;
  33. /**
  34. * SDK版本。
  35. */
  36. int32_t sdkVersion;
  37. /**
  38. * 这是一个用户专属变量,不受框架控制,用户可以将一些数据存储到此处,方便在其他地方使用。
  39. */
  40. void* instance;
  41. /**
  42. * Asset Manager对象。用户可以通过此对象访问应用的assets文件。
  43. */
  44. AAssetManager* assetManager;
  45. /**
  46. * 指向应用内OBB文件的目录。如果应用没有OBB文件,此路径可能不存在。
  47. */
  48. const char* obbPath;
  49. } ANativeActivity;

6.1.3 NativeActivity入口函数

  1. typedef void ANativeActivity_createFunc(ANativeActivity* activity,
  2. void* savedState, size_t savedStateSize);
  3. extern ANativeActivity_createFunc ANativeActivity_onCreate;

native_activity.h定义了ANativeActivity_createFunc函数,这个函数是NativeActivity的入口函数,同时也是生命周期中的onCreate方法。ANativeActivity_onCreate声明了入口函数的名称,我们在写入口函数时,其名称必须是ANativeActivity_onCreate。

6.1.4 其他NativeActivity方法

  1. /**
  2. * 与普通Activity中finish()方法相同,当调用此函数时,将会销毁此activity,注意,这个
  3. * 函数可以在任何线程被调用。
  4. */
  5. void ANativeActivity_finish(ANativeActivity* activity);
  6. /**
  7. * 更改当前Activity中窗口的图像格式,目前可选 WINDOW_FORMAT_RGBA_8888、WINDOW_FORMAT_RGBX_8888、
  8. * WINDOW_FORMAT_RGB_565 ,具体参数请查阅<native_window.h>。此函数可以在任意线程被调用。
  9. */
  10. void ANativeActivity_setWindowFormat(ANativeActivity* activity, int32_t format);
  11. /**
  12. * 添加或移除当前Activity的窗口标记,比如AWINDOW_FLAG_FULLSCREEN等,具体参数请参考<window.h>
  13. * 此函数可以在任何线程被调用。
  14. */
  15. void ANativeActivity_setWindowFlags(ANativeActivity* activity,
  16. uint32_t addFlags, uint32_t removeFlags);
  17. enum {
  18. /**
  19. * 显示软键盘,被打开的键盘可以自动隐藏。
  20. */
  21. ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT = 0x0001,
  22. /**
  23. * 显示软键盘,被打开的键盘不能自动隐藏,除非调用相应的隐藏方法。
  24. */
  25. ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED = 0x0002,
  26. };
  27. /**
  28. * 在当前Activity上打开软键盘。 该函数可以在任何线程被调用。
  29. */
  30. void ANativeActivity_showSoftInput(ANativeActivity* activity, uint32_t flags);
  31. enum {
  32. /**
  33. * 通知表示可以隐藏软键盘。
  34. */
  35. ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY = 0x0001,
  36. /**
  37. * 强制隐藏软键盘。
  38. */
  39. ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS = 0x0002,
  40. };
  41. /**
  42. * 隐藏当前Activity上打开的软键盘。该函数可以在任何线程被调用。
  43. */
  44. void ANativeActivity_hideSoftInput(ANativeActivity* activity, uint32_t flags);

6.2 实现NativeActivity

在Google的官方示例中,其使用了一个胶水层<android_native_app_glue.h>,在这一层里对NativeActivity进行了封装,然而,这里的封装结果并没有使得NativeActivity的结果清晰,反而使人摸不着头脑。所以我们抛弃这个胶水层,直接使用native_activity.h实现。

6.2.1 定义头文件

创建一个native-activity.h头文件,在其中声明生命周期以及其他必要的函数。

  1. #ifndef NDK_NATIVE_ACTIVITY_H
  2. #define NDK_NATIVE_ACTIVITY_H
  3. #include <android/native_activity.h>
  4. /*
  5. * 绑定声明周期函数到activity
  6. */
  7. void bindLifeCycle(ANativeActivity *activity);
  8. /*
  9. * NativeActivity的入口函数
  10. */
  11. void ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize);
  12. /*
  13. * 处理事件队列的线程函数。
  14. */
  15. void *looper(void *args);
  16. void onStart(ANativeActivity *activity);
  17. void onResume(ANativeActivity *activity);
  18. void *onSaveInstanceState(ANativeActivity *activity, size_t *outSize);
  19. void onPause(ANativeActivity *activity);
  20. void onStop(ANativeActivity *activity);
  21. void onDestroy(ANativeActivity *activity);
  22. void onWindowFocusChanged(ANativeActivity *activity, int hasFocus);
  23. void onNativeWindowCreated(ANativeActivity *activity, ANativeWindow *window);
  24. void onNativeWindowDestroyed(ANativeActivity *activity, ANativeWindow *window);
  25. void onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue);
  26. void onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue);
  27. void onConfigurationChanged(ANativeActivity *activity);
  28. void onLowMemory(ANativeActivity *activity);
  29. #endif //NDK_NATIVE_ACTIVITY_H

6.2.2 定义生命周期函数

接下来我们需要创建一个native-activity.cpp源文件,然后定义声明中方法,并对声明周期方法进行绑定。声明周期的定义如下,在其中使用LOG打印函数名。

  1. #define LOG_TAG "native-activity"
  2. #include "JniUtil.h"
  3. #include "native-activity.h"
  4. ...
  5. void
  6. onStart(ANativeActivity *activity) {
  7. ALOGE("onStart");
  8. }
  9. ...

接下来我们定义绑定声明周期的函数:

  1. void
  2. bindLifeCycle(ANativeActivity *activity) {
  3. activity->callbacks->onStart = onStart;
  4. activity->callbacks->onResume = onResume;
  5. activity->callbacks->onSaveInstanceState = onSaveInstanceState;
  6. activity->callbacks->onPause = onPause;
  7. activity->callbacks->onStop = onStop;
  8. activity->callbacks->onDestroy = onDestroy;
  9. activity->callbacks->onWindowFocusChanged = onWindowFocusChanged;
  10. activity->callbacks->onNativeWindowCreated = onNativeWindowCreated;
  11. activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed;
  12. activity->callbacks->onInputQueueCreated = onInputQueueCreated;
  13. activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed;
  14. activity->callbacks->onConfigurationChanged = onConfigurationChanged;
  15. activity->callbacks->onLowMemory = onLowMemory;
  16. }

接下来实现Activity的入口函数,并在其中调用bindLifeCycle函数

  1. void
  2. ANativeActivity_onCreate(ANativeActivity *activity, void *savedState, size_t savedStateSize) {
  3. ALOGE("onCreate");
  4. bindLifeCycle(activity);
  5. }

6.2.3 实现事件处理线程

通过上面的步骤,这个NativeActivity其实已经可以正常运行了。但是,如果我们需要能够正常接收事件的话,就需要开启一个线程。于是我们需要实现looper函数。

  1. static bool isLoop = false;
  2. static pthread_t loopID;
  3. void *
  4. looper(void *args) {
  5. ANativeActivity *activity = (ANativeActivity *) args;
  6. AInputQueue *queue = (AInputQueue *) activity->instance;
  7. AInputEvent *event = NULL;
  8. while (isLoop) {
  9. if (!AInputQueue_hasEvents(queue))//判断队列中是否有未处理事件
  10. continue;
  11. AInputQueue_getEvent(queue, &event);//从队列中获取一个事件
  12. switch (AInputEvent_getType(event)) {//判断事件类型
  13. case AINPUT_EVENT_TYPE_MOTION: {//触摸事件类型
  14. switch (AMotionEvent_getAction(event)) {
  15. case AMOTION_EVENT_ACTION_DOWN:{//触摸按下事件
  16. float x = AMotionEvent_getX(event, 0);//获得x坐标
  17. float y = AMotionEvent_getY(event, 0);//获得y坐标
  18. ALOGE("X:%f,Y:%f", x, y);//输出坐标
  19. break;
  20. }
  21. case AMOTION_EVENT_ACTION_UP:{//触摸抬起事件
  22. break;
  23. }
  24. }
  25. break;
  26. }
  27. case AINPUT_EVENT_TYPE_KEY: {//按键事件类型
  28. switch (AKeyEvent_getAction(event)) {
  29. case AKEY_EVENT_ACTION_DOWN: {//按键按下事件
  30. switch (AKeyEvent_getKeyCode(event)) {
  31. case AKEYCODE_BACK: {//返回键
  32. ANativeActivity_finish(activity);//退出Activity
  33. break;
  34. }
  35. }
  36. break;
  37. }
  38. case AKEY_EVENT_ACTION_UP: {//按键抬起事件
  39. break;
  40. }
  41. }
  42. }
  43. }
  44. AInputQueue_finishEvent(queue, event, 1);//将事件从事件队列中移除
  45. }
  46. return args;
  47. }

接下来我们只用在事件队列创建完成后开启线程,然后在事件队列销毁前退出线程循环即可,如果对线程不太了解的,可以参考C++多线程编程(二)——线程操作

  1. void
  2. onInputQueueCreated(ANativeActivity *activity, AInputQueue *queue) {
  3. ALOGE("onInputQueueCreated");
  4. isLoop = true;
  5. activity->instance = (void *) queue;
  6. pthread_create(&loopID, NULL, looper, activity);
  7. }
  8. void
  9. onInputQueueDestroyed(ANativeActivity *activity, AInputQueue *queue) {
  10. ALOGE("onInputQueueDestroyed");
  11. isLoop = false;
  12. }

6.2.4 注册NativeActivity

在配置完CMakeList之后,我们需要修改应用的清单配置文件

  1. <activity
  2. android:name="android.app.NativeActivity"
  3. android:configChanges="orientation|keyboardHidden"
  4. android:label="@string/app_name">
  5. <meta-data
  6. android:name="android.app.lib_name"
  7. android:value="ndk-lib" />
  8. <intent-filter>
  9. <action android:name="android.intent.action.MAIN" />
  10. <category android:name="android.intent.category.LAUNCHER" />
  11. </intent-filter>
  12. </activity>

这里activity的名称是固定的android.app.NativeActivitymeta-data以key-value形式描述该Activity参数,比如name="android.app.lib_name"value="ndk-lib"指明了这个NativeActivity所在的本地库的库名为ndk-lib,另外,该Activity必须被指定为MainActivity且需要自动启动。


下一篇:OpenGL-ES 3.0学习指南(五)——EGL基础

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注