「游戏引擎Mojoc」(6)NDK替换android_native_app_glue的实现
来源:互联网 发布:网络新词语大全 编辑:程序博客网 时间:2024/06/05 15:11
Android NDK开发native app官方提供了一个胶水层,android_native_app_glue,NDK的demo里给出了示例,如何使用胶水层开发native activity。
但我发现这个胶水层不好,过于复杂,过度封装,代码质量不高。于是经过研究,直接使用ANativeActivity的接口,重写一个自己的胶水层。
Mojoc里重写的层是NativeGlue.c,经过测试完全没有问题。本文将会介绍一下实现思路,完整的代码已经包含在Mojoc之中了。NativeGlue有以下几个特点:
- 直接使用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 Start"); nativeActivity = 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->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; AApplication->Init();//---------------------------------------------------------------------------------------------------------------------- AData->assetConfig = AConfiguration_new(); AConfiguration_fromAssetManager(AData->assetConfig, activity->assetManager); 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 的输入输出事件以及重力感应事件。
下面是这个线程的代码。思路很简单,放弃了looper的方案,我们就是用switch case去处理主循环的事件。定义一系列和回调函数对应的事件类型。
而线程开头创建的Looper是为了配合事件处理的,不是我们自己需要使用的。可以看到,在这个线程里完全处理了Activity的生命周期,并且还有OpenGL和EGL相关的初始化工作,当然还有引擎的回调通知。
static void* ThreadRun(void* param){ AData->looper = ALooper_prepare(0);//---------------------------------------------------------------------------------------------------------------------- while (true) { switch (AData->mainThreadCallback) { case MainThread_OnNull: // handle event ALooper_pollAll(0, NULL, NULL, NULL); // application main loop AApplication->Loop(); // render buffer eglSwapBuffers(AData->display, AData->surface); continue; case MainThread_OnDestroy: // call in main thread AEGLTool->DestroyEGL(&AData->display, &AData->context, &AData->surface); AApplication->Destroy(); return NULL; case MainThread_OnPause: // sometimes before resized // call in main thread AApplication->Pause(); AData->mainThreadCallback = MainThread_OnWait; continue; case MainThread_OnResume: // call in main thread AApplication->Resume(); AData->mainThreadCallback = MainThread_OnNull; break; case MainThread_OnFirstResized: // we need create EGL and use openGL in one thread // call in main thread AEGLTool->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); AApplication->GLReady ( ANativeWindow_getWidth (AData->window), ANativeWindow_getHeight(AData->window) ); AData->mainThreadCallback = MainThread_OnNull; break; case MainThread_OnResized: // call in main thread AEGLTool->ResetSurface(AData->window, AData->display, AData->context, AData->config, &AData->surface); ANativeWindow_setBuffersGeometry(AData->window, 0, 0, AData->format); AApplication->Resized(ANativeWindow_getWidth(AData->window), ANativeWindow_getHeight(AData->window)); AData->mainThreadCallback = MainThread_OnNull; break; case MainThread_OnWait: break; } } return NULL;}
这些case是自定义的枚举类型,是在主线程回调函数里设置的,主线程回调就是ANativeActivity挂载的那些On事件函数。
typedef volatile enum{ MainThread_OnNull, MainThread_OnWait, MainThread_OnResized, MainThread_OnPause, MainThread_OnResume, MainThread_OnFirstResized, MainThread_OnDestroy,}MainThreadCallback;
在游戏被切入后台的时候,就阻塞线程利用case到一个空的条件上,让循环空转。恢复只是简单切换case状态即可。这样主线程的事件被我们放到了我们自己的线程用处理,达到了使用Looper的效果。
在线程最开始的地方,我们初始化一个Looper以供input和senor使用。ALooper_pollAll(0, NULL, NULL, NULL),每帧调用就是让Looper去处理input和senor的事件。
主线程的事件回调函数实现,就是简单的切换case的状态。代码就不贴了,Mojoc里都有。
这里需要注意的是,在OnNativeWindowResized回调中,我们区分了第一次调用和非第一次调用。因为我发现,resized事件总是在 CreateWindow之后。CreateWindow之后表示可以进行绘制,而在resized函数中拿到的window是最终不会变化的window,所以我建议绘制开始的代码应该放在resized里面,并不是create window时候。并且,区分第一次resized调用,是为了进行第一次初始化的工作,比如EGL的创建等等。这样就不必再游戏进入后台销毁,切换到前台在创建一遍EGL。
那么,还有事件处理如下:
static int LooperOnInputEvent(int fd, int events, void* data){ AInputEvent* event; while (AInputQueue_getEvent(AData->inputQueue, &event) >= 0) { if (AInputQueue_preDispatchEvent(AData->inputQueue, event) == 0) { AInputQueue_finishEvent(AData->inputQueue, event, OnInputEvent(event)); } } return 1;}
AInputQueue_attachLooper(inputQueue, AData->looper, looper_id_input, LooperOnInputEvent, NULL),这句话的调用,系统在回调里给了inputQueue,那么自己缓存的looper也有了。这里LooperOnInputEvent就是一个looper回调模式的使用。每次调用ALooper_pollAll这个,都会去检查looper是否有事件处理,如果有就会回调注册到looper的回调函数。
最后
Mojoc替换掉了android_native_app_glue胶水层,使用了自己的实现。在过程中,我发现了EGL的初始化和销毁问题,NDK的demo中,每次切换前后台的时候,都会去销毁在初始化。我发现这是没有必要的,因为切换系统仅仅是把window销毁了,没必要吧ELG的context display config等一起销毁,只需要重新构建surface即可。
「复杂源于混乱和未知,现实只有简单」
- 「游戏引擎Mojoc」(6)NDK替换android_native_app_glue的实现
- 「游戏引擎Mojoc」(1)简介
- 「游戏引擎Mojoc」(7)C使用goto label地址实现协程
- 「游戏引擎Mojoc」(8)C实现泛型ArrayList
- 「游戏引擎Mojoc」(2)C代码风格
- 「游戏引擎Mojoc」(3)C面向对象编程
- 「游戏引擎Mojoc」(5)快速指南
- 「游戏引擎Mojoc」(4)面向组件-状态机-消息驱动3合1编程模型
- 替换android_native_app_glue实现, 直接使用ANativeActivity回调
- 使用游戏引擎实现的水波纹
- 游戏服务器引擎实现
- libgdx游戏引擎(七)之多游戏界面的实现
- 高层游戏引擎——基于OGRE所实现的高层游戏引擎框架
- 高层游戏引擎——基于OGRE所实现的高层游戏引擎框架
- 高层游戏引擎——基于OGRE所实现的高层游戏引擎框架
- 一个简单的游戏引擎核心状态机的C++实现
- 游戏引擎的资源管理
- 游戏引擎的开发
- Linux(CentOS)下设置nginx开机自动启动和chkconfig管理
- 希尔伯特内积空间
- eclipse adb占用 以及重启卡死
- 从主题模型(Topic Model)到隐语义模型(Latent Factor Model)
- postgresql的Explain命令结果分析
- 「游戏引擎Mojoc」(6)NDK替换android_native_app_glue的实现
- sub2ind与ind2sub
- Visual Studio 2015中的常用调试技巧分享
- c++重载操作符续
- spring mvc完成restful风格的url
- C. Maximum splitting--codeforces
- 「游戏引擎Mojoc」(7)C使用goto label地址实现协程
- java timer 指定某时间点执行
- 解决RedHat Enterprise Linux 6.4 需要注册才能使用yum源的问题