0%

Unity 游戏在 iOS 上 Facebook 登录问题

发现问题

最近在 Unity 上接入 Facebook 登录,在 iOS 上出现了一个诡异的问题。在弹出 Facebook 登录网页弹窗的时候,弹窗会自行消失,然后游戏界面卡死,无法响应任何点击。如图:

UIViewController 层级

在 lldb 下使用 po [[[UIWindow keyWindow] rootViewController] _printHierarchy] 打印当前视图控制器的结构,得到如下:

1
2
3
(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
<UnityDefaultViewController 0x10af79f50>, state: disappeared, view: <UnityView 0x10ae19650> not in the window
+ <UIViewController 0x11040ceb0>, state: appeared, view: <_UIReplicantView 0x11040f4d0>, presented with: <_UIFullscreenPresentationController 0x10e313150>

可以看出来在 Unity 默认的控制器上面,还有一个 UIViewController。而且游戏还在运行当中,后台的线程还在继续运行,只是有一个控制器覆盖在了上面,导致游戏页面卡住。

使用了旧的 Unity 项目,可以正常地调出了 Facebook 登录网页弹窗。如图。

在 lldb 下使用 po [[[UIWindow keyWindow] rootViewController] _printHierarchy] 打印当前视图控制器的结构,得到如下:

1
2
3
4
(lldb) po [[[UIWindow keyWindow] rootViewController] _printHierarchy]
<UnityDefaultViewController 0x153e79f50>, state: appeared, view: <UnityView 0x153e78570>
+ <SFAuthenticationViewController 0x154029800>, state: appeared, view: <SFSafariView 0x154b102a0>, presented with: <_UIFormSheetPresentationController 0x154a111d0>
| | <SFBrowserRemoteViewController 0x155059a00>, state: appeared, view: <_UISizeTrackingView 0x15b821590>

在 Unity 默认控制器上面,是 SFAuthenticationViewControllerSFBrowserRemoteViewController,和现在的情况不一样。

对比 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
2
3
4
5
6
7
8
9
10
- (UIView*)createSnapshotView
{
// Note that on iPads with iOS 9 or later (up to iOS 10.2 at least) there's a bug in the iOS compositor: any use of -[UIView snapshotViewAfterScreenUpdates]
// causes black screen being shown temporarily when 4 finger gesture to swipe to another app in the task switcher is being performed slowly
#if UNITY_SNAPSHOT_VIEW_ON_APPLICATION_PAUSE
return [_rootView snapshotViewAfterScreenUpdates: YES];
#else
return nil;
#endif
}

使得 Unity 在暂停时不额外渲染出快照 UIView,从而也不会展示快照的 UIViewController,这样解决了问题。

1
2
3
4
5
6
7
8
9
10
UIView* snapshotView = [self createSnapshotView];

if (snapshotView != nil)
{
_snapshotViewController = [[UIViewController alloc] init];
_snapshotViewController.modalPresentationStyle = UIModalPresentationFullScreen;
_snapshotViewController.view = snapshotView;

[_rootController presentViewController: _snapshotViewController animated: false completion: nil];
}

方案二,设置 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
2
3
4
5
6
7
8
9
10
11
12
13
// Pause Unity only if we don't need special background processing
// otherwise batched player loop can be called to run user scripts.
if (!UnityGetUseCustomAppBackgroundBehavior())
{
// Force player to do one more frame, so scripts get a chance to render custom screen for minimized app in task manager.
// NB: UnityWillPause will schedule OnApplicationPause message, which will be sent normally inside repaint (unity player loop)
// NB: We will actually pause after the loop (when calling UnityPause).
UnityWillPause();
[self repaint];
UnityPause(1);

[self addSnapshotViewController];
}

这样就不会展示快照的 UIViewController,解决了问题。

参考链接