Version: Unity 6.0 (6000.0)
语言 : 中文
iOS 上的托管堆栈跟踪
报告 iOS 的崩溃问题

在 iOS 设备上进行故障排除

使用以下信息有助于找到相应的解决方案应对在 iOS 设备上运行 Unity 应用程序时的常见崩溃和其他问题。

注意:如果在故障排除后问题仍然存在,请参阅报告 iOS 崩溃错误

应用程序停止响应,Xcode 在状态栏中显示中断

以下列表提供了此问题的一些常见原因:

  • 脚本错误,例如使用未初始化的变量。
  • 使用了第三方 Thumb 编译的原生库。这些库可以在 iOS SDK 链接器中触发一个已知问题,并可能导致随机崩溃。
  • 使用具有值类型作为参数的泛型类型。例如,序列化脚本属性的 List<int>List<SomeStruct>List<SomeEnum>
  • 在启用托管代码剥离的情况下使用了反射。
  • 原生插件接口中的错误,即托管代码方法签名与原生代码函数签名不匹配的情况。

来自 Xcode 调试器控制台的信息通常有助于检测这些问题。从查看 (View) > ** 调试区 (Debug Area)** > 激活控制台 (Activate Console) 访问调试器控制台。

Xcode 控制台显示程序收到的信号:“SIGBUS”或 EXC_BAD_ACCESS 错误

在 iOS 设备上,当应用程序收到 NullReferenceException 时,通常会出现此消息。使用本机堆栈跟踪来查找故障发生的位置。

原生堆栈跟踪是一种实用的故障调查工具,但使用时需要一些专门知识。在这些硬件内存访问错误发生后,通常无法继续。要获取原生堆栈跟踪,请在 Xcode 调试器控制台中键入 bt all。检查输出的堆栈跟踪,因为这些堆栈跟踪可能包含有关错误发生位置的信息。例如,典型的堆栈跟踪可能如下所示:

...
Thread 1 (thread 11523): 

1. 0 0x006267d0 in m_OptionsMenu_Start ()
1. 1 0x002e4160 in wrapper_runtime_invoke_object_runtime_invoke_void__this___object_intptr_intptr_intptr ()
1. 2 0x00a1dd64 in mono_jit_runtime_invoke (method=0x18b63bc, obj=0x5d10cb0, params=0x0, exc=0x2fffdd34) at /Users/mantasp/work/unity/unity-mono/External/Mono/mono/mono/mini/mini.c:4487
1. 3 0x0088481c in MonoBehaviour::InvokeMethodOrCoroutineChecked ()
...

查找“Thread 1”(也就是主线程)的堆栈跟踪。该堆栈跟踪的起始几行将指向错误发生的位置。在此示例中,跟踪指出 NullReferenceException 发生在 _OptionsMenu_ 脚本的 _Start_ 方法中。检查此方法实现情况有助于揭示问题的原因。通常,如果对初始化顺序做出不正确的判断,_Start_ 中就会发生 NullReferenceExceptions。

调试器控制台上有时仅显示部分堆栈跟踪。例如:

Thread 1 (thread 11523): 

1. 0 0x0062564c in start ()

此消息表明原生符号在应用程序的发行版构建过程中被剥离。要进一步调查,请使用以下步骤访问完整的堆栈跟踪:

  1. 从设备中删除应用程序。
  2. 清除所有目标。
  3. 选择商品 (Product) > 运行 (Run)
  4. 按先前所述检索堆栈跟踪。

Xcode 控制台显示“WARNING -> applicationDidReceiveMemoryWarning()”,然后应用程序崩溃

您可能会遇到类似 Program received signal: "0" 的警告消息。此警告消息通常并不致命,表示 iOS 内存不足。通常情况下,像邮件这类后台进程会释放一些内存,这样你的应用程序就能继续运行。但是,如果应用程序继续使用内存或请求更多内存,iOS 将开始终止包括您自己的应用程序在内的应用程序。Apple 没有公布多大的内存使用量是安全的,但经验观察表明,使用不到 50% 的整体设备 RAM 的应用程序不会遇到严重的内存使用量问题。

主要指标是应用程序使用的 RAM 大小。应用程序内存使用量由以下部分构成:

Component 描述
应用程序代码 操作系统需要在 RAM 中加载和保留应用程序代码,但如果确实需要,可能会丢弃其中一些代码。
原生堆 由引擎用于在 RAM 中存储其状态、资源。
托管堆 __ il2cpp__种由 Unity 开发的脚本后端,可在为某些平台构建项目时替代 Mono。更多信息
See in Glossary
运行时用于存储 C# 对象。
金属内存池 用于存储纹理、帧缓冲区和编译的着色器。

您可以在 Xcode 中跟踪应用程序内存使用情况。有关更多信息,请参阅收集有关内存使用的信息(Apple 开发者)。

要保持较低的内存使用量,请使用以下建议:

  • 减小应用程序二进制大小:使用最强大的 iOS 剥离选项,并避免对不同 .NET 库的不必要依赖。有关更多信息,请参阅播放器设置和优化构建的 iOS Player 的大小
  • 减小内容的大小:对纹理进行 PVRTC 压缩并使用简单多边形模型。有关更多信息,请参阅减小文件大小
  • 不要在脚本中分配超过必要数量的内存:使用性能分析器 (Profiler) 窗口跟踪单声道堆的大小和使用情况。

向操作系统查询可用内存量看似是评估应用性能的最有效方式。然而,由于操作系统使用了大量的动态缓冲区和缓存,可用内存的统计数据很可能是不可靠的。建议跟踪应用程序的内存消耗情况,并将此作为主要指标,与 Xcode 内存工具结合使用,尤其是在加载新场景后。

应用程序从 Xcode 正确启动,但在设备上加载第一个场景时崩溃

建议检查设备日志以获得更多详细信息。要完成此操作,请使用以下步骤:

  1. 将目标设备连接到 macOS 设备。
  2. 在 Xcode 中,选择窗口 (Window) > 设备和模拟器 (Devices and Simulators)
  3. 在窗口的左侧工具栏中选择目标设备。
  4. 单击显示设备控制台 (Show the device console) 并查看最新消息。

您可能还需要调查崩溃报告。有关更多信息,请参阅获取崩溃报告和诊断日志 (Apple Developer)。

Xcode Organizer 控制台出现消息“killed by SpringBoard”

iOS 应用程序在渲染第一帧画面和处理输入时,有一个设定的时间限制。如果应用程序超过此限制,SpringBoard 会将其终止。例如,当应用程序中第一个场景的规模过大时可能会发生这种情况。建议创建一个带有启动画面的小型初始场景,等待几帧,然后继续加载更大的场景。要实现此目的,请使用以下示例:

IEnumerator Start() {
    yield return new WaitForEndOfFrame();
// Do not forget using UnityEngine.SceneManagement directive
    SceneManager.LoadScene("Test");
}

将 System.Security.Cryptography 与托管代码剥离组合使用时,设备上发生崩溃

.NET 加密服务与托管代码剥离不兼容。这些服务依赖于反射,而托管代码剥离涉及静态代码分析。通过向 Unity 项目的 Assets 文件夹中添加自定义的 link.xml 文件可以自定义剥离过程。指定要从剥离中排除的类型和命名空间。从剥离过程中排除 System.Security.Crypography 命名空间以帮助解决此问题。例如,向 link.xml 文件添加以下内容:

<linker>
       <assembly fullname="mscorlib">
               <namespace fullname="System.Security.Cryptography" preserve="all"/>
       </assembly>
</linker>

将 System.Security.Cryptography.MD5 与托管代码剥离一起使用时,应用程序崩溃

可以按照上一部分所述的相同方式解决此问题,也可以在脚本代码中添加对特定类的额外引用。要完成此操作,请使用以下示例:

object obj = new MD5CryptoServiceProvider();

通过原生函数来使用 Cocoa 时,发生“PlayerLoop called recursively!”错误

UI 中的某些操作会导致 iOS 立即重绘窗口。最常见的示例是,将具有 UIViewController 的 UIView 添加到主 UIWindow。如果从脚本中调用一个原生函数,那么这个调用会发生在 Unity 的 PlayerLoop 内部,从而导致 PlayerLoop 被递归调用。发生这种情况时,您将收到错误消息 PlayerLoop called recursively!。在这种情况下,请考虑使用 performSelectorOnMainThread (Apple Developer) 方法,并将 waitUntilDone 设置为 false。它会通知 iOS 将该操作安排在 Unity 的 PlayerLoop 调用之间运行。

性能分析器或调试器无法访问应用程序

要诊断此问题,请使用以下建议:

  • 检查是否进行了开发构建,并且启用了 脚本调试 (Script Debugging)自动连接性能分析器 (Autoconnect Profiler)。有关这些属性的更多信息,请参阅 iOS 构建设置参考
  • 在设备上运行的应用程序将在 UDP 端口 54997 上向 225.0.0.222 进行多播广播。检查你的网络设置是否允许此类网络流量通过。性能分析器将连接到远程设备上 55000 - 55511 范围内的端口,以便从设备中获取性能分析器数据。这些端口需要开放以便进行 TCP 访问。

Xcode 显示错误 ARM64 分支超出范围(<value> 最大值为 +/–128MB)

当构建的机器代码太大并且已达到 Xcode 限制时,会导致此问题。如果您有很多脚本代码,或者在构建中使用大型外部 .NET 程序集,则会发生这种情况。使用脚本调试 (Script Debugging) 构建设置也会增加此问题,因为它为每个函数创建额外的指令。

要解决此问题,请在 Unity 编辑器中导航到编辑 (Edit) > 项目设置 (Project Settings) > 播放器 (Player) > iOS,然后尝试以下一个或多个选项:

  • 启用剥离引擎代码 (Strip Engine Code)
  • 使用更高的托管剥离级别 (Managed Stripping Level)
  • IL2CPP 代码生成 (IL2CPP Code Generation) 设置为更快(更小)的构建 (Faster (smaller) builds)

如果问题仍然存在,建议将用户脚本代码拆分为多个程序集。例如,Plugins 文件夹可用于放置任何拆分代码,因为此位置的代码会添加到不同的程序集。有关特殊文件夹名称如何影响脚本编译的信息,建议参阅特殊文件夹和脚本编译顺序

在运行 Xcode 14.3 的基于 ARM 的 Mac 上,iOS 模拟器不可见

从 Xcode 14.3 版开始,Apple 引入了目标架构 (Destination Architecture) 选项。借助目标架构 (Destination Architecture),您可以在基于 ARM 的 Mac 上使用 iOS 模拟器,而无需在 Rosetta 模拟器模式下运行 Xcode。

要查看 iOS 模拟器,请使用以下步骤:

  1. 在 Xcode 菜单栏中,选择商品 (Product) > 目标 (Destination) > 目标架构 (Destination Architectures)
  2. 选择显示 Rosetta 目标 (Show Rosetta Destinations)显示两者 (Show Both) 以查看 Apple Silicon 和 Rosetta 架构的 iOS 模拟器。

其他资源

iOS 上的托管堆栈跟踪
报告 iOS 的崩溃问题