背景
UE Android lib集成过程,使用普通Activity+SurfaceView的方式加载UElib,在退出游戏Activity时,出现nativeOnInputQueueDestroyed引擎的崩溃
原因
InputQueue处理流程
- onDestroy()
- onInputQueueDestroy()
这两个里面都可以在native层处理inputQueue,主要是 AInputQueue_detachLooper取消looper绑定
然后是在native的ondestroy中设置android_app为空(这里会把inputqueue也置为空) - onDetachedFromWindow中兜底将inputqueue设置为空
上面这个流程导致UE改造中出现了问题:
- android层post onInputQueueDestroy消息到主线程,导致onInputQueueDestroy执行在onDetachedFromWindow之后,从而导致AInputQueue_detachLooper执行时出现“pthread_mutex_lock called on a destroyed mutex (0xf698788c)”错误,也就是inputqueue对象为空导致
- android层post onInputQueueDestroy消息到子线程,由于主线程和子线程时序没有保证,导致偶尔出现上面的问题,inputqueue先被置为NULL,然后执行了AInputQueue_detachLooper。
解决方案
方案一
直接主线程手动调用nativeOnInputQueueDestroyed接口。
由于里面没有走AInputQueue_detachLooper(),会走在onDetachedFromWindow前面,避免出现崩溃。对于GameThread线程为子线程的场景,可能会出现其他地方释放inputqueue,目前没有找到,有一定的风险,但是简单。
方案二
仿照NativeActivity的胶水层,在处理线程同步时,采用锁同步机制
//NDK胶水层代码
//android_app_set_input方法发运行在UIThread
static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) {
pthread_mutex_lock(&android_app->mutex);//加锁
android_app->pendingInputQueue = inputQueue;
android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED);//APP_CMD_INPUT_CHANGED消息写到GameThread
while (android_app->inputQueue != android_app->pendingInputQueue) {//等待,直到APP_CMD_INPUT_CHANGED被消费掉,detachlooper操作在消息处理时被执行
pthread_cond_wait(&android_app->cond, &android_app->mutex);
}
pthread_mutex_unlock(&android_app->mutex);//解除锁,UIThread继续执行
}
我们需要仿照这样在java层post MSG_TYPE_INPUTQUEUE_DESTROY消息时加锁并wait,在消息处理后跳出等待并解锁。
方案三
生命周期相关的方法直接在主线程中执行,不需要走GameThread。 可能在engine初始化时出现耗时的问题。
Android demo中会涉及到三个线程
- UI Thread 应用主线程,处理非游戏UI界面逻辑。
- GameThread NativeActivity创建时自动创建的一个带loop的子线程,处理UE tick事件(游戏逻辑)。
- AndroidEventThread ,事件处理子线程,处理来自Android端的事件,包括Event事件(生命周期)和Input事件(点击)。
AndroidEventThread事件会将Event事件通过FAppEventManager::GetInstance()->EnqueueAppEvent(APP_EVENT_STATE_ON_RESUME)加入到queue中,等待GameThread中tick触发时dequeue处理消息。