不要忽视Managed code stripping的副作用
0x00 前言
Unity 2018.3之后,新的“Managed Stripping Level”选项将替换 player settings 中原有的“Stripping Level”选项。 这个新的选项可用于所有平台以及Mono和IL2CPP脚本运行时。而这个功能的主要目的则是通过删除一些未使用的代码来减小应用程序的大小。 嗯,听起来不错,但这里存在一个潜在的副作用,即Unity如何知道哪些代码才是未使用的代码呢?
0x01 Load From Assetbundle 以及 “the script is missing”
但是,在继续讨论代码剔除流水线之前,让我们看一下由于代码剔除导致问题的一个情景。
现在,让我们在编辑器中创建一个Cube和一个Timeline资源。 使用Timeline,我们可以将Cube从A点移动到B点。
为了更好地管理和更新资源,我们将场景中的Cube制作为Prefab并将Cube本身从场景中删除。 然后,我们将这个prefab和Timeline资源制成AssetBundle,以便在运行时动态加载资源。
我们可以在编辑器中加载Assetbundle并实例化Prefab,然后我们可以看到Cube开始了从点A到点B的移动——Timeline的脚本生效了。
此时,这个工作流似乎工作正常: 从场景中删除资源,将它们放入Assetbundle中,并在运行时动态加载它们。
之后,我们将项目构建到iOS平台,运行相同的场景并加载相同的Assetbundle以在运行时创建Cube对象。 但是这次,Cube并没有开始按预期移动,并且我们从Unity收到了一条错误消息。
“The referenced script (UnityEngine.Timeline.AnimationTrack) on this Behaviour is missing!”
Timeline的脚本丢失了,并且导致了Cube无法在iOS平台上移动。
0x02 代码剔除流水线
丢失与Timeline相关的脚本的原因是,在构建iOS版本时Unity删除了相关代码。
首先要注意的是,iOS使用IL2CPP脚本运行时。 因为Apple的App Store不再接受Mono版本,并且iOS 11及更高版本也不支持Mono。 而选择IL2CPP脚本运行时的时候,“Managed code stripping”的“Disabled”选项将不可用。 这意味着当我们选择IL2CPP脚本运行时的结果就是无法关闭代码剔除。
要注意的第二件事是代码剔除流水线本身。 Unity的Build Pipeline会使用一个称为UnityLinker的工具来剔除托管代码。该过程的工作方式是定义root assemblies,然后使用静态代码分析来确定这些root assemblies还要使用哪些其他的托管代码。 之后删除所有无法访问的代码,即所谓的未使用代码。 其中Unity Engine的程序集也是有可能被剥离的。 root assemblies是Unity Editor根据用户脚本代码编译的程序集,例如Assembly-CSharp.dll。同时构建中包含的场景也会被视为root。
因此,在运行时从Assetbundle加载资产时,至少有3种方法可以避免类似脚本丢失的问题。
- 在构建的场景中引用所需的脚本,以防止在构建项目时剔除需要的代码。
如图,将PlayableDirector组件和一个空的Timeline对象添加到场景中。
2. 在脚本中引用所需的类,以防止在构建项目时剔除需要的代码。
3. 添加一个link.xml文件,以防止UnityLinker剔除所需的代码。
可以在下面的代码仓库中查看这三种方式的示例,4个分支分别代表了会被剔除代码(Master)以及3种防止代码被错误剔除的方式。
https://github.com/chenjd/CodeStripExample
0x03 结论
如果你开启了代码剔除功能以减小构建的包体大小,那么请留意Unity是否会剔除你在运行时所需的代码,例如反射相关的代码等等。 特别是对于那些使用IL2CPP脚本运行时的平台(例如iOS),默认情况下会启用代码剔除。 如果使用Assetbundle管理资源,则需要注意不要删除所需的代码。