可可西

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;
            }
        }
    }
}

 

posted on 2024-04-19 00:19  可可西  阅读(158)  评论(0编辑  收藏  举报

导航