更新

  • 2019/04/06 增加正确时机解释
  • 2017/12/17 初次发布

环境

  • Unity 2017.1.1f1
  • macOS 10.11.6
  • iOS 11.1
  • Android 5.1

问题

在一个有加载卸载资源的 UI 中,发现在某些情况下会导致 Unity 闪退,包括 iOS、Android、macOS 平台下 Unity 编辑器。 确定在一定的操作后必然发生,尝试精简重现步骤,最后只需要固定两步操作即可重现。

macOS 平台下 Unity 编辑器崩溃日志:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Assertion failed on expression: 'MecanimDataWasBuilt()'

(Filename: /Users/builduser/buildslave/unity/build/Runtime/Animation/AnimationClip.cpp Line: 1742)

Receiving unhandled NULL exception
Obtained 29 stack frames.
#0  0x0000010490db40 in typeinfo for EditorExtension
#1  0x00000101e78084 in mecanim::statemachine::SetStateMachineInInitialState(mecanim::statemachine::StateMachineConstant const&, mecanim::statemachine::StateMachineInput const&, mecanim::statemachine::StateMachineOutput&, mecanim::statemachine::StateMachineMemory&, mecanim::statemachine::StateMachineWorkspace&)
#2  0x00000101de949f in AnimatorControllerPlayable::GenerateGraph()
#3  0x00000101de6bb0 in AnimatorControllerPlayable::SetAnimatorController(RuntimeAnimatorController*)
#4  0x00000101d33bb5 in Animator::CreateInternalControllerPlayable()
#5  0x00000101d22d08 in Animator::CreateObject()
#6  0x00000101d254a1 in Animator::UpdateAvatars(dynamic_array<PlayableOutput*, 8ul> const&, bool, bool, bool)
#7  0x000001007ea46f in DirectorManager::ExecuteProcessCallbacks(DirectorStage)
#8  0x000001007e7dc0 in DirectorManager::InitializeClass()::PreLateUpdateDirectorUpdateAnimationBeginRegistrator::Forward()
#9  0x00000100ab592b in PlayerLoop()
#10 0x00000101834a74 in PlayerLoopController::UpdateScene(bool)
#11 0x0000010183046c in PlayerLoopController::UpdateSceneIfNeeded()
#12 0x0000010182f7a4 in Application::TickTimer()
#13 0x007fffbff9de0f in __NSFireTimer
#14 0x007fffbe513c54 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
#15 0x007fffbe5138df in __CFRunLoopDoTimer
#16 0x007fffbe51343a in __CFRunLoopDoTimers
#17 0x007fffbe50ab81 in __CFRunLoopRun
#18 0x007fffbe50a114 in CFRunLoopRunSpecific
#19 0x007fffbda6aebc in RunCurrentEventLoopInMode
#20 0x007fffbda6acf1 in ReceiveNextEventCommon
#21 0x007fffbda6ab26 in _BlockUntilNextEventMatchingListInModeWithFilter
#22 0x007fffbc003a54 in _DPSNextEvent
#23 0x007fffbc77f7ee in -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:]
#24 0x007fffbbff83db in -[NSApplication run]
#25 0x007fffbbfc2e0e in NSApplicationMain
#26 0x0000010044be37 in EditorMain(int, char const**)
#27 0x0000010044c2b9 in main
#28 0x007fffd3c92235 in start
Launching bug reporter

分析

由于崩溃堆栈中出现了 AnimatorControllerPlayable::SetAnimatorController 调用,尝试在崩溃操作前将场景内所有 Animator 组件禁用。

结果发现在崩溃操作后编辑器没有崩溃,仔细查看场景,发现一个模型已经消失不见。

解决

由于资源管理是使用 AssetBundle,所以当资源消失时优先查找是否是由 AssetBundle.Unload(true) 导致,在日志中输出后发现的确如此。

所以最终是由于游戏仍在使用 Animator 及其动画,如果 AssetBundle.Unload(true) 将所有资源卸载会导致资源重建,结果就是之前的崩溃问题了。

解决方法也很简单,在正确的时机调用 AssetBundle.Unload(true) 就可以了。

有评论问到:正确的时机是指什么?
其实很简单,就是资源不再被使用的时候。

注意,卸载的时候要分两步:

  1. 销毁 Destroy 场景中实例化 Instantiate 出来的 GameObject,确保场景中没有对象引用到资源
  2. 执行 AssetBundle.Unload(true),使用 AssetBundle 内部的引用追踪将所有从当前 AssetBundle 中加载的资源卸载掉