UE4手游切前后台(iOS)
GameThread主函数
-(void)MainAppThread:(NSDictionary*)launchOptions { // make sure this thread has an auto release pool setup NSAutoreleasePool* AutoreleasePool = [[NSAutoreleasePool alloc] init]; { SCOPED_BOOT_TIMING("[IOSAppDelegate MainAppThread setup]"); self.bHasStarted = true; GIsGuarded = false; GStartTime = FPlatformTime::Seconds(); while(!self.bCommandLineReady) { usleep(100); } } FAppEntry::Init(); // 执行GEngineLoop.PreInit和GEngineLoop.Init // check for update on app store if cvar is enabled /* dispatch_async(dispatch_get_main_queue(), ^{ [[IOSAppDelegate GetDelegate] DoUpdateCheck]; });*/ // now that GConfig has been loaded, load the EnabledAudioFeatures from ini TArray<FString> EnabledAudioFeatures; GConfig->GetArray(TEXT("Audio"), TEXT("EnabledAudioFeatures"), EnabledAudioFeatures, GEngineIni); for (const FString& EnabledAudioFeature : EnabledAudioFeatures) { EAudioFeature Feature = EAudioFeature::NumFeatures; LexFromString(Feature, *EnabledAudioFeature); if (Feature != EAudioFeature::NumFeatures) { GEnabledAudioFeatures[static_cast<uint8>(Feature)] = 1; } } [self ToggleAudioSession:true]; #if !BUILD_EMBEDDED_APP [self InitIdleTimerSettings]; #endif bEngineInit = true; // 完成引擎初始化 // put a render thread job to turn off the splash screen after the first render flip if (GShowSplashScreen) { FGraphEventRef SplashTask = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { GShowSplashScreen = false; }, TStatId(), NULL, ENamedThreads::ActualRenderingThread); } for (NSDictionary* openUrlParameter in self.savedOpenUrlParameters) { UIApplication* application = [openUrlParameter valueForKey : @"application"]; NSURL* url = [openUrlParameter valueForKey : @"url"]; NSString * sourceApplication = [openUrlParameter valueForKey : @"sourceApplication"]; id annotation = [openUrlParameter valueForKey : @"annotation"]; FIOSCoreDelegates::OnOpenURL.Broadcast(application, url, sourceApplication, annotation); } self.savedOpenUrlParameters = nil; // clear after saved openurl delegate running #if BUILD_EMBEDDED_APP // tell the embedded app that the while 1 loop is going FEmbeddedCallParamsHelper Helper; Helper.Command = TEXT("engineisrunning"); FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("native")).Broadcast(Helper); #endif #if WITH_ACCESSIBILITY // Initialize accessibility code if VoiceOver is enabled. This must happen after Slate has been initialized. dispatch_async(dispatch_get_main_queue(), ^{ if (UIAccessibilityIsVoiceOverRunning()) { [[IOSAppDelegate GetDelegate] OnVoiceOverStatusChanged]; } }); #endif while( !IsEngineExitRequested() ) { if (self.bIsSuspended) { FAppEntry::SuspendTick(); // 其中会销毁RenderThread self.bHasSuspended = true; } else { bool bOtherAudioPlayingNow = [self IsBackgroundAudioPlaying]; if (bOtherAudioPlayingNow != self.bLastOtherAudioPlaying || self.bForceEmitOtherAudioPlaying) { FGraphEventRef UserMusicInterruptTask = FFunctionGraphTask::CreateAndDispatchWhenReady([bOtherAudioPlayingNow]() { //NSLog(@"UserMusicInterrupt Change: %s", bOtherAudioPlayingNow ? "playing" : "paused"); FCoreDelegates::UserMusicInterruptDelegate.Broadcast(bOtherAudioPlayingNow); }, TStatId(), NULL, ENamedThreads::GameThread); self.bLastOtherAudioPlaying = bOtherAudioPlayingNow; self.bForceEmitOtherAudioPlaying = false; } int OutputVolume = [self GetAudioVolume]; bool bMuted = false; #if USE_MUTE_SWITCH_DETECTION SharkfoodMuteSwitchDetector* MuteDetector = [SharkfoodMuteSwitchDetector shared]; bMuted = MuteDetector.isMute; if (bMuted != self.bLastMutedState || self.bForceEmitMutedState) { FGraphEventRef AudioMuteTask = FFunctionGraphTask::CreateAndDispatchWhenReady([bMuted, OutputVolume]() { //NSLog(@"Audio Session %s", bMuted ? "MUTED" : "UNMUTED"); FCoreDelegates::AudioMuteDelegate.Broadcast(bMuted, OutputVolume); }, TStatId(), NULL, ENamedThreads::GameThread); self.bLastMutedState = bMuted; self.bForceEmitMutedState = false; } #endif if (OutputVolume != self.LastVolume || self.bForceEmitVolume) { FGraphEventRef AudioMuteTask = FFunctionGraphTask::CreateAndDispatchWhenReady([bMuted, OutputVolume]() { //NSLog(@"Audio Volume: %d", OutputVolume); FCoreDelegates::AudioMuteDelegate.Broadcast(bMuted, OutputVolume); }, TStatId(), NULL, ENamedThreads::GameThread); self.LastVolume = OutputVolume; self.bForceEmitVolume = false; } FAppEntry::Tick(); // ① 如果RenderThread被销毁,会再创建出来;② 执行GEngineLoop.Tick // free any autoreleased objects every once in awhile to keep memory use down (strings, splash screens, etc) if (((GFrameCounter) & 31) == 0) { // If you crash upon release, turn on Zombie Objects (Edit Scheme... | Diagnostics | Zombie Objects) // This will list the last object sent the release message, which will help identify the double free [AutoreleasePool release]; AutoreleasePool = [[NSAutoreleasePool alloc] init]; } } // drain the async task queue from the game thread [FIOSAsyncTask ProcessAsyncTasks]; // 在GameThread中执行FIOSAsyncTask任务 } dispatch_sync(dispatch_get_main_queue(),^ { [UIApplication sharedApplication].idleTimerDisabled = NO; }); [AutoreleasePool release]; FAppEntry::Shutdown(); // 执行GEngineLoop.Exit self.bHasStarted = false; if(bForceExit || FApp::IsUnattended()) { _Exit(0); //exit(0); // As far as I can tell we run into a lot of trouble trying to run static destructors, so this is a no go :( } }
FAppEntry辅助静态类
/** UnrealEngine\Engine\Source\Runtime\Launch\Private\IOS\LaunchIOS.cpp */ void FAppEntry::Suspend(bool bIsInterrupt) { // also treats interrupts BEFORE initializing the engine // the movie player gets initialized on the preinit phase, ApplicationHasEnteredForegroundDelegate and ApplicationWillEnterBackgroundDelegate are not yet available if (GetMoviePlayer()) { GetMoviePlayer()->Suspend(); } // if background audio is active, then we don't want to do suspend any audio if ([[IOSAppDelegate GetDelegate] IsFeatureActive:EAudioFeature::BackgroundAudio] == false) { if (GEngine && GEngine->GetMainAudioDevice() && !IsEngineExitRequested()) { FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); if (bIsInterrupt && DisableAudioSuspendOnAudioInterruptCvar) { if (FTaskGraphInterface::IsRunning() && !IsEngineExitRequested()) { FFunctionGraphTask::CreateAndDispatchWhenReady([]() { FAudioThread::RunCommandOnAudioThread([]() { if (GEngine && GEngine->GetMainAudioDevice()) { GEngine->GetMainAudioDevice()->SetTransientMasterVolume(0.0f); } }, TStatId()); }, TStatId(), NULL, ENamedThreads::GameThread); } else { AudioDevice->SetTransientMasterVolume(0.0f); } } else { if (AudioContextResumeTime == 0) { // wait 0.5 sec before restarting the audio on resume // another Suspend event may occur when pulling down the notification center (Suspend-Resume-Suspend) AudioContextResumeTime = FPlatformTime::Seconds() + cMaxAudioContextResumeDelay; } else { //second resume, restart the audio immediately after resume AudioContextResumeTime = 0; } if (FTaskGraphInterface::IsRunning()) { FGraphEventRef ResignTask = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { FAudioThread::RunCommandOnAudioThread([]() { if (GEngine && GEngine->GetMainAudioDevice()) { GEngine->GetMainAudioDevice()->SuspendContext(); } }, TStatId()); FAudioCommandFence AudioCommandFence; AudioCommandFence.BeginFence(); AudioCommandFence.Wait(); }, TStatId(), NULL, ENamedThreads::GameThread); float BlockTime = [[IOSAppDelegate GetDelegate] GetBackgroundingMainThreadBlockTime]; // Do not wait forever for this task to complete since the game thread may be stuck on waiting for user input from a modal dialog box FEmbeddedCommunication::KeepAwake(TEXT("Background"), false); double startTime = FPlatformTime::Seconds(); while((FPlatformTime::Seconds() - startTime) < BlockTime) { FPlatformProcess::Sleep(0.05f); if(ResignTask->IsComplete()) { break; } } FEmbeddedCommunication::AllowSleep(TEXT("Background")); } else { AudioDevice->SuspendContext(); } } } else { // Increment IncrementAudioSuspendCounters(); } } } void FAppEntry::Resume(bool bIsInterrupt) { if (GetMoviePlayer()) { GetMoviePlayer()->Resume(); } // if background audio is active, then we don't want to do suspend any audio // @todo: should this check if we were suspended, in case this changes while in the background? (suspend with background off, but resume with background audio on? is that a thing?) if ([[IOSAppDelegate GetDelegate] IsFeatureActive:EAudioFeature::BackgroundAudio] == false) { if (GEngine && GEngine->GetMainAudioDevice()) { FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice(); if (bIsInterrupt && DisableAudioSuspendOnAudioInterruptCvar) { if (FTaskGraphInterface::IsRunning()) { FFunctionGraphTask::CreateAndDispatchWhenReady([]() { FAudioThread::RunCommandOnAudioThread([]() { if (GEngine && GEngine->GetMainAudioDevice()) { GEngine->GetMainAudioDevice()->SetTransientMasterVolume(1.0f); } }, TStatId()); }, TStatId(), NULL, ENamedThreads::GameThread); } else { AudioDevice->SetTransientMasterVolume(1.0f); } } else { if (AudioContextResumeTime != 0) { // resume audio on Tick() AudioContextResumeTime = FPlatformTime::Seconds() + cMaxAudioContextResumeDelay; } else { // resume audio immediately ResumeAudioContext(); } } } else { // Decrement DecrementAudioSuspendCounters(); } } } void FAppEntry::Init() { SCOPED_BOOT_TIMING("FAppEntry::Init()"); FPlatformProcess::SetRealTimeMode(); //extern TCHAR GCmdLine[16384]; GEngineLoop.PreInit(FCommandLine::Get()); // initialize messaging subsystem FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging"); //Set up the message handling to interface with other endpoints on our end. NSLog(@"%s", "Initializing ULD Communications in game mode\n"); GCommandSystem.Init(); GLog->SetCurrentThreadAsMasterThread(); // Send the launch local notification to the local notification service now that the engine module system has been initialized if(gAppLaunchedWithLocalNotification) { ILocalNotificationService* notificationService = NULL; // Get the module name from the .ini file FString ModuleName; GConfig->GetString(TEXT("LocalNotification"), TEXT("DefaultPlatformService"), ModuleName, GEngineIni); if (ModuleName.Len() > 0) { // load the module by name retrieved from the .ini ILocalNotificationModule* module = FModuleManager::LoadModulePtr<ILocalNotificationModule>(*ModuleName); // does the module exist? if (module != nullptr) { notificationService = module->GetLocalNotificationService(); if(notificationService != NULL) { notificationService->SetLaunchNotification(gLaunchLocalNotificationActivationEvent, gLaunchLocalNotificationFireDate); } } } } // start up the engine GEngineLoop.Init(); #if !UE_BUILD_SHIPPING UE_LOG(LogInit, Display, TEXT("Initializing TCPConsoleListener.")); if (ConsoleListener == nullptr) { FIPv4Endpoint ConsoleTCP(FIPv4Address::InternalLoopback, 8888); //TODO: @csulea read this from some .ini ConsoleListener = new TcpConsoleListener(ConsoleTCP); } #endif // UE_BUILD_SHIPPING } #if BUILD_EMBEDDED_APP // 该宏在iOS下为0 static bool GWasTickSuspended = false; static double GPreviousSuspendTime = FPlatformTime::Seconds(); #else static FSuspendRenderingThread* SuspendThread = NULL; #endif void FAppEntry::Tick() { #if BUILD_EMBEDDED_APP // 该宏在iOS下为0 if (GWasTickSuspended) { FPlatformProcess::SetRealTimeMode(); GWasTickSuspended = false; } #else if (SuspendThread != NULL) { delete SuspendThread; // 销毁FSuspendRenderingThread对象,会在析构函数中重新创建出RenderThread SuspendThread = NULL; FPlatformProcess::SetRealTimeMode(); } #endif if (AudioContextResumeTime != 0) { if (FPlatformTime::Seconds() >= AudioContextResumeTime) { ResumeAudioContext(); AudioContextResumeTime = 0; } } // tick the engine GEngineLoop.Tick(); } void FAppEntry::SuspendTick() { #if BUILD_EMBEDDED_APP // 该宏在iOS下为0 static double PreviousTime = FPlatformTime::Seconds(); if (!GWasTickSuspended) { GWasTickSuspended = true; // reset it each time we background PreviousTime = FPlatformTime::Seconds(); } float DeltaTime = FPlatformTime::Seconds() - PreviousTime; PreviousTime = FPlatformTime::Seconds(); // allow for some background processing FEmbeddedCommunication::TickGameThread(DeltaTime); FCoreDelegates::MobileBackgroundTickDelegate.Broadcast(DeltaTime); #else if (!SuspendThread) { SuspendThread = new FSuspendRenderingThread(true); // 重新创建FSuspendRenderingThread对象,会在构造函数中销毁RenderThread } #endif FPlatformProcess::Sleep(0.1f); } void FAppEntry::Shutdown() { if (ConsoleListener) { delete ConsoleListener; } NSLog(@"%s", "Shutting down Game ULD Communications\n"); GCommandSystem.Shutdown(); // kill the engine GEngineLoop.Exit(); }
切后台
游戏在主线程会收到applicationWillResignActive的回调
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 7.1 * frame #0: 0x000000010a7e8d28 MyGame`-[IOSAppDelegate applicationWillResignActive:](self=0x000000028003bc20, _cmd="applicationWillResignActive:", application=0x0000000142d37fa0) at IOSAppDelegate.cpp:2119:19 frame #1: 0x000000018412dbd4 UIKitCore`-[UIApplication _deactivateForReason:notify:] + 1352 frame #2: 0x000000018420935c UIKitCore`-[_UISceneLifecycleMultiplexer _performBlock:withApplicationOfDeactivationReasons:fromReasons:] + 212 frame #3: 0x0000000184449f8c UIKitCore`-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] + 760 frame #4: 0x00000001843d5710 UIKitCore`-[_UISceneLifecycleMultiplexer uiScene:transitionedFromState:withTransitionContext:] + 340 frame #5: 0x0000000183fbe430 UIKitCore`__186-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:]_block_invoke + 196 frame #6: 0x000000018407e350 UIKitCore`+[BSAnimationSettings(UIKit) tryAnimatingWithSettings:actions:completion:] + 892 frame #7: 0x0000000183fc0340 UIKitCore`_UISceneSettingsDiffActionPerformChangesWithTransitionContext + 276 frame #8: 0x00000001840aadf4 UIKitCore`-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] + 384 frame #9: 0x0000000184532260 UIKitCore`__64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.608 + 776 frame #10: 0x0000000183feec60 UIKitCore`-[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] + 256 frame #11: 0x0000000184032524 UIKitCore`-[UIScene scene:didUpdateWithDiff:transitionContext:completion:] + 288 frame #12: 0x0000000183fbc370 UIKitCore`-[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] + 492 frame #13: 0x00000001934dc100 FrontBoardServices`-[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] + 528 frame #14: 0x00000001934f4d4c FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 + 152 frame #15: 0x00000001934d96b4 FrontBoardServices`-[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 240 frame #16: 0x00000001934dfb10 FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke + 396 frame #17: 0x00000001426423b4 libdispatch.dylib`_dispatch_client_callout + 20 frame #18: 0x0000000142645e70 libdispatch.dylib`_dispatch_block_invoke_direct + 364 frame #19: 0x00000001934daf94 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 48 frame #20: 0x00000001934da3d4 FrontBoardServices`-[FBSSerialQueue _targetQueue_performNextIfPossible] + 220 frame #21: 0x00000001934de9e4 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 28 frame #22: 0x0000000181a23020 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 frame #23: 0x0000000181a33ce0 CoreFoundation`__CFRunLoopDoSource0 + 208 frame #24: 0x000000018196dfe8 CoreFoundation`__CFRunLoopDoSources0 + 268 frame #25: 0x00000001819737f4 CoreFoundation`__CFRunLoopRun + 820 frame #26: 0x00000001819873b8 CoreFoundation`CFRunLoopRunSpecific + 600 frame #27: 0x000000019d31738c GraphicsServices`GSEventRunModal + 164 frame #28: 0x00000001843276a8 UIKitCore`-[UIApplication _run] + 1100 frame #29: 0x00000001840a67f4 UIKitCore`UIApplicationMain + 2092 frame #30: 0x0000000104b0a86c MyGame`main(argc=1, argv=0x000000016ee53780) at LaunchIOS.cpp:606:13 frame #31: 0x0000000142515a24 dyld`start + 520
applicationWillResignActive回调逻辑:
/** UnrealEngine\Engine\Source\Runtime\ApplicationCore\Private\IOS\IOSAppDelegate.cpp */ FCriticalSection RenderSuspend; - (void)applicationWillResignActive:(UIApplication *)application { FIOSPlatformMisc::ResetBrightness(); /* Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. */ if (bEngineInit) // 引擎是否已初始化 { FEmbeddedCommunication::KeepAwake(TEXT("Background"), false); FGraphEventRef ResignTask = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { UE_LOG(LogTemp, Display, TEXT("Calling Delegate")); FCoreDelegates::ApplicationWillDeactivateDelegate.Broadcast(); // 在GameThread中发送ApplicationWillDeactivateDelegate代理 FEmbeddedCommunication::AllowSleep(TEXT("Background")); }, TStatId(), NULL, ENamedThreads::GameThread); // 同步等待 // Do not wait forever for this task to complete since the game thread may be stuck on waiting for user input from a modal dialog box double startTime = FPlatformTime::Seconds(); while((FPlatformTime::Seconds() - startTime) < [self GetBackgroundingMainThreadBlockTime]) { FPlatformProcess::Sleep(0.05f); if(ResignTask->IsComplete()) { UE_LOG(LogTemp, Display, TEXT("Task was completed before time.")); break; } } UE_LOG(LogTemp, Display, TEXT("Done with entering background tasks time.")); } // fix for freeze on tvOS, moving to applicationDidEnterBackground. Not making the changes for iOS platforms as the bug does not happen and could bring some side effets. #if !PLATFORM_TVOS [self ToggleSuspend:true]; // 暂停MoviePlayer、暂停Audio #endif [self ToggleAudioSession:false]; RenderSuspend.TryLock(); // 主线程先拿住锁 if (FTaskGraphInterface::IsRunning()) { if (bEngineInit) // 引擎是否已初始化 { FGraphEventRef ResignTask = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { FScopeLock ScopeLock(&RenderSuspend); // 由于主线程已经拿住了锁,GameThread会卡住 }, TStatId(), NULL, ENamedThreads::GameThread); } else { FGraphEventRef ResignTask = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { FScopeLock ScopeLock(&RenderSuspend); // 由于主线程已经拿住了锁,RenderingThread会卡住 注:如果RenderingThread不存在,会自动Rollback回GameThread }, TStatId(), NULL, ENamedThreads::ActualRenderingThread); } } FIOSCoreDelegates::OnWillResignActive.Broadcast(); // 在主线程中发送ApplicationWillDeactivateDelegate代理 } bool GIsSuspended = 0; - (void)ToggleSuspend:(bool)bSuspend { self.bHasSuspended = !bSuspend; self.bIsSuspended = bSuspend; GIsSuspended = self.bIsSuspended; if (bSuspend) { FAppEntry::Suspend(); // 里面主要是暂停MoviePlayer和Audio } else { FIOSPlatformRHIFramePacer::Resume(); FAppEntry::Resume(); // 里面主要是重启MoviePlayer和Audio } if (IOSView && IOSView->bIsInitialized) { // Don't deadlock here because a msg box may appear super early blocking the game thread and then the app may go into the background double startTime = FPlatformTime::Seconds(); // don't wait for FDefaultGameMoviePlayer::WaitForMovieToFinish(), crash with 0x8badf00d if "Wait for Movies to Complete" is checked FEmbeddedCommunication::KeepAwake(TEXT("Background"), false); while(!self.bHasSuspended && !FAppEntry::IsStartupMoviePlaying() && (FPlatformTime::Seconds() - startTime) < [self GetBackgroundingMainThreadBlockTime]) { FIOSPlatformRHIFramePacer::Suspend(); FPlatformProcess::Sleep(0.05f); } FEmbeddedCommunication::AllowSleep(TEXT("Background")); } }
过几帧(为4帧)之后,在主线程会继续收到applicationDidEnterBackground的回调
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
* frame #0: 0x000000010a7e9668 MyGame`-[IOSAppDelegate applicationDidEnterBackground:](self=0x000000028003bc20, _cmd="applicationDidEnterBackground:", application=0x0000000142d37fa0) at IOSAppDelegate.cpp:2195:16
frame #1: 0x0000000184074c60 UIKitCore`__47-[UIApplication _applicationDidEnterBackground]_block_invoke + 232
frame #2: 0x0000000183fd4a08 UIKitCore`+[UIViewController _performWithoutDeferringTransitionsAllowingAnimation:actions:] + 164
frame #3: 0x000000018420d048 UIKitCore`-[UIApplication _applicationDidEnterBackground] + 144
frame #4: 0x0000000184002da4 UIKitCore`__101-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:]_block_invoke_2 + 840
frame #5: 0x0000000183fbd62c UIKitCore`_UIScenePerformActionsWithLifecycleActionMask + 104
frame #6: 0x00000001840e8b7c UIKitCore`__101-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:]_block_invoke + 224
frame #7: 0x0000000184209380 UIKitCore`-[_UISceneLifecycleMultiplexer _performBlock:withApplicationOfDeactivationReasons:fromReasons:] + 248
frame #8: 0x0000000184449f8c UIKitCore`-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] + 760
frame #9: 0x00000001843d5710 UIKitCore`-[_UISceneLifecycleMultiplexer uiScene:transitionedFromState:withTransitionContext:] + 340
frame #10: 0x0000000183fbe430 UIKitCore`__186-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:]_block_invoke + 196
frame #11: 0x000000018407e350 UIKitCore`+[BSAnimationSettings(UIKit) tryAnimatingWithSettings:actions:completion:] + 892
frame #12: 0x0000000183fc0340 UIKitCore`_UISceneSettingsDiffActionPerformChangesWithTransitionContext + 276
frame #13: 0x00000001840aadf4 UIKitCore`-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] + 384
frame #14: 0x0000000184532260 UIKitCore`__64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.608 + 776
frame #15: 0x0000000183feec60 UIKitCore`-[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] + 256
frame #16: 0x0000000184032524 UIKitCore`-[UIScene scene:didUpdateWithDiff:transitionContext:completion:] + 288
frame #17: 0x0000000183fbc370 UIKitCore`-[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] + 492
frame #18: 0x00000001934dc100 FrontBoardServices`-[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] + 528
frame #19: 0x00000001934f4d4c FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 + 152
frame #20: 0x00000001934d96b4 FrontBoardServices`-[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 240
frame #21: 0x00000001934dfb10 FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke + 396
frame #22: 0x00000001426423b4 libdispatch.dylib`_dispatch_client_callout + 20
frame #23: 0x0000000142645e70 libdispatch.dylib`_dispatch_block_invoke_direct + 364
frame #24: 0x00000001934daf94 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 48
frame #25: 0x00000001934da3d4 FrontBoardServices`-[FBSSerialQueue _targetQueue_performNextIfPossible] + 220
frame #26: 0x00000001934de9e4 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 28
frame #27: 0x0000000181a23020 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
frame #28: 0x0000000181a33ce0 CoreFoundation`__CFRunLoopDoSource0 + 208
frame #29: 0x000000018196dfe8 CoreFoundation`__CFRunLoopDoSources0 + 268
frame #30: 0x00000001819737f4 CoreFoundation`__CFRunLoopRun + 820
frame #31: 0x00000001819873b8 CoreFoundation`CFRunLoopRunSpecific + 600
frame #32: 0x000000019d31738c GraphicsServices`GSEventRunModal + 164
frame #33: 0x00000001843276a8 UIKitCore`-[UIApplication _run] + 1100
frame #34: 0x00000001840a67f4 UIKitCore`UIApplicationMain + 2092
frame #35: 0x0000000104b0a86c MyGame`main(argc=1, argv=0x000000016ee53780) at LaunchIOS.cpp:606:13
frame #36: 0x0000000142515a24 dyld`start + 520
applicationDidEnterBackground回调逻辑:
/** UnrealEngine\Engine\Source\Runtime\ApplicationCore\Private\IOS\IOSAppDelegate.cpp */ - (void)applicationDidEnterBackground:(UIApplication *)application { /* Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. */ // fix for freeze on tvOS, moving to applicationDidEnterBackground. Not making the changes for iOS platforms as the bug does not happen and could bring some side effets. #if PLATFORM_TVOS [self ToggleSuspend:true]; #endif FEmbeddedCommunication::KeepAwake(TEXT("Background"), false); [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) // 该Task在GameThread中执行,详见游戏主循环中的[FIOSAsyncTask ProcessAsyncTasks] { // the audio context should resume immediately after interrupt, if suspended FAppEntry::ResetAudioContextResumeTime(); FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Broadcast(); // GameThread中发出ApplicationWillEnterBackgroundDelegate代理 FEmbeddedCommunication::AllowSleep(TEXT("Background")); return true; }]; }
切完后台后,GameThread会阻塞在释放了cpu的某个等待上(如:Sleep或Lock),具体的位置不固定,如下:
切回前台
游戏在主线程会收到applicationWillEnterForeground的回调
* thread #1, stop reason = breakpoint 5.1
* frame #0: 0x000000010a7e9740 MyGame`-[IOSAppDelegate applicationWillEnterForeground:](self=0x000000028003bc20, _cmd="applicationWillEnterForeground:", application=0x0000000142d37fa0) at IOSAppDelegate.cpp:2216:16
frame #1: 0x0000000184373350 UIKitCore`-[UIApplication _sendWillEnterForegroundCallbacks] + 224
frame #2: 0x00000001840030dc UIKitCore`__101-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:]_block_invoke_2 + 1664
frame #3: 0x0000000183fbd62c UIKitCore`_UIScenePerformActionsWithLifecycleActionMask + 104
frame #4: 0x00000001840e8b7c UIKitCore`__101-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:]_block_invoke + 224
frame #5: 0x0000000184209380 UIKitCore`-[_UISceneLifecycleMultiplexer _performBlock:withApplicationOfDeactivationReasons:fromReasons:] + 248
frame #6: 0x0000000184449f8c UIKitCore`-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] + 760
frame #7: 0x00000001843d5710 UIKitCore`-[_UISceneLifecycleMultiplexer uiScene:transitionedFromState:withTransitionContext:] + 340
frame #8: 0x0000000183fbe430 UIKitCore`__186-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:]_block_invoke + 196
frame #9: 0x000000018407e350 UIKitCore`+[BSAnimationSettings(UIKit) tryAnimatingWithSettings:actions:completion:] + 892
frame #10: 0x0000000183fc0340 UIKitCore`_UISceneSettingsDiffActionPerformChangesWithTransitionContext + 276
frame #11: 0x00000001840aadf4 UIKitCore`-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] + 384
frame #12: 0x0000000184532260 UIKitCore`__64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.608 + 776
frame #13: 0x0000000183feec60 UIKitCore`-[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] + 256
frame #14: 0x0000000184032524 UIKitCore`-[UIScene scene:didUpdateWithDiff:transitionContext:completion:] + 288
frame #15: 0x0000000183fbc370 UIKitCore`-[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] + 492
frame #16: 0x00000001934dc100 FrontBoardServices`-[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] + 528
frame #17: 0x00000001934f4d4c FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 + 152
frame #18: 0x00000001934d96b4 FrontBoardServices`-[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 240
frame #19: 0x00000001934dfb10 FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke + 396
frame #20: 0x00000001426423b4 libdispatch.dylib`_dispatch_client_callout + 20
frame #21: 0x0000000142645e70 libdispatch.dylib`_dispatch_block_invoke_direct + 364
frame #22: 0x00000001934daf94 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 48
frame #23: 0x00000001934da3d4 FrontBoardServices`-[FBSSerialQueue _targetQueue_performNextIfPossible] + 220
frame #24: 0x00000001934de9e4 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 28
frame #25: 0x0000000181a23020 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
frame #26: 0x0000000181a33ce0 CoreFoundation`__CFRunLoopDoSource0 + 208
frame #27: 0x000000018196dfe8 CoreFoundation`__CFRunLoopDoSources0 + 268
frame #28: 0x00000001819737f4 CoreFoundation`__CFRunLoopRun + 820
frame #29: 0x00000001819873b8 CoreFoundation`CFRunLoopRunSpecific + 600
frame #30: 0x000000019d31738c GraphicsServices`GSEventRunModal + 164
frame #31: 0x00000001843276a8 UIKitCore`-[UIApplication _run] + 1100
frame #32: 0x00000001840a67f4 UIKitCore`UIApplicationMain + 2092
frame #33: 0x0000000104b0a86c MyGame`main(argc=1, argv=0x000000016ee53780) at LaunchIOS.cpp:606:13
frame #34: 0x0000000142515a24 dyld`start + 520
applicationWillEnterForeground回调逻辑:
/** UnrealEngine\Engine\Source\Runtime\ApplicationCore\Private\IOS\IOSAppDelegate.cpp */ - (void)applicationWillEnterForeground:(UIApplication *)application { FEmbeddedCommunication::KeepAwake(TEXT("Background"), false); /* Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. */ [FIOSAsyncTask CreateTaskWithBlock : ^ bool(void) // 该Task在GameThread中执行,详见游戏主循环中的[FIOSAsyncTask ProcessAsyncTasks] { // the audio context should resume immediately after interrupt, if suspended FAppEntry::ResetAudioContextResumeTime(); FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Broadcast(); // GameThread中发出ApplicationHasEnteredForegroundDelegate代理 FEmbeddedCommunication::AllowSleep(TEXT("Background")); return true; }]; }
在同一帧,在主线程会继续收到applicationDidBecomeActive的回调
* frame #0: 0x000000010a7e9810 MyGame`-[IOSAppDelegate applicationDidBecomeActive:](self=0x000000028003bc20, _cmd="applicationDidBecomeActive:", application=0x0000000142d37fa0) at IOSAppDelegate.cpp:2242:16
frame #1: 0x00000001840e0f68 UIKitCore`-[UIApplication _stopDeactivatingForReason:] + 1448
frame #2: 0x00000001842093d8 UIKitCore`-[_UISceneLifecycleMultiplexer _performBlock:withApplicationOfDeactivationReasons:fromReasons:] + 336
frame #3: 0x0000000184449f8c UIKitCore`-[_UISceneLifecycleMultiplexer _evalTransitionToSettings:fromSettings:forceExit:withTransitionStore:] + 760
frame #4: 0x00000001843d5710 UIKitCore`-[_UISceneLifecycleMultiplexer uiScene:transitionedFromState:withTransitionContext:] + 340
frame #5: 0x0000000183fbe430 UIKitCore`__186-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:]_block_invoke + 196
frame #6: 0x000000018407e350 UIKitCore`+[BSAnimationSettings(UIKit) tryAnimatingWithSettings:actions:completion:] + 892
frame #7: 0x0000000183fc0340 UIKitCore`_UISceneSettingsDiffActionPerformChangesWithTransitionContext + 276
frame #8: 0x00000001840aadf4 UIKitCore`-[_UIWindowSceneFBSSceneTransitionContextDrivenLifecycleSettingsDiffAction _performActionsForUIScene:withUpdatedFBSScene:settingsDiff:fromSettings:transitionContext:lifecycleActionType:] + 384
frame #9: 0x0000000184532260 UIKitCore`__64-[UIScene scene:didUpdateWithDiff:transitionContext:completion:]_block_invoke.608 + 776
frame #10: 0x0000000183feec60 UIKitCore`-[UIScene _emitSceneSettingsUpdateResponseForCompletion:afterSceneUpdateWork:] + 256
frame #11: 0x0000000184032524 UIKitCore`-[UIScene scene:didUpdateWithDiff:transitionContext:completion:] + 288
frame #12: 0x0000000183fbc370 UIKitCore`-[UIApplicationSceneClientAgent scene:handleEvent:withCompletion:] + 492
frame #13: 0x00000001934dc100 FrontBoardServices`-[FBSScene updater:didUpdateSettings:withDiff:transitionContext:completion:] + 528
frame #14: 0x00000001934f4d4c FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke_2 + 152
frame #15: 0x00000001934d96b4 FrontBoardServices`-[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 240
frame #16: 0x00000001934dfb10 FrontBoardServices`__94-[FBSWorkspaceScenesClient _queue_updateScene:withSettings:diff:transitionContext:completion:]_block_invoke + 396
frame #17: 0x00000001426423b4 libdispatch.dylib`_dispatch_client_callout + 20
frame #18: 0x0000000142645e70 libdispatch.dylib`_dispatch_block_invoke_direct + 364
frame #19: 0x00000001934daf94 FrontBoardServices`__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 48
frame #20: 0x00000001934da3d4 FrontBoardServices`-[FBSSerialQueue _targetQueue_performNextIfPossible] + 220
frame #21: 0x00000001934de9e4 FrontBoardServices`-[FBSSerialQueue _performNextFromRunLoopSource] + 28
frame #22: 0x0000000181a23020 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
frame #23: 0x0000000181a33ce0 CoreFoundation`__CFRunLoopDoSource0 + 208
frame #24: 0x000000018196dfe8 CoreFoundation`__CFRunLoopDoSources0 + 268
frame #25: 0x00000001819737f4 CoreFoundation`__CFRunLoopRun + 820
frame #26: 0x00000001819873b8 CoreFoundation`CFRunLoopRunSpecific + 600
frame #27: 0x000000019d31738c GraphicsServices`GSEventRunModal + 164
frame #28: 0x00000001843276a8 UIKitCore`-[UIApplication _run] + 1100
frame #29: 0x00000001840a67f4 UIKitCore`UIApplicationMain + 2092
frame #30: 0x0000000104b0a86c MyGame`main(argc=1, argv=0x000000016ee53780) at LaunchIOS.cpp:606:13
frame #31: 0x0000000142515a24 dyld`start + 520
applicationDidBecomeActive回调逻辑:
/** UnrealEngine\Engine\Source\Runtime\ApplicationCore\Private\IOS\IOSAppDelegate.cpp */ extern double GCStartTime; - (void)applicationDidBecomeActive:(UIApplication *)application { FIOSCoreDelegates::OnDidBecomeActive.Broadcast(); // make sure a GC will not timeout because it was started before entering background GCStartTime = FPlatformTime::Seconds(); /* Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. */ RenderSuspend.Unlock(); // 主线程释放锁,让GameThread恢复 [self ToggleSuspend : false]; // 恢复MoviePlayer、恢复Audio [self ToggleAudioSession:true]; if (bEngineInit) // 引擎是否已初始化 { FEmbeddedCommunication::KeepAwake(TEXT("Background"), false); FGraphEventRef ResignTask = FFunctionGraphTask::CreateAndDispatchWhenReady([]() { FCoreDelegates::ApplicationHasReactivatedDelegate.Broadcast(); // 在GameThread中发送ApplicationHasReactivatedDelegate代理 FEmbeddedCommunication::AllowSleep(TEXT("Background")); }, TStatId(), NULL, ENamedThreads::GameThread); // 同步等待 // Do not wait forever for this task to complete since the game thread may be stuck on waiting for user input from a modal dialog box double startTime = FPlatformTime::Seconds(); while((FPlatformTime::Seconds() - startTime) < [self GetBackgroundingMainThreadBlockTime]) { FPlatformProcess::Sleep(0.05f); if(ResignTask->IsComplete()) { break; } } } }