赞
踩
如果还不了解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. 自动生成不能影响用户自定义部分。把自动生成部分用标识包起来,每次只变更被标识包出来的范围。
1. 获取项目依赖的全部程序集:
Build项目后Unity会在Library子目录生成程序集dll文件,不同目标平台生成的位置不同。HybridCLR会把目标平台生成的程序集单独拷贝出来存放在指定位置。我这里直接读取HybridCLR备份出来的全部程序集dll
- /// <summary>
- /// 获取项目全部dll
- /// </summary>
- /// <returns></returns>
- public static string[] GetProjectAssemblyDlls()
- {
- List<string> dlls = new List<string>();
- var dllDir = BuildConfig.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget);
- if (!Directory.Exists(dllDir))
- {
- return dlls.ToArray();
- }
- var files = Directory.GetFiles(dllDir, "*.dll", SearchOption.AllDirectories);
- foreach (var file in files)
- {
- var fileInfo = new FileInfo(file);
- var fileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - fileInfo.Extension.Length);
- if (!dlls.Contains(fileName)) dlls.Add(fileName);
- }
- return dlls.ToArray();
- }
上面直接使用了HybridCLR里获取裁剪后dll的方法,Unity Build后会在Library目录下生成裁剪后的dll,不同平台和Unity版本,dll输出目录不同,例如Unity 2021可以通过以下获取:
- public static string GetStripAssembliesDir2021(BuildTarget target)
- {
- string projectDir = Directory.GetParent(Application.dataPath).FullName;
- #if UNITY_STANDALONE_WIN
- return $"{projectDir}/Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped";
- #elif UNITY_ANDROID
- return $"{projectDir}/Library/Bee/artifacts/Android/ManagedStripped";
- #elif UNITY_IOS
- return $"{projectDir}/Temp/StagingArea/Data/Managed/tempStrip";
- #elif UNITY_WEBGL
- return $"{projectDir}/Library/Bee/artifacts/WebGL/ManagedStripped";
- #elif UNITY_EDITOR_OSX
- return $"{projectDir}/Library/Bee/artifacts/MacStandalonePlayerBuildProgram/ManagedStripped";
- #else
- throw new NotSupportedException("GetOriginBuildStripAssembliesDir");
- #endif
- }
2. 解析现有link.xml,拿到已经添加的程序集:
读取link.xml文本行,通过正则表达式匹配获取已配置的程序集名字。
- /// <summary>
- /// 获取已经配置到link.xml里的dll
- /// </summary>
- /// <returns></returns>
- public static string[] GetSelectedAssemblyDlls()
- {
- List<string> dlls = new List<string>();
- if (!File.Exists(LinkFile))
- {
- return dlls.ToArray();
- }
- var lines = File.ReadAllLines(LinkFile);
- int generateBeginLine = lines.Length, generateEndLine = lines.Length;
- for (int i = 0; i < lines.Length; i++)
- {
- string line = lines[i];
- if (generateBeginLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
- {
- generateBeginLine = i;
- }
- else if (generateEndLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
- {
- generateEndLine = i;
- }
- if (((i > generateBeginLine && generateEndLine >= lines.Length) || (i > generateBeginLine && i < generateEndLine)) && !string.IsNullOrWhiteSpace(line))
- {
- var match = Regex.Match(line, MatchPattern);
- if (match.Success)
- {
- var assemblyName = match.Result("$1");
- if (!dlls.Contains(assemblyName)) dlls.Add(assemblyName);
- }
- }
-
- }
- return dlls.ToArray();
- }
3. 保存程序集列表到link.xml
- public static bool Save2LinkFile(string[] stripList)
- {
- if (!File.Exists(LinkFile))
- {
- File.WriteAllText(LinkFile, $"<linker>{Environment.NewLine}{STRIP_GENERATE_TAG}{Environment.NewLine}{STRIP_GENERATE_TAG}</linker>");
- }
- var lines = File.ReadAllLines(LinkFile);
- FindGenerateLine(lines, out int beginLineIdx, out int endLineIdx);
- int headIdx = ArrayUtility.FindIndex(lines, line => line.Trim().CompareTo("<linker>") == 0);
- if (beginLineIdx >= lines.Length)
- {
- ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
- }
- if (endLineIdx >= lines.Length)
- {
- ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
- }
- FindGenerateLine(lines, out beginLineIdx, out endLineIdx);
- int insertIdx = beginLineIdx;
- for (int i = 0; i < stripList.Length; i++)
- {
- insertIdx = beginLineIdx + i + 1;
- if (insertIdx >= endLineIdx)
- {
- ArrayUtility.Insert(ref lines, endLineIdx, FormatStripLine(stripList[i]));
- }
- else
- {
- lines[insertIdx] = FormatStripLine(stripList[i]);
- }
- }
- while ((insertIdx + 1) < lines.Length && lines[insertIdx + 1].Trim().CompareTo(STRIP_GENERATE_TAG) != 0)
- {
- ArrayUtility.RemoveAt(ref lines, insertIdx + 1);
- }
- try
- {
- File.WriteAllLines(LinkFile, lines, System.Text.Encoding.UTF8);
- return true;
- }
- catch (Exception e)
- {
- Debug.LogErrorFormat("Save2LinkFile Failed:{0}", e.Message);
- return false;
- }
- }
自动生成link.xml:
自动添加的程序集被”<!--GENERATE_TAG--> “标签前后包裹,不影响自定义程序集
- <linker>
- <!--GENERATE_TAG-->
- <assembly fullname="Builtin.Runtime" preserve="all" />
- <assembly fullname="Cinemachine" preserve="all" />
- <assembly fullname="DOTween" preserve="all" />
- <assembly fullname="HybridCLR" preserve="all" />
- <assembly fullname="LitJson" preserve="all" />
- <assembly fullname="mscorlib" preserve="all" />
- <assembly fullname="System.Core" preserve="all" />
- <assembly fullname="System" preserve="all" />
- <assembly fullname="UltimateJoystick" preserve="all" />
- <assembly fullname="Unity.TextMeshPro" preserve="all" />
- <assembly fullname="UnityEngine.CoreModule" preserve="all" />
- <assembly fullname="UnityEngine" preserve="all" />
- <assembly fullname="UnityEngine.TextCoreTextEngineModule" preserve="all" />
- <assembly fullname="UnityEngine.TextRenderingModule" preserve="all" />
- <assembly fullname="UnityEngine.UI" preserve="all" />
- <assembly fullname="UnityEngine.UIModule" preserve="all" />
- <assembly fullname="UnityEngine.UnityWebRequestModule" preserve="all" />
- <!--GENERATE_TAG-->
-
- <assembly fullname="Assembly-CSharp" preserve="all" />
- <assembly fullname="UnityEngine">
- <type fullname="UnityEngine.Animator" preserve="all"/>
- <type fullname="UnityEngine.Animation" preserve="all"/>
- </assembly>
- </linker>
4. 编辑器界面UI布局:
- private void OnGUI()
- {
- EditorGUILayout.BeginVertical();
- if (dataList.Count <= 0)
- {
- EditorGUILayout.HelpBox("未找到程序集,请先Build项目以生成程序集.", MessageType.Warning);
- }
- else
- {
- EditorGUILayout.HelpBox("勾选需要添加到Link.xml的程序集,然后点击保存生效.", MessageType.Info);
- }
- scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, false, true);
- for (int i = 0; i < dataList.Count; i++)
- {
- EditorGUILayout.BeginHorizontal();
- var item = dataList[i];
- item.isOn = EditorGUILayout.ToggleLeft(item.dllName, item.isOn, item.isOn ? selectedStyle : normalStyle);
- EditorGUILayout.EndHorizontal();
- }
- EditorGUILayout.EndScrollView();
- EditorGUILayout.BeginHorizontal();
- if (GUILayout.Button("Select All", GUILayout.Width(100)))
- {
- SelectAll(true);
- }
- if (GUILayout.Button("Cancel All", GUILayout.Width(100)))
- {
- SelectAll(false);
- }
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("Reload", GUILayout.Width(120)))
- {
- RefreshListData();
- }
- if (GUILayout.Button("Save", GUILayout.Width(120)))
- {
- if (MyGameTools.Save2LinkFile(GetCurrentSelectedList()))
- {
- EditorUtility.DisplayDialog("Strip LinkConfig Editor", "Update link.xml success!", "OK");
- }
- }
- EditorGUILayout.EndHorizontal();
- EditorGUILayout.EndVertical();
- }
Strip Config Editor完整代码:
工具类处理逻辑:
- using GameFramework;
- using HybridCLR;
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.IO;
- using System.Text.RegularExpressions;
- using UnityEditor;
- using UnityEngine;
-
- public partial class MyGameTools
- {
- public const string LinkFile = "Assets/link.xml";
- public const string STRIP_GENERATE_TAG = "<!--GENERATE_TAG-->";
- private const string MatchPattern = "<assembly[\\s]+fullname[\\s]*=[\\s]*\"([^\"]+)\"";
- [MenuItem("Game Framework/GameTools/Strip Config Window", false, 1)]
- public static void ShowStripConfigEditor()
- {
- EditorWindow.GetWindow<StripLinkConfigWindow>("Strip LinkConfig Editor").Show();
- }
- /// <summary>
- /// 获取项目全部dll
- /// </summary>
- /// <returns></returns>
- public static string[] GetProjectAssemblyDlls()
- {
- List<string> dlls = new List<string>();
- var dllDir = BuildConfig.GetAssembliesPostIl2CppStripDir(EditorUserBuildSettings.activeBuildTarget);
- if (!Directory.Exists(dllDir))
- {
- return dlls.ToArray();
- }
- var files = Directory.GetFiles(dllDir, "*.dll", SearchOption.AllDirectories);
- foreach (var file in files)
- {
- var fileInfo = new FileInfo(file);
- var fileName = fileInfo.Name.Substring(0, fileInfo.Name.Length - fileInfo.Extension.Length);
- if (!dlls.Contains(fileName)) dlls.Add(fileName);
- }
- return dlls.ToArray();
- }
- /// <summary>
- /// 获取已经配置到link.xml里的dll
- /// </summary>
- /// <returns></returns>
- public static string[] GetSelectedAssemblyDlls()
- {
- List<string> dlls = new List<string>();
- if (!File.Exists(LinkFile))
- {
- return dlls.ToArray();
- }
- var lines = File.ReadAllLines(LinkFile);
- int generateBeginLine = lines.Length, generateEndLine = lines.Length;
- for (int i = 0; i < lines.Length; i++)
- {
- string line = lines[i];
- if (generateBeginLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
- {
- generateBeginLine = i;
- }
- else if (generateEndLine >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
- {
- generateEndLine = i;
- }
- if (((i > generateBeginLine && generateEndLine >= lines.Length) || (i > generateBeginLine && i < generateEndLine)) && !string.IsNullOrWhiteSpace(line))
- {
- var match = Regex.Match(line, MatchPattern);
- if (match.Success)
- {
- var assemblyName = match.Result("$1");
- if (!dlls.Contains(assemblyName)) dlls.Add(assemblyName);
- }
- }
-
- }
- return dlls.ToArray();
- }
- public static bool Save2LinkFile(string[] stripList)
- {
- if (!File.Exists(LinkFile))
- {
- File.WriteAllText(LinkFile, $"<linker>{Environment.NewLine}{STRIP_GENERATE_TAG}{Environment.NewLine}{STRIP_GENERATE_TAG}</linker>");
- }
- var lines = File.ReadAllLines(LinkFile);
- FindGenerateLine(lines, out int beginLineIdx, out int endLineIdx);
- int headIdx = ArrayUtility.FindIndex(lines, line => line.Trim().CompareTo("<linker>") == 0);
- if (beginLineIdx >= lines.Length)
- {
- ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
- }
- if (endLineIdx >= lines.Length)
- {
- ArrayUtility.Insert(ref lines, headIdx + 1, STRIP_GENERATE_TAG);
- }
- FindGenerateLine(lines, out beginLineIdx, out endLineIdx);
- int insertIdx = beginLineIdx;
- for (int i = 0; i < stripList.Length; i++)
- {
- insertIdx = beginLineIdx + i + 1;
- if (insertIdx >= endLineIdx)
- {
- ArrayUtility.Insert(ref lines, endLineIdx, FormatStripLine(stripList[i]));
- }
- else
- {
- lines[insertIdx] = FormatStripLine(stripList[i]);
- }
- }
- while ((insertIdx + 1) < lines.Length && lines[insertIdx + 1].Trim().CompareTo(STRIP_GENERATE_TAG) != 0)
- {
- ArrayUtility.RemoveAt(ref lines, insertIdx + 1);
- }
- try
- {
- File.WriteAllLines(LinkFile, lines, System.Text.Encoding.UTF8);
- return true;
- }
- catch (Exception e)
- {
- Debug.LogErrorFormat("Save2LinkFile Failed:{0}", e.Message);
- return false;
- }
- }
- private static string FormatStripLine(string assemblyName)
- {
- return $"\t<assembly fullname=\"{assemblyName}\" preserve=\"all\" />";
- }
- private static void FindGenerateLine(string[] lines, out int beginLineIdx, out int endLineIdx)
- {
- beginLineIdx = endLineIdx = lines.Length;
- for (int i = 0; i < lines.Length; i++)
- {
- var line = lines[i];
- if (beginLineIdx >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
- {
- beginLineIdx = i;
- }
- else if (endLineIdx >= lines.Length && line.Trim().CompareTo(STRIP_GENERATE_TAG) == 0)
- {
- endLineIdx = i;
- }
- }
- }
- }
编辑器GUI:
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEditor;
- using UnityEngine.UIElements;
-
- public class StripLinkConfigWindow : EditorWindow
- {
- private class ItemData
- {
- public bool isOn;
- public string dllName;
- public ItemData(bool isOn, string dllName)
- {
- this.isOn = isOn;
- this.dllName = dllName;
- }
- }
- private Vector2 scrollPosition;
- private string[] selectedDllList;
- private List<ItemData> dataList;
- private GUIStyle normalStyle;
- private GUIStyle selectedStyle;
- private void OnEnable()
- {
- normalStyle = new GUIStyle();
- normalStyle.normal.textColor = Color.white;
-
- selectedStyle = new GUIStyle();
- selectedStyle.normal.textColor = Color.green;
- dataList = new List<ItemData>();
- RefreshListData();
- }
- private void OnGUI()
- {
- EditorGUILayout.BeginVertical();
- if (dataList.Count <= 0)
- {
- EditorGUILayout.HelpBox("未找到程序集,请先Build项目以生成程序集.", MessageType.Warning);
- }
- else
- {
- EditorGUILayout.HelpBox("勾选需要添加到Link.xml的程序集,然后点击保存生效.", MessageType.Info);
- }
- scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, false, true);
- for (int i = 0; i < dataList.Count; i++)
- {
- EditorGUILayout.BeginHorizontal();
- var item = dataList[i];
- item.isOn = EditorGUILayout.ToggleLeft(item.dllName, item.isOn, item.isOn ? selectedStyle : normalStyle);
- EditorGUILayout.EndHorizontal();
- }
- EditorGUILayout.EndScrollView();
- EditorGUILayout.BeginHorizontal();
- if (GUILayout.Button("Select All", GUILayout.Width(100)))
- {
- SelectAll(true);
- }
- if (GUILayout.Button("Cancel All", GUILayout.Width(100)))
- {
- SelectAll(false);
- }
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("Reload", GUILayout.Width(120)))
- {
- RefreshListData();
- }
- if (GUILayout.Button("Save", GUILayout.Width(120)))
- {
- if (MyGameTools.Save2LinkFile(GetCurrentSelectedList()))
- {
- EditorUtility.DisplayDialog("Strip LinkConfig Editor", "Update link.xml success!", "OK");
- }
- }
- EditorGUILayout.EndHorizontal();
- EditorGUILayout.EndVertical();
- }
- private void SelectAll(bool isOn)
- {
- foreach (var item in dataList)
- {
- item.isOn = isOn;
- }
- }
- private string[] GetCurrentSelectedList()
- {
- List<string> result = new List<string>();
- foreach (var item in dataList)
- {
- if (item.isOn)
- {
- result.Add(item.dllName);
- }
- }
- return result.ToArray();
- }
- private void RefreshListData()
- {
- dataList.Clear();
- selectedDllList = MyGameTools.GetSelectedAssemblyDlls();
- foreach (var item in MyGameTools.GetProjectAssemblyDlls())
- {
- dataList.Add(new ItemData(IsInSelectedList(item), item));
- }
- }
- private bool IsInSelectedList(string dllName)
- {
- return ArrayUtility.Contains(selectedDllList, dllName);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。