当前位置:   article > 正文

【Unity编辑器扩展】il2cpp代码裁剪(Strip Engine Code)配置工具

strip engine code

如果还不了解il2cpp代码裁剪请移步:Unity IL2CPP发布64位,以及代码裁剪Strip Engine Code_TopGames的博客-CSDN博客_il2cpp代码裁剪

背景: 

il2cpp方式打包有个头疼的问题就是,如果项目使用了AssetBundle,Unity代码裁剪机制不会查找保留AssetBundle或热更新dll引用的引擎代码。例如内置程序集未使用UnityEngine.CanvasGroup这个类,但是热更dll里使用了这个类,打包时代码裁剪机制只从内置程序集中查找保留用到的代码,所以UnityEngine.CanvasGroup就被狠心抛弃了,程序运行时调用UnityEngine.CanvasGroup就会报错。

更麻烦的是我已经在link.xml配置了<assembly fullname="UnityEngine" preserve="all" />和<assembly fullname="UnityEngine.UI" preserve="all" />以保留UnityEngine和UnityEngine.UI两个程序集,但是UnityEngine.CanvasGroup依然报错。

点击CanvasGroup跳转才发现,原来它属于UnityEngine.UIModule程序集:

 它的名字空间很容易误导以为它属于UnityEngine程序集, 那么问题来了,某个类所在程序集难以判定,难道只能等哪个类报错了再找哪个类补到link.xml里吗?那些没有暴露出来的岂不是存在极大的隐患?

 那么写个工具扫描项目里所有Runtime程序集,然后把那些出现过的类揪出来放到link.xml是不是就一劳永逸了?然鹅~,其实这种“量身裁剪” 的方式对于单机游戏是完美的,即能保证运行时安全,又充分利用了代码裁剪减少了程序集大小。但是...,这对于热更新游戏来说弊大于利,越是“量身裁剪”对后续热更新的限制就越大,一旦热更新引用了新的引擎类就会报错,绕不开就只能重新发包,那么热更新存在的意义何在?

因此对于热更新项目,倒不如抛弃“量身”代码裁剪, 一不做二不休,把工程里所有必要的程序集preserve="all",全部编译打到包体里,这样留给热更新发挥的空间就大了。

工具设计思路:

Okay, 那这样问题就简单了,项目编译后会生成所有项目依赖的程序集dll,为了配置link.xml方便,我们写个UI面板,更直观得显示出全部程序集和已配置到列表里的程序集。通过勾选程序集名字,然后点击保存一键添加到link.xml里。

功能设计:

1. 打开工具界面显示全部程序集轮动列表,列表Item以 勾选框 + 程序集名称显示,已经定义到link.xml里的程序集默认勾选且文字颜色为绿色,反之为白色。一目了然

2. 需要支持一键全选/取消,程序集过多时一个一个勾是场灾难。

3. 需要支持重新加载列表。这样如果用户点击了全选/取消,又想恢复原有列表时,点一下重新加载列表就好了。

4. 保存按钮,把当前列表中勾选的程序集一键写入到link.xml

5. 自动生成不能影响用户自定义部分。把自动生成部分用标识包起来,每次只变更被标识包出来的范围。

Strip Config Editor

 程序实现:

 1. 获取项目依赖的全部程序集:

Build项目后Unity会在Library子目录生成程序集dll文件,不同目标平台生成的位置不同。HybridCLR会把目标平台生成的程序集单独拷贝出来存放在指定位置。我这里直接读取HybridCLR备份出来的全部程序集dll

  1. /// <summary>
  2. /// 获取项目全部dll
  3. /// </summary>
  4. /// <returns></returns>
  5. public static string[] GetProjectAssemblyDlls()
  6. {
  7. List<string> dlls = new List<string>();
  8. var dllDir = BuildConfig.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget);
  9. if (!Directory.Exists(dllDir))
  10. {
  11. return dlls.ToArray();
  12. }
  13. var files = Directory.GetFiles(dllDir, "*.dll", SearchOption.AllDirectories);
  14. foreach (var file in files)
  15. {
  16. var fileInfo = new FileInfo(file);
  17. var fileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - fileInfo.Extension.Length);
  18. if (!dlls.Contains(fileName)) dlls.Add(fileName);
  19. }
  20. return dlls.ToArray();
  21. }

 上面直接使用了HybridCLR里获取裁剪后dll的方法,Unity Build后会在Library目录下生成裁剪后的dll,不同平台和Unity版本,dll输出目录不同,例如Unity 2021可以通过以下获取:

  1. public static string GetStripAssembliesDir2021(BuildTarget target)
  2. {
  3. string projectDir = Directory.GetParent(Application.dataPath).FullName;
  4. #if UNITY_STANDALONE_WIN
  5. return $"{projectDir}/Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped";
  6. #elif UNITY_ANDROID
  7. return $"{projectDir}/Library/Bee/artifacts/Android/ManagedStripped";
  8. #elif UNITY_IOS
  9. return $"{projectDir}/Temp/StagingArea/Data/Managed/tempStrip";
  10. #elif UNITY_WEBGL
  11. return $"{projectDir}/Library/Bee/artifacts/WebGL/ManagedStripped";
  12. #elif UNITY_EDITOR_OSX
  13. return $"{projectDir}/Library/Bee/artifacts/MacStandalonePlayerBuildProgram/ManagedStripped";
  14. #else
  15. throw new NotSupportedException("GetOriginBuildStripAssembliesDir");
  16. #endif
  17. }

2. 解析现有link.xml,拿到已经添加的程序集:

读取link.xml文本行,通过正则表达式匹配获取已配置的程序集名字。

  1. /// <summary>
  2. /// 获取已经配置到link.xml里的dll
  3. /// </summary>
  4. /// <returns></returns>
  5. public static string[] GetSelectedAssemblyDlls()
  6. {
  7. List<string> dlls = new List<string>();
  8. if (!File.Exists(LinkFile))
  9. {
  10. return dlls.ToArray();
  11. }
  12. var lines = File.ReadAllLines(LinkFile);
  13. int generateBeginLine = lines.Length, generateEndLine = lines.Length;
  14. for (int i = 0; i < lines.Length; i++)
  15. {
  16. string line = lines[i];
  17. if (generateBeginLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
  18. {
  19. generateBeginLine = i;
  20. }
  21. else if (generateEndLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
  22. {
  23. generateEndLine = i;
  24. }
  25. if (((i > generateBeginLine && generateEndLine >= lines.Length) || (i > generateBeginLine && i < generateEndLine)) && !string.IsNullOrWhiteSpace(line))
  26. {
  27. var match = Regex.Match(line, MatchPattern);
  28. if (match.Success)
  29. {
  30. var assemblyName = match.Result("$1");
  31. if (!dlls.Contains(assemblyName)) dlls.Add(assemblyName);
  32. }
  33. }
  34. }
  35. return dlls.ToArray();
  36. }

3. 保存程序集列表到link.xml

  1. public static bool Save2LinkFile(string[] stripList)
  2. {
  3. if (!File.Exists(LinkFile))
  4. {
  5. File.WriteAllText(LinkFile, $"<linker>{Environment.NewLine}{STRIP_GENERATE_TAG}{Environment.NewLine}{STRIP_GENERATE_TAG}</linker>");
  6. }
  7. var lines = File.ReadAllLines(LinkFile);
  8. FindGenerateLine(lines, out int beginLineIdx, out int endLineIdx);
  9. int headIdx = ArrayUtility.FindIndex(lines, line => line.Trim().CompareTo("<linker>") == 0);
  10. if (beginLineIdx >= lines.Length)
  11. {
  12. ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
  13. }
  14. if (endLineIdx >= lines.Length)
  15. {
  16. ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
  17. }
  18. FindGenerateLine(lines, out beginLineIdx, out endLineIdx);
  19. int insertIdx = beginLineIdx;
  20. for (int i = 0; i < stripList.Length; i++)
  21. {
  22. insertIdx = beginLineIdx + i + 1;
  23. if (insertIdx >= endLineIdx)
  24. {
  25. ArrayUtility.Insert(ref lines, endLineIdx, FormatStripLine(stripList[i]));
  26. }
  27. else
  28. {
  29. lines[insertIdx] = FormatStripLine(stripList[i]);
  30. }
  31. }
  32. while ((insertIdx + 1) < lines.Length && lines[insertIdx + 1].Trim().CompareTo(STRIP_GENERATE_TAG) != 0)
  33. {
  34. ArrayUtility.RemoveAt(ref lines, insertIdx + 1);
  35. }
  36. try
  37. {
  38. File.WriteAllLines(LinkFile, lines, System.Text.Encoding.UTF8);
  39. return true;
  40. }
  41. catch (Exception e)
  42. {
  43. Debug.LogErrorFormat("Save2LinkFile Failed:{0}", e.Message);
  44. return false;
  45. }
  46. }

自动生成link.xml:

自动添加的程序集被”<!--GENERATE_TAG--> “标签前后包裹,不影响自定义程序集

  1. <linker>
  2. <!--GENERATE_TAG-->
  3. <assembly fullname="Builtin.Runtime" preserve="all" />
  4. <assembly fullname="Cinemachine" preserve="all" />
  5. <assembly fullname="DOTween" preserve="all" />
  6. <assembly fullname="HybridCLR" preserve="all" />
  7. <assembly fullname="LitJson" preserve="all" />
  8. <assembly fullname="mscorlib" preserve="all" />
  9. <assembly fullname="System.Core" preserve="all" />
  10. <assembly fullname="System" preserve="all" />
  11. <assembly fullname="UltimateJoystick" preserve="all" />
  12. <assembly fullname="Unity.TextMeshPro" preserve="all" />
  13. <assembly fullname="UnityEngine.CoreModule" preserve="all" />
  14. <assembly fullname="UnityEngine" preserve="all" />
  15. <assembly fullname="UnityEngine.TextCoreTextEngineModule" preserve="all" />
  16. <assembly fullname="UnityEngine.TextRenderingModule" preserve="all" />
  17. <assembly fullname="UnityEngine.UI" preserve="all" />
  18. <assembly fullname="UnityEngine.UIModule" preserve="all" />
  19. <assembly fullname="UnityEngine.UnityWebRequestModule" preserve="all" />
  20. <!--GENERATE_TAG-->
  21. <assembly fullname="Assembly-CSharp" preserve="all" />
  22. <assembly fullname="UnityEngine">
  23. <type fullname="UnityEngine.Animator" preserve="all"/>
  24. <type fullname="UnityEngine.Animation" preserve="all"/>
  25. </assembly>
  26. </linker>

4. 编辑器界面UI布局:

  1. private void OnGUI()
  2. {
  3. EditorGUILayout.BeginVertical();
  4. if (dataList.Count <= 0)
  5. {
  6. EditorGUILayout.HelpBox("未找到程序集,请先Build项目以生成程序集.", MessageType.Warning);
  7. }
  8. else
  9. {
  10. EditorGUILayout.HelpBox("勾选需要添加到Link.xml的程序集,然后点击保存生效.", MessageType.Info);
  11. }
  12. scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, false, true);
  13. for (int i = 0; i < dataList.Count; i++)
  14. {
  15. EditorGUILayout.BeginHorizontal();
  16. var item = dataList[i];
  17. item.isOn = EditorGUILayout.ToggleLeft(item.dllName, item.isOn, item.isOn ? selectedStyle : normalStyle);
  18. EditorGUILayout.EndHorizontal();
  19. }
  20. EditorGUILayout.EndScrollView();
  21. EditorGUILayout.BeginHorizontal();
  22. if (GUILayout.Button("Select All", GUILayout.Width(100)))
  23. {
  24. SelectAll(true);
  25. }
  26. if (GUILayout.Button("Cancel All", GUILayout.Width(100)))
  27. {
  28. SelectAll(false);
  29. }
  30. GUILayout.FlexibleSpace();
  31. if (GUILayout.Button("Reload", GUILayout.Width(120)))
  32. {
  33. RefreshListData();
  34. }
  35. if (GUILayout.Button("Save", GUILayout.Width(120)))
  36. {
  37. if (MyGameTools.Save2LinkFile(GetCurrentSelectedList()))
  38. {
  39. EditorUtility.DisplayDialog("Strip LinkConfig Editor", "Update link.xml success!", "OK");
  40. }
  41. }
  42. EditorGUILayout.EndHorizontal();
  43. EditorGUILayout.EndVertical();
  44. }

Strip Config Editor完整代码:

工具类处理逻辑:

  1. using GameFramework;
  2. using HybridCLR;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Text.RegularExpressions;
  8. using UnityEditor;
  9. using UnityEngine;
  10. public partial class MyGameTools
  11. {
  12. public const string LinkFile = "Assets/link.xml";
  13. public const string STRIP_GENERATE_TAG = "<!--GENERATE_TAG-->";
  14. private const string MatchPattern = "<assembly[\\s]+fullname[\\s]*=[\\s]*\"([^\"]+)\"";
  15. [MenuItem("Game Framework/GameTools/Strip Config Window", false, 1)]
  16. public static void ShowStripConfigEditor()
  17. {
  18. EditorWindow.GetWindow<StripLinkConfigWindow>("Strip LinkConfig Editor").Show();
  19. }
  20. /// <summary>
  21. /// 获取项目全部dll
  22. /// </summary>
  23. /// <returns></returns>
  24. public static string[] GetProjectAssemblyDlls()
  25. {
  26. List<string> dlls = new List<string>();
  27. var dllDir = BuildConfig.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget);
  28. if (!Directory.Exists(dllDir))
  29. {
  30. return dlls.ToArray();
  31. }
  32. var files = Directory.GetFiles(dllDir, "*.dll", SearchOption.AllDirectories);
  33. foreach (var file in files)
  34. {
  35. var fileInfo = new FileInfo(file);
  36. var fileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - fileInfo.Extension.Length);
  37. if (!dlls.Contains(fileName)) dlls.Add(fileName);
  38. }
  39. return dlls.ToArray();
  40. }
  41. /// <summary>
  42. /// 获取已经配置到link.xml里的dll
  43. /// </summary>
  44. /// <returns></returns>
  45. public static string[] GetSelectedAssemblyDlls()
  46. {
  47. List<string> dlls = new List<string>();
  48. if (!File.Exists(LinkFile))
  49. {
  50. return dlls.ToArray();
  51. }
  52. var lines = File.ReadAllLines(LinkFile);
  53. int generateBeginLine = lines.Length, generateEndLine = lines.Length;
  54. for (int i = 0; i < lines.Length; i++)
  55. {
  56. string line = lines[i];
  57. if (generateBeginLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
  58. {
  59. generateBeginLine = i;
  60. }
  61. else if (generateEndLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
  62. {
  63. generateEndLine = i;
  64. }
  65. if (((i > generateBeginLine && generateEndLine >= lines.Length) || (i > generateBeginLine && i < generateEndLine)) && !string.IsNullOrWhiteSpace(line))
  66. {
  67. var match = Regex.Match(line, MatchPattern);
  68. if (match.Success)
  69. {
  70. var assemblyName = match.Result("$1");
  71. if (!dlls.Contains(assemblyName)) dlls.Add(assemblyName);
  72. }
  73. }
  74. }
  75. return dlls.ToArray();
  76. }
  77. public static bool Save2LinkFile(string[] stripList)
  78. {
  79. if (!File.Exists(LinkFile))
  80. {
  81. File.WriteAllText(LinkFile, $"<linker>{Environment.NewLine}{STRIP_GENERATE_TAG}{Environment.NewLine}{STRIP_GENERATE_TAG}</linker>");
  82. }
  83. var lines = File.ReadAllLines(LinkFile);
  84. FindGenerateLine(lines, out int beginLineIdx, out int endLineIdx);
  85. int headIdx = ArrayUtility.FindIndex(lines, line => line.Trim().CompareTo("<linker>") == 0);
  86. if (beginLineIdx >= lines.Length)
  87. {
  88. ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
  89. }
  90. if (endLineIdx >= lines.Length)
  91. {
  92. ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
  93. }
  94. FindGenerateLine(lines, out beginLineIdx, out endLineIdx);
  95. int insertIdx = beginLineIdx;
  96. for (int i = 0; i < stripList.Length; i++)
  97. {
  98. insertIdx = beginLineIdx + i + 1;
  99. if (insertIdx >= endLineIdx)
  100. {
  101. ArrayUtility.Insert(ref lines, endLineIdx, FormatStripLine(stripList[i]));
  102. }
  103. else
  104. {
  105. lines[insertIdx] = FormatStripLine(stripList[i]);
  106. }
  107. }
  108. while ((insertIdx + 1) < lines.Length && lines[insertIdx + 1].Trim().CompareTo(STRIP_GENERATE_TAG) != 0)
  109. {
  110. ArrayUtility.RemoveAt(ref lines, insertIdx + 1);
  111. }
  112. try
  113. {
  114. File.WriteAllLines(LinkFile, lines, System.Text.Encoding.UTF8);
  115. return true;
  116. }
  117. catch (Exception e)
  118. {
  119. Debug.LogErrorFormat("Save2LinkFile Failed:{0}", e.Message);
  120. return false;
  121. }
  122. }
  123. private static string FormatStripLine(string assemblyName)
  124. {
  125. return $"\t<assembly fullname=\"{assemblyName}\" preserve=\"all\" />";
  126. }
  127. private static void FindGenerateLine(string[] lines, out int beginLineIdx, out int endLineIdx)
  128. {
  129. beginLineIdx = endLineIdx = lines.Length;
  130. for (int i = 0; i < lines.Length; i++)
  131. {
  132. var line = lines[i];
  133. if (beginLineIdx >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
  134. {
  135. beginLineIdx = i;
  136. }
  137. else if (endLineIdx >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
  138. {
  139. endLineIdx = i;
  140. }
  141. }
  142. }
  143. }

编辑器GUI:

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEditor;
  6. using UnityEngine.UIElements;
  7. public class StripLinkConfigWindow : EditorWindow
  8. {
  9. private class ItemData
  10. {
  11. public bool isOn;
  12. public string dllName;
  13. public ItemData(bool isOn, string dllName)
  14. {
  15. this.isOn = isOn;
  16. this.dllName = dllName;
  17. }
  18. }
  19. private Vector2 scrollPosition;
  20. private string[] selectedDllList;
  21. private List<ItemData> dataList;
  22. private GUIStyle normalStyle;
  23. private GUIStyle selectedStyle;
  24. private void OnEnable()
  25. {
  26. normalStyle = new GUIStyle();
  27. normalStyle.normal.textColor = Color.white;
  28. selectedStyle = new GUIStyle();
  29. selectedStyle.normal.textColor = Color.green;
  30. dataList = new List<ItemData>();
  31. RefreshListData();
  32. }
  33. private void OnGUI()
  34. {
  35. EditorGUILayout.BeginVertical();
  36. if (dataList.Count <= 0)
  37. {
  38. EditorGUILayout.HelpBox("未找到程序集,请先Build项目以生成程序集.", MessageType.Warning);
  39. }
  40. else
  41. {
  42. EditorGUILayout.HelpBox("勾选需要添加到Link.xml的程序集,然后点击保存生效.", MessageType.Info);
  43. }
  44. scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, false, true);
  45. for (int i = 0; i < dataList.Count; i++)
  46. {
  47. EditorGUILayout.BeginHorizontal();
  48. var item = dataList[i];
  49. item.isOn = EditorGUILayout.ToggleLeft(item.dllName, item.isOn, item.isOn ? selectedStyle : normalStyle);
  50. EditorGUILayout.EndHorizontal();
  51. }
  52. EditorGUILayout.EndScrollView();
  53. EditorGUILayout.BeginHorizontal();
  54. if (GUILayout.Button("Select All", GUILayout.Width(100)))
  55. {
  56. SelectAll(true);
  57. }
  58. if (GUILayout.Button("Cancel All", GUILayout.Width(100)))
  59. {
  60. SelectAll(false);
  61. }
  62. GUILayout.FlexibleSpace();
  63. if (GUILayout.Button("Reload", GUILayout.Width(120)))
  64. {
  65. RefreshListData();
  66. }
  67. if (GUILayout.Button("Save", GUILayout.Width(120)))
  68. {
  69. if (MyGameTools.Save2LinkFile(GetCurrentSelectedList()))
  70. {
  71. EditorUtility.DisplayDialog("Strip LinkConfig Editor", "Update link.xml success!", "OK");
  72. }
  73. }
  74. EditorGUILayout.EndHorizontal();
  75. EditorGUILayout.EndVertical();
  76. }
  77. private void SelectAll(bool isOn)
  78. {
  79. foreach (var item in dataList)
  80. {
  81. item.isOn = isOn;
  82. }
  83. }
  84. private string[] GetCurrentSelectedList()
  85. {
  86. List<string> result = new List<string>();
  87. foreach (var item in dataList)
  88. {
  89. if (item.isOn)
  90. {
  91. result.Add(item.dllName);
  92. }
  93. }
  94. return result.ToArray();
  95. }
  96. private void RefreshListData()
  97. {
  98. dataList.Clear();
  99. selectedDllList = MyGameTools.GetSelectedAssemblyDlls();
  100. foreach (var item in MyGameTools.GetProjectAssemblyDlls())
  101. {
  102. dataList.Add(new ItemData(IsInSelectedList(item), item));
  103. }
  104. }
  105. private bool IsInSelectedList(string dllName)
  106. {
  107. return ArrayUtility.Contains(selectedDllList, dllName);
  108. }
  109. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/115365
推荐阅读
相关标签
  

闽ICP备14008679号