发现问题
最近在 Unity 上接入 Facebook 登录,在 iOS 上出现了一个诡异的问题。在弹出 Facebook 登录网页弹窗的时候,弹窗会自行消失,然后游戏界面卡死,无法响应任何点击。如图:
UIViewController 层级
在 lldb 下使用 po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
打印当前视图控制器的结构,得到如下:
1 | (lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy] |
可以看出来在 Unity 默认的控制器上面,还有一个 UIViewController
。而且游戏还在运行当中,后台的线程还在继续运行,只是有一个控制器覆盖在了上面,导致游戏页面卡住。
使用了旧的 Unity 项目,可以正常地调出了 Facebook 登录网页弹窗。如图。
在 lldb 下使用 po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
打印当前视图控制器的结构,得到如下:
1 | (lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy] |
在 Unity 默认控制器上面,是 SFAuthenticationViewController
和 SFBrowserRemoteViewController
,和现在的情况不一样。
对比 Unity 版本
新的项目和旧的项目使用的 Unity 版本不一样,新的项目是 Unity 2018.4.23f,而旧的是 Unity 2018.4.6f。虽然只是小版本更新,但是其中也有很多坑。
于是对比了两个版本生成的 Xcode 工程,发现 UnityAppController.mm
中有一些可疑的部分。
如图,左侧为 2018.4.6f,右侧为 2018.4.23f:
在 Unity 接收到 applicationWillResignActive:
这个系统委托回调时,游戏实现快照的方式有着本质区别。
- 2018.4.6f 版本,在游戏失去活跃时在 Unity 当前
UIView
添加了生成的快照UIView
,
1 | [_rootView addSubview: _snapshotView]; |
当游戏恢复活跃时再移除快照的 UIView
。
1 | [_snapshotView removeFromSuperview]; |
- 而 2018.4.23f 版本是,在游戏失去活跃时创建了一个快照的
UIViewController
,并展示:
1 | [_rootController presentViewController: _snapshotViewController animated: false completion: nil]; |
当游戏恢复活跃时再移除快照的 UIViewController
。
1 | [_snapshotViewController dismissViewControllerAnimated: NO completion: nil]; |
那么问题就出现了,Facebook 登录的网页弹窗,也是用 presentViewController
的方式,而调用弹窗的时候,会触发 applicationWillResignActive:
,结果就发生了冲突。在 Facebook 的弹窗出现后,就被 Unity 的快照 UIViewController
给顶掉了,就出现了开头的问题。
解决方案
最终的解决方案就是,不生成快照的 UIViewController
。
方案一,关闭 Render Extra Frame on Pause
Build Setting - iOS - Other Settings - Configuration - Render Extra Frame on Pause,将其设置为 false
,如图:
这个选项,官方的解释是:
Enable this option to issue an additional frame after the frame when the app is paused. This allows your app to show graphics that indicate the paused state when the app is going into the background.
当应用暂停时,额外渲染一帧,用于在进入后台时的展示。
关闭这个选项会使 UNITY_SNAPSHOT_VIEW_ON_APPLICATION_PAUSE
这个宏的值为 0
,在 Preprocessor.h
中定义。
进而影响到 UnityAppController+ViewHandling.mm
中的创建快照方法:
1 | - (UIView*)createSnapshotView |
使得 Unity 在暂停时不额外渲染出快照 UIView
,从而也不会展示快照的 UIViewController
,这样解决了问题。
1 | UIView* snapshotView = [self createSnapshotView]; |
方案二,设置 Behavior in Background
Build Setting - iOS - Other Settings - Configuration - Behavior in Background,将其由 Suspend
设置为 Custom
。如图:
修改后:
代码中关闭方式为:
1 | UnityEditor.PlayerSettings.iOS.appInBackgroundBehavior = UnityEditor.iOSAppInBackgroundBehavior.Custom; |
这个选项的官方解释是:
Choose what the application should do when the user presses the home button.
应用在用户按了 home 键后的操作。
分别对应三个选项:
- Custom. You can implement your own behaviour with background processing. For an example, see the BackgroundFetch Bitbucket project.
- 自定义:退到后台后自行实现操作。
- Suspend. Suspend the app but don’t quit. This is the default behavior.
- 挂起:退到后台后程序挂起,默认选项。
- Exit. Instead of suspending, let the app quit when the user presses the home button.
- 退出:退到后台后程序退出。
修改这个选项会影响 UnityGetUseCustomAppBackgroundBehavior()
这个方法的返回值,而在 UnityAppController.mm
中:
1 | // Pause Unity only if we don't need special background processing |
这样就不会展示快照的 UIViewController
,解决了问题。
参考链接
- Xcode调试——打印控制器层次结构和视图层次结构, Walden_tinghou。
- Player settings for the iOS platform, docs.unity3d.com.
- Facebook Login is not working on on iOS Unity 2018.4.22f1, github.com.
- Facebook Login broken in Unity 2019.3.13f1 but works in 2019.3.7f1, forum.unity.com.