UE Android项目工程中的GameActivity基于NativeActivity,相关知识请参考之前的一篇文章,NativeActivity介绍
在之前NativeActivity介绍中,我们知道胶水层app_native_app_glue会创建一个子线程并prepare()开启loop循环。UE中这个线程又叫做GameThread,用于处理游戏逻辑业务。初次之外,通过android_main()入口,在UE4引擎代码LaunchAndroid中会创建一个AndroidProcessEvents,用来轮询并处理cmd命令和input事件。
android_main()
void android_main(struct android_app* state)
{
//STEP1 获取当前GameThread线程Id,并将android_app赋值给到GNativeAndroidApp
GGameThreadId = FPlatformTLS::GetCurrentThreadId();
BootTimingPoint("android_main");
FPlatformMisc::LowLevelOutputDebugString(TEXT("Entering native app glue main function"));
GNativeAndroidApp = state;
check(GNativeAndroidApp);
//STEP2 创建新的AndroidEventThreadWorker线程,用来处理cmd和input事件。
//关于这两类事件,可以参考源码文档中
//https://cs.android.com/android/platform/superproject/+/master:prebuilts/ndk/current/sources/android/native_app_glue/android_native_app_glue.h 对android_app数据结构的注解
pthread_attr_t otherAttr;
pthread_attr_init(&otherAttr);
pthread_attr_setdetachstate(&otherAttr, PTHREAD_CREATE_DETACHED);
pthread_create(&G_AndroidEventThread, &otherAttr, AndroidEventThreadWorker, state);
FPlatformMisc::LowLevelOutputDebugString(TEXT("Created event thread"));
// Make sure glue isn't stripped. (not needed in ndk-15)
#if PLATFORM_ANDROID_NDK_VERSION < 150000
app_dummy();
#endif
//STEP3 进入到GameThread的主循环,里面会初始化ue引擎以及定时tick
//@todo android: replace with native activity, main loop off of UI thread, etc.
AndroidMain(state);
}
android_main做了三件事情
- 获取当前GameThread Id,将android_app赋值给到GNativeAndroidApp,struct android_app 定义
- 创建AndroidEventThreadWorker线程,处理cmd和input事件
- 调用AndroidMain函数
AndroidMain()
//Main function called from the android entry point
int32 AndroidMain(struct android_app* state)
{
BootTimingPoint("AndroidMain");
FPlatformMisc::LowLevelOutputDebugString(TEXT("Entered AndroidMain()\n"));
// Force the first call to GetJavaEnv() to happen on the game thread, allowing subsequent calls to occur on any thread
FAndroidApplication::GetJavaEnv();
// Set window format to 8888
ANativeActivity_setWindowFormat(state->activity, WINDOW_FORMAT_RGBA_8888);
//balabala...
//阻塞等待ResumeMainInit被调用
// wait for java activity onCreate to finish
{
SCOPED_BOOT_TIMING("Wait for GResumeMainInit");
while (!GResumeMainInit)
{
FPlatformProcess::Sleep(0.01f);
FPlatformMisc::MemoryBarrier();
}
}
// read the command line file
InitCommandLine();
// ready for onCreate to complete
GEventHandlerInitialized = true;
// 初始化文件挂载
// Initialize file system access (i.e. mount OBBs, etc.).
// We need to do this really early for Android so that files in the
// OBBs and APK are found.
IPlatformFile::GetPlatformPhysical().Initialize(nullptr, FCommandLine::Get());
// balabala...
// pre初始化游戏引擎
// initialize the engine
int32 PreInitResult = GEngineLoop.PreInit(0, NULL, FCommandLine::Get());
// 若pre初始化失败直接退出游戏线程
if (PreInitResult != 0)
{
checkf(false, TEXT("Engine Preinit Failed"));
return PreInitResult;
}
// balabala...
// 初始化游戏引擎
GEngineLoop.Init();
bDidCompleteEngineInit = true;
UE_LOG(LogAndroid, Log, TEXT("Passed GEngineLoop.Init()"));
// balabala...
//开始TICK
// tick until done
while (!IsEngineExitRequested())
{
FAndroidStats::UpdateAndroidStats();
FAppEventManager::GetInstance()->Tick();
if(!FAppEventManager::GetInstance()->IsGamePaused())
{
GEngineLoop.Tick();
}
else
{
// use less CPU when paused
FPlatformProcess::Sleep(0.10f);
}
//balabala...
}
//退出游戏则走到这里来
FAppEventManager::GetInstance()->TriggerEmptyQueue();
UE_LOG(LogAndroid, Log, TEXT("Exiting"));
// exit out!
GEngineLoop.Exit();
UE_LOG(LogAndroid, Log, TEXT("Exiting is over"));
FPlatformMisc::RequestExit(1);
return 0;
}
AndroidMain中做了这么几件事情
- 阻塞等待ResumeMainInit被调用
- 初始化文件系统权限
- 初始化游戏引擎,失败直接结束线程
- tick循环,TICK中处理游戏逻辑
- 退出游戏线程
Activity生命周期处理和input输入事件处理
通过NativeActivity一文,我们梳理了cmd和input事件的处理流程
而在UE项目中,在AndroidEventThreadWorker中实现了onAppCmd()和onInputEvent()方法,用来响应cmd和input事件
static void* AndroidEventThreadWorker( void* param )
{
FAndroidMisc::SetThreadName("EventWorker");
struct android_app* state = (struct android_app*)param;
FPlatformProcess::SetThreadAffinityMask(FPlatformAffinity::GetMainGameMask());
FPlatformMisc::LowLevelOutputDebugString(TEXT("Entering event processing thread engine entry point"));
EventThreadID = FPlatformTLS::GetCurrentThreadId();
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ALooper_addFd(looper, state->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL,
&state->cmdPollSource);
state->looper = looper;
FPlatformMisc::LowLevelOutputDebugString(TEXT("Prepared looper for event thread"));
//callback赋值,胶水层的事件响应最终会调用这里的方法
//Assign the callbacks
state->onAppCmd = OnAppCommandCB;
state->onInputEvent = HandleInputCB;
FPlatformMisc::LowLevelOutputDebugString(TEXT("Passed callback initialization"));
FPlatformMisc::LowLevelOutputDebugString(TEXT("Passed sensor initialization"));
TheChoreographer.SetupChoreographer();
// window is initially invalid/locked.
UE_LOG(LogAndroid, Log, TEXT("event thread, Initial HW window lock."));
GAndroidWindowLock.Lock();
//continue to process events until the engine is shutting down
while (!IsEngineExitRequested())
{
// FPlatformMisc::LowLevelOutputDebugString(TEXT("AndroidEventThreadWorker"));
//这里有点不明白,NativeActivity中已经处理了事件,这里的是用来干啥的
AndroidProcessEvents(state);
sleep(EventRefreshRate); // this is really 0 since it takes int seconds.
}
GAndroidWindowLock.Unlock();
UE_LOG(LogAndroid, Log, TEXT("Exiting"));
return NULL;
}
OnAppCommandCB方法中会处理事件,并且将有关的事件推到UE自身的queue(GameThread中)中,并且在下一次tick到来的时候消费掉
//Called from the event process thread
static void OnAppCommandCB(struct android_app* app, int32_t cmd)
{
check(IsInAndroidEventThread());
static bool bDidGainFocus = false;
//FPlatformMisc::LowLevelOutputDebugStringf(TEXT("OnAppCommandCB cmd: %u, tid = %d"), cmd, gettid());
static bool bHasFocus = false;
static bool bHasWindow = false;
static bool bIsResumed = false;
// Set event thread's view of the window dimensions:
{
ANativeWindow* DimensionWindow = app->pendingWindow ? app->pendingWindow : app->window;
if (DimensionWindow)
{
FAndroidWindow::SetWindowDimensions_EventThread(DimensionWindow);
}
}
switch (cmd)
{
case APP_CMD_SAVE_STATE:
/**
* Command from main thread: the app should generate a new saved state
* for itself, to restore from later if needed. If you have saved state,
* allocate it with malloc and place it in android_app.savedState with
* the size in android_app.savedStateSize. The will be freed for you
* later.
*/
// the OS asked us to save the state of the app
UE_LOG(LogAndroid, Log, TEXT("Case APP_CMD_SAVE_STATE"));
//推送到UE的QUEUE中,最终在tick()执行的时候消费掉
FAppEventManager::GetInstance()->EnqueueAppEvent(APP_EVENT_STATE_SAVE_STATE);
break;
//balabala....
}
//balabala...
}
在AndroidEventManager.cpp中实现了Tick()
void FAppEventManager::Tick()
{
check(IsInGameThread());
while (!Queue.IsEmpty())
{
FAppEventPacket Event = DequeueAppEvent();
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("FAppEventManager::Tick processing, %d"), int(Event.State));
switch (Event.State)
{
case APP_EVENT_STATE_WINDOW_CREATED:
FAndroidWindow::EventManagerUpdateWindowDimensions(Event.Data.WindowWidth, Event.Data.WindowHeight);
bCreateWindow = true;
break;
case APP_EVENT_STATE_WINDOW_RESIZED:
// Cache the new window's dimensions for the game thread.
FAndroidWindow::EventManagerUpdateWindowDimensions(Event.Data.WindowWidth, Event.Data.WindowHeight);
ExecWindowResized();
break;
case APP_EVENT_STATE_WINDOW_CHANGED:
// React on device orientation/windowSize changes only when application has window
// In case window was created this tick it should already has correct size
// see 'Java_com_epicgames_ue4_GameActivity_nativeOnConfigurationChanged' for event thread/game thread mismatches.
ExecWindowResized();
break;
case APP_EVENT_STATE_SAVE_STATE:
bSaveState = true; //todo android: handle save state.
break;
//balabala...
}
//balabala...
}
这里涉及到多个线程的通信,事件传递后续会以线程间传递的方式来介绍
UIThread (Android sdk)
GameThread (Android NDK创建,NDK和UE都使用)
AndroidEventThread(UE创建并使用)