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管理資源,則需要注意不要刪除所需的代碼。