替换android_native_app_glue实现, 直接使用ANativeActivity回调
2016-02-07 23:25
477 查看
NDK为我们提供了胶水层android_native_app_glue,但我们现在要抛弃它,我们能够有一个更简单更有效率的玩法。替换思路如下:
直接使用ANativeActivity事件回调,放弃使用Looper的方案
input.h和senor.h提供的接口中,必须使用Looper,所以还是会初始化一个Looper
Looper使用回调函数模式
首先,ANativeActivity提供的回调函数,我们全部挂载上,这里的事件属于主线程事件,来自于java代码回调。我们还是会自己启动一个线程,完全控制执行逻辑。
直接实现native代码的入口函数,注意这个函数名字可以在AndroidManifest.xml文件中配置。这个函数只会执行一遍在启动的时候,而后面的On开头的回调函数,会在游戏进入后台在切换回来的时候,不断反复的按照某个顺序被调用,通过日志测试即可知道具体的顺序。这里,我们在最开始的时候,就启动一个线程直到应用销毁,只会启动一个。注意PTHREAD_CREATE_DETACHED属性的设置,我们这个线程结束回收资源,和调用线程没有依赖关系。
这个独立的线程,有几个工作需要完成。
这个线程要一直运行,并且可以由我们控制生命周期。并且需要在游戏进入后台时候阻塞,回来的时候恢复。
需要能够接受处理主线程事件回调
能够接受和处理赖在input.h 和 senor.h 的输入输出事件以及重力感应事件
看代码喽==
思路很简单,放弃了looper的方案,我们就是用switch case去处理主循环的事件。定义一系列和回调函数对应的事件类型。
在游戏被切入后台的时候,就阻塞线程利用case到一个空的条件上,让循环空转。恢复只是简单切换case状态即可。这样主线程的事件被我们放到了我们自己的线程用处理,达到了使用Looper的效果。在线程最开始的地方,我们初始化一个Looper以供input和senor使用。ALooper_pollAll(0,
NULL, NULL, NULL); 每帧调用就是让Looper去处理input和senor的事件。
那么主线程的事件回调函数实现,我们就是简单的切换case的状态。
这里需要注意的是,在OnNativeWindowResized回调中,我们区分了第一次调用和非第一次调用。因为我发现,resized事件总是在 CreateWindow之后。CreateWindow之后表示可以进行绘制,而在resized函数中拿到的window是最终不会变化的window,所以我建议绘制开始的代码应该放在resized里面,并不是create window时候。并且,区分第一次resized调用,是为了进行第一次初始化的工作,比如EGL的创建等等。这样就不必再游戏进入后台销毁,切换到前台在创建一遍EGL。
那么,如何处理input事件的呢。看AInputQueue_attachLooper(inputQueue, AData->looper,
looper_id_input, LooperOnInputEvent, NULL);这句话的调用,系统在回调里给了我inputQueue,那么自己缓存的looper也有了。这里LooperOnInputEvent就是一个looper回调模式的使用。每次调用ALooper_pollAll这个,都会去检查looper是否有事件处理,如果有就会回调注册到looper的回调函数。
这是NDK demo给我们展示的处理input事件的标准流程。至于AData结构,就是自己定义的存放上下文需要夸函数使用的变量。
经过我的测试,本文提供的思路和实现方法是完全可以的,我替换掉了android_native_app_glue胶水层,使用了自己的实现。在实现过程中,我发现了EGL的初始化和销毁问题,NDK的demo中,每次切换前后台的时候,都会去销毁在初始化。我发现这是没有必要的,因为切换系统仅仅是把window销毁了,没必要吧ELG的context display config等一起销毁,只需要重新构建surface即可。下一篇我会介绍一下。
直接使用ANativeActivity事件回调,放弃使用Looper的方案
input.h和senor.h提供的接口中,必须使用Looper,所以还是会初始化一个Looper
Looper使用回调函数模式
首先,ANativeActivity提供的回调函数,我们全部挂载上,这里的事件属于主线程事件,来自于java代码回调。我们还是会自己启动一个线程,完全控制执行逻辑。
void ANativeActivity_OnCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize) { ALog_D("ANativeActivity_onCreate OnCreate"); 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->onNativeWindowResized = OnNativeWindowResized; activity->callbacks->onNativeWindowRedrawNeeded = OnNativeWindowRedrawNeeded; activity->callbacks->onNativeWindowDestroyed = OnNativeWindowDestroyed; activity->callbacks->onInputQueueCreated = OnInputQueueCreated; activity->callbacks->onInputQueueDestroyed = OnInputQueueDestroyed; activity->callbacks->onContentRectChanged = OnContentRectChanged; activity->callbacks->onConfigurationChanged = OnConfigurationChanged; activity->callbacks->onLowMemory = OnLowMemory; /* if (savedState) { AData->savedState = malloc(savedStateSize); AData->savedStateSize = savedStateSize; memcpy(AData->savedState, savedState, savedStateSize); } */ pthread_t thread[1]; pthread_attr_t attr [1]; pthread_attr_init(attr); pthread_attr_setdetachstate(attr, PTHREAD_CREATE_DETACHED); pthread_create(thread, attr, ThreadRun, NULL); pthread_attr_destroy(attr); }
直接实现native代码的入口函数,注意这个函数名字可以在AndroidManifest.xml文件中配置。这个函数只会执行一遍在启动的时候,而后面的On开头的回调函数,会在游戏进入后台在切换回来的时候,不断反复的按照某个顺序被调用,通过日志测试即可知道具体的顺序。这里,我们在最开始的时候,就启动一个线程直到应用销毁,只会启动一个。注意PTHREAD_CREATE_DETACHED属性的设置,我们这个线程结束回收资源,和调用线程没有依赖关系。
这个独立的线程,有几个工作需要完成。
这个线程要一直运行,并且可以由我们控制生命周期。并且需要在游戏进入后台时候阻塞,回来的时候恢复。
需要能够接受处理主线程事件回调
能够接受和处理赖在input.h 和 senor.h 的输入输出事件以及重力感应事件
看代码喽==
static void* ThreadRun(void* param) { AApplication->Init(); AData->looper = ALooper_prepare(0); struct timespec now; struct timespec last; // start clock clock_gettime(CLOCK_MONOTONIC, &last); while (true) { clock_gettime(CLOCK_MONOTONIC, &now); float deltaTime = (now.tv_nsec - last.tv_nsec) * 0.000000001 + (now.tv_sec - last.tv_sec); last = now; switch (AData->mainThreadCallbck) { case main_thread_on_destroy: AGLUtils->DestroyEGL(&AData->display, &AData->context, &AData->surface); return NULL; case main_thread_on_pause: // sometimes before resized AApplication->callbacks->OnPause(); AData->mainThreadCallbck = main_thread_on_wait; continue; case main_thread_on_first_resized: // we need create EGL and use openGL in one thread AGLUtils->CreateEGL(AData->window, &AData->display, &AData->context, &AData->surface, &AData->config); // EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is // guaranteed to be accepted by ANativeWindow_SetBuffersGeometry() // As soon as we picked a EGLConfig, we can safely reconfigure the // ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID eglGetConfigAttrib (AData->display, AData->config, EGL_NATIVE_VISUAL_ID, &AData->format); ANativeWindow_setBuffersGeometry(AData->window, 0, 0, AData->format); OnResized(ANativeWindow_getWidth(AData->window), ANativeWindow_getHeight(AData->window)); AApplication->OnGLReady(); AApplication->callbacks->OnCreated(); AData->mainThreadCallbck = main_thread_on_none; break; case main_thread_on_resized: AGLUtils->ResetSurface(AData->window, AData->display, AData->context, AData->config, &AData->surface); ANativeWindow_setBuffersGeometry(AData->window, 0, 0, AData->format); OnResized(ANativeWindow_getWidth(AData->window), ANativeWindow_getHeight(AData->window)); AData->mainThreadCallbck = main_thread_on_none; break; case main_thread_on_none: break; case main_thread_on_wait: continue; } // handle event ALooper_pollAll(0, NULL, NULL, NULL); // application main loop AApplication->Loop(deltaTime); eglSwapBuffers(AData->display, AData->surface); } return NULL; }
思路很简单,放弃了looper的方案,我们就是用switch case去处理主循环的事件。定义一系列和回调函数对应的事件类型。
typedef enum { main_thread_on_none, main_thread_on_wait, main_thread_on_resized, main_thread_on_pause, main_thread_on_first_resized, main_thread_on_destroy, } MainThreadCallback;
在游戏被切入后台的时候,就阻塞线程利用case到一个空的条件上,让循环空转。恢复只是简单切换case状态即可。这样主线程的事件被我们放到了我们自己的线程用处理,达到了使用Looper的效果。在线程最开始的地方,我们初始化一个Looper以供input和senor使用。ALooper_pollAll(0,
NULL, NULL, NULL); 每帧调用就是让Looper去处理input和senor的事件。
那么主线程的事件回调函数实现,我们就是简单的切换case的状态。
static void OnPause(ANativeActivity* activity) { ALog_D("NativeActivity OnPause"); AData->mainThreadCallbck = main_thread_on_pause; } static void OnStop(ANativeActivity* activity) { ALog_D("NativeActivity OnStop"); } static void OnDestroy(ANativeActivity* activity) { ALog_D("NativeActivity OnDestroy"); AData->mainThreadCallbck = main_thread_on_destroy; } static void OnWindowFocusChanged(ANativeActivity* activity, int hasFocus) { ALog_D("NativeActivity OnWindowFocusChanged"); } static void OnNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { ALog_D("NativeActivity OnNativeWindowCreated"); AData->window = window; } static void OnNativeWindowResized(ANativeActivity* activity, ANativeWindow* window) { ALog_D("NativeActivity OnNativeWindowResized"); AData->window = window; static bool isFirst = true; if (isFirst) { isFirst = false; AData->mainThreadCallbck = main_thread_on_first_resized; } else { AData->mainThreadCallbck = main_thread_on_resized; } } static void OnNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow* window) { ALog_D("NativeActivity OnNativeWindowRedrawNeeded"); } static void OnNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { ALog_D("NativeActivity OnNativeWindowDestroyed"); AData->mainThreadCallbck = main_thread_on_wait; } static void OnInputQueueCreated(ANativeActivity* activity, AInputQueue* inputQueue) { ALog_D("NativeActivity OnInputQueueCreated"); AData->inputQueue = inputQueue; AInputQueue_attachLooper(inputQueue, AData->looper, looper_id_input, LooperOnInputEvent, NULL); } static void OnInputQueueDestroyed(ANativeActivity* activity, AInputQueue* inputQueue) { ALog_D("NativeActivity OnInputQueueDestroyed"); AInputQueue_detachLooper(inputQueue); } static void OnContentRectChanged(ANativeActivity* activity, const ARect* rect) { ALog_D("NativeActivity OnContentRectChanged"); } static void OnConfigurationChanged(ANativeActivity* activity) { ALog_D("NativeActivity OnConfigurationChanged"); AConfiguration_fromAssetManager(AData->assetConfig, activity->assetManager); } static void OnLowMemory(ANativeActivity* activity) { ALog_D("NativeActivity OnLowMemory"); }
这里需要注意的是,在OnNativeWindowResized回调中,我们区分了第一次调用和非第一次调用。因为我发现,resized事件总是在 CreateWindow之后。CreateWindow之后表示可以进行绘制,而在resized函数中拿到的window是最终不会变化的window,所以我建议绘制开始的代码应该放在resized里面,并不是create window时候。并且,区分第一次resized调用,是为了进行第一次初始化的工作,比如EGL的创建等等。这样就不必再游戏进入后台销毁,切换到前台在创建一遍EGL。
那么,如何处理input事件的呢。看AInputQueue_attachLooper(inputQueue, AData->looper,
looper_id_input, LooperOnInputEvent, NULL);这句话的调用,系统在回调里给了我inputQueue,那么自己缓存的looper也有了。这里LooperOnInputEvent就是一个looper回调模式的使用。每次调用ALooper_pollAll这个,都会去检查looper是否有事件处理,如果有就会回调注册到looper的回调函数。
int LooperOnInputEvent(int fd, int events, void* data) { AInputEvent* event; while (AInputQueue_getEvent(AData->inputQueue, &event) >= 0) { if (AInputQueue_preDispatchEvent(AData->inputQueue, event)) { continue; } AInputQueue_finishEvent(AData->inputQueue, event, OnInputEvent(event)); } return 1; }
这是NDK demo给我们展示的处理input事件的标准流程。至于AData结构,就是自己定义的存放上下文需要夸函数使用的变量。
经过我的测试,本文提供的思路和实现方法是完全可以的,我替换掉了android_native_app_glue胶水层,使用了自己的实现。在实现过程中,我发现了EGL的初始化和销毁问题,NDK的demo中,每次切换前后台的时候,都会去销毁在初始化。我发现这是没有必要的,因为切换系统仅仅是把window销毁了,没必要吧ELG的context display config等一起销毁,只需要重新构建surface即可。下一篇我会介绍一下。
相关文章推荐
- Android项目中的DAO
- swift学习笔记之-闭包
- android如何根据一个String来级联ListView
- Objective-C Runtime系统
- android日常开发总结的技术经验60条
- Unity在编辑器状态下清空控制台信息
- Android对话框自定义标题
- 单例
- Spring注解之:@SpringBootApplication
- Android应用:StatusBar状态栏、NavigationBar虚拟按键栏、ActionBar标题栏、Window屏幕内容区域等的宽高
- Android应用:StatusBar状态栏、NavigationBar虚拟按键栏、ActionBar标题栏、Window屏幕内容区域等的宽高
- Gradle的使用
- iOS 协议 委托
- android Scroller类详解
- ios 崩溃信息获取代码
- 文件的下载和断电续传
- IoSetNextIrpStackLocation routine
- 【Android开发小记--17】一键清理
- 【Android开发小记--16】assets、raw、内部存储、外部存储——文件的读写
- JS调试输出Object