參考鏈接:
Unity-Managed bytecode stripping with IL2CPP
簡而言之,Code Strip就是代碼裁剪。開啟代碼裁剪,能夠在build時將項目中沒有用到的代碼裁減掉,以減少build出的代碼量。
如果使用了il2cpp,由于build過程中先生成CIL然后再生成cpp代碼。所以開啟Managed code stripping之后,能減少CIL的生成,從而加快cpp生成的過程。
Strip的主要作用在于裁剪系統庫、Unity引擎代碼、插件代碼,往往它們包含了大量對工程無用的代碼。
開啟方法
在Unity編輯器的File->Build Settings->Player Settings可以設置Managed Stripping Level。
(PC平臺)有4個選項可供選擇:Disable(默認),Low,Medium,High。
(iOS平臺)沒有Disable選項,默認為Low,但提供了Strip Engine Code的選項。
各個Level對不同代碼的裁剪規則不同,具體內容可以看上面第一個鏈接。
強制保留
因為UnityLinker進行strip的邏輯原因,代碼中使用反射調用(沒有直接引用)的類和屬性等也會被裁剪掉,這樣會導致運行時錯誤。
Unity提供了兩種方法來標記需要強制保留的代碼:
- 使用[Preserve]標簽,可以對Assembly、Type、Field、Properties、Method使用。
- 使用Link.xml文件,在文件中寫明需要保留的代碼,使用范圍同上,還可以標記保留整個Namespace。
對比
這里使用新工程做一個測試,對新工程設置不同的strip level,然后在iOS平臺build出xCode工程。對比xCode工程的Classes/Native中代碼的數量:
-
low:72.7M(74項)
low -
high:54.5M(62項)
high
strip中遇到的問題及解決方法
類型轉換錯誤
InvalidCastException: Unable to cast object of type 'BehaviourTree' to type 'DialogueTree'.
對于該類型錯誤,無法有效的確定錯誤原因(以NodeCanvas為例,對錯誤的實例進行類型轉換的邏輯流程無法準確定位,可能與NodeCanvas對行為樹資源反序列化的實現有關),最直接的辦法是將整個模塊的Namespace包含到link中以避免此類問題。
(同時可以對官方示例進行同等級的strip,嘗試更仔細的解決問題,通常官方示例因為體量小,能更快的進行build迭代和測試)。
無法為抽象類創建對象
Exception: Cannot create an instance of an interface or abstract type for NodeCanvas.Framework.ActionTask。
通常原因在于其實現類沒有被保留下來。報錯定位依然不明確。解決辦法同上.
無法解析符號
Type with name 'NodeCanvas.Tasks.Actions.PlayerActions.AnimationAction' could not be resolved.
其中指出的符號,即類型、方法、屬性等,因為沒有被保留下來,導致進行反射調用時無法確定符號意義導致的問題。解決方法很明確,就是將符號寫入link.xml以在strip過程中保留。
找不到Class ID對應的類型
ReportException: UnityLogError Could not produce class with ID 134.
通常是因為Unity引擎的代碼被Strip掉了,導致在程序運行時找不到對應的類。對此類問題比較方便的一點在于,錯誤信息給出了具體的類(https://docs.unity3d.com/Manual/ClassIDReference.html),要解決問題只需要將查表找到的Class加入到link.xml即可。
如果在文檔中無法找到對應的class,這里還有另外一種辦法,參考Could not produce class with ID 363
找不到構造器
ReflectionException: The reflector requires concrete classes.Type XXX has no constructor. Is it an interface?
這是在使用StrangeIOC框架時遇到的問題。因為框架通過注入->反射來獲取具體類型的實例,所以其中的Mediator、Model、Command、Service都沒有直接引用(在MVCSContext中會進行綁定,以此為根可以找到所有的MVCS類),在strip時由于沒有自定義的構造器,導致錯誤(原因待查)。解決該問題的方式,就是不通過UnityLinker的邏輯來strip這些類,而是將這些類所在的Namespace加入到link.xml。
找不到set函數
ReportException: UnityLogError System.ArgumentException: Set Method not found for 'xxx'
這也是在使用StrangeIOC框架時遇到的問題。因為我們在Mediator、Service類的開頭使用[Inject]標簽來獲取一個Model,而且在邏輯處理部分只用到了屬性的get方法,如果這個Mediator、Service本身沒有被完整保留(添加到Link.xml或者使用了[Preserve]標簽),這樣會導致該條屬性的set方法被裁剪掉,初始化這個類時,會因為找不到屬性的set方法而報錯。
(代碼示例:)
[Inject]
public MyModel myModel {get; set;}
此時解決辦法之一是將這個Mediator、Service類加入到Link.xml中,在Strip時完整保留這個類。還有另一個小技巧:讓Inject繼承PreserveAttribute,這樣能保證所有使用[Inject]標簽的屬性都被完整保留下來。
strip之后需要注意的問題
- 務必做全量測試。雖然部分插件對Strip做了處理(例如提供了link.xml),但是依然有許多插件、第三方庫沒有考慮Strip的問題。所以必須通過全量測試來保證沒有過度裁剪。
- 維護好邏輯代碼。通常邏輯代碼不需要進行裁剪,但是對于某些特殊情況(大量出現一部分代碼僅運行在PC平臺,另一部分代碼需要運行在各個平臺)最好是對代碼合理分配Namespace,以便于在link.xml中通過Namespace進行裁剪。
問題及參考資料
- 目前沒有找到能夠查看Strip過程裁剪掉(或保留)內容的方法。