- 最近搞了下 unity 的 AssetBundle(以下简称 AB) 资源冗余检测,并导出一个md文件列出冗余的资源及其被打入进哪几个AB中,方便排除冗余
思路
- 科普下AB资源冗余,当然这里并不会详细说明,只会放个链接 点我 (需要科学上网)
- 可以搜到大部分的文章都是在说一个 侑虎科技 的第三方检测平台,我也去上面检测了下正确检测出来,据说是免费几次后就需要付费。但是资源不加密就上传上去貌似不是很靠谱,资源大了上传也麻烦。所以就自己实现了一个检测。
- 检测的思路也很简单
- 先用主AB生产一个所有 资源及所在AB 的一个 映射表
- 递归遍历打出来的包下的所有的AB,通过
AssetDatabase.GetDependencies
获取到 AB中资源名字在 AssetDatabase 中所有的依赖。(此时要求工程下Asset下有正常的资源) - 遍历所有依赖,是否在 映射表 中,如果 不存在 且 超过两次,相同 资源A 被打进了两个AB中,而不是 资源A 打成一个AB,被其他AB依赖进去。
- 最后会收集到这些 不存在 且 超过两次 的资源名及被打进去的AB文件名,导出到一个md文件中,使用md编辑器查看(这里推荐个md客户端叫 Haroopad,平常都用这个写md)
- 检测的思路也很简单
源码 及 使用
- 鉴于源码就一个cs文件,就不上传到git,直接这里贴了,同时也会上传几个测试的 源资源(test_res.rar)、打包出的 有冗余(ABoutput.rar)、无冗余(ABoutput_red.rar) 的资源
使用:
- 分别解压出来,test_res目录放到工程Asset目录下(AssetDatabase才能找到资源,获取依赖),打包出的资源随意放(最好英文路径)
选择打包出的资源的主AB
Image may be NSFW.
Clik here to view.Image may be NSFW.
Clik here to view.开始检测,有冗余会导出md文件
Image may be NSFW.
Clik here to view.Image may be NSFW.
Clik here to view.
资源传送门:unity3d冗余测试资源.rar
源码:
using System; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEditor; using System.IO; using System.Linq; class ABRedundancyChecker : EditorWindow { class CRedAsset { public CRedAsset() { } public string mName = ""; public string mType = ""; public List<string> mUsers = new List<string>(); } List<Type> mAssetTypeList = new List<Type> { typeof(Material), typeof(Texture2D), typeof(AnimationClip), typeof(AudioClip), typeof(Sprite), typeof(Shader), typeof(Font), typeof(Mesh) }; const string kABRedundencyDir = "/a_ABRedundency"; //输出文件的目录 const string kSearchPattern = "*.assetbundle"; string kResultPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + kABRedundencyDir; //const string kABPath = "Assets/test_res/ABoutput_red"; //const string kManiFest = "ABoutput"; bool mIsForQ6 = false; string mABPath = "D:\\svn_q6_app\\win64\\q6-v0.0.63.0\\q6_Data\\StreamingAssets\\resource"; string mMainAb = "resource"; List<string> mAllABFiles = null; Dictionary<string, string> mAssetGenMap = null; Dictionary<string, CRedAsset> mRedAssetMap = null; float mCheckTime = 0f; static ABRedundancyChecker mInstance = null; public static ABRedundancyChecker Ins { get { if (mInstance == null) mInstance = new ABRedundancyChecker(); return mInstance; } } //[MenuItem("AB冗余检测/AB检测")] ////[MenuItem("Q5/Bundle相关/Bundle冗余检测")] //public static void Launch() //{ // ABRedundancyChecker.Ins.StartCheck(); //} // 提供给其他脚本调用的接口 public void StartCheck(string path, string abName) { mABPath = path; mMainAb = abName; mIsForQ6 = true; StartCheck(); } void StartCheck() { EditorUtility.DisplayCancelableProgressBar("AB资源冗余检测中", "资源读取中......", 0f); mCheckTime = UnityEngine.Time.realtimeSinceStartup; if (mAllABFiles == null) mAllABFiles = new List<string>(); if (mAssetGenMap == null) mAssetGenMap = new Dictionary<string, string>(); if (mRedAssetMap == null) mRedAssetMap = new Dictionary<string, CRedAsset>(); if (!GenAssetMap(mABPath, mMainAb)) { EditorUtility.ClearProgressBar(); EditorUtility.DisplayDialog("错误", "请检查是否选择正确的AB资源", "Ok"); return; } GetAllFiles(mAllABFiles, mABPath, kSearchPattern); int startIndex = 0; EditorApplication.CallbackFunction myUpdate = null; myUpdate = () => { string file = mAllABFiles[startIndex]; AssetBundle ab = null; try { ab = CreateABAdapter(file); string[] arr = file.Split('/'); CheckABInfo(ab, arr[arr.Length - 1]); } catch (Exception e) { Debug.LogError("MyError:" + e.StackTrace); } finally { if (ab != null) ab.Unload(true); } bool isCancel = EditorUtility.DisplayCancelableProgressBar("AB资源冗余检测中", file, (float)startIndex / (float)mAllABFiles.Count); startIndex++; if (isCancel || startIndex >= mAllABFiles.Count) { EditorUtility.ClearProgressBar(); if (!isCancel) { CullNotRed(); mCheckTime = UnityEngine.Time.realtimeSinceStartup - mCheckTime; EditorUtility.DisplayDialog("AssetBundle资源冗余检测结果", Export(), "Ok"); } mAllABFiles.Clear(); mAllABFiles = null; mAssetGenMap.Clear(); mAssetGenMap = null; mRedAssetMap.Clear(); mRedAssetMap = null; Resources.UnloadUnusedAssets(); EditorUtility.UnloadUnusedAssetsImmediate(); GC.Collect(); EditorApplication.update -= myUpdate; startIndex = 0; } }; EditorApplication.update += myUpdate; } //适配项目打包(有加密) 或 原生打包 AssetBundle CreateABAdapter(string path) { //if (mIsForQ6) // return UtilCommon.CreateBundleFromFile(path); //else return AssetBundle.LoadFromFile(path); } bool GenAssetMap(string path, string maniFest) { path = path.Replace("\\", "/"); AssetBundle maniFestAb = CreateABAdapter(System.IO.Path.Combine(path, maniFest)); if (maniFestAb == null) return false; AssetBundleManifest manifest = maniFestAb.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); if (manifest == null) return false; string[] allBundles = manifest.GetAllAssetBundles(); maniFestAb.Unload(true); foreach (string abName in allBundles) { string filePath = System.IO.Path.Combine(path, abName); AssetBundle ab = CreateABAdapter(filePath); foreach (string asset in ab.GetAllAssetNames()) { mAssetGenMap.Add(asset.ToLower(), abName); } foreach (string asset in ab.GetAllScenePaths()) { mAssetGenMap.Add(asset.ToLower(), abName); } ab.Unload(true); } if (mAssetGenMap.Count == 0) return false; return true; } void CheckABInfo(AssetBundle ab, string abName) { EditorSettings.serializationMode = SerializationMode.ForceText; string[] names = ab.GetAllAssetNames(); string[] dependencies = AssetDatabase.GetDependencies(names); string[] allDepen = dependencies.Length > 0 ? dependencies : names; string currDep = ""; for (int i = 0; i < allDepen.Length; i++) { currDep = allDepen[i].ToLower(); CalcuDenpend(currDep, abName); //UnityEngine.Object obj = ab.LoadAsset(currDep, typeof(UnityEngine.Object)); //if (obj != null) //{ // Debugger.Log("--- obj type:{0}", GetObjectType(obj)); //} } } //todo: 待加入 类型 void CalcuDenpend(string depName, string abName) { if (depName.EndsWith(".cs")) return; if (!mAssetGenMap.ContainsKey(depName)) //不存在这个ab,记录一下 { if (!mRedAssetMap.ContainsKey(depName)) { CRedAsset ra = new CRedAsset(); ra.mName = depName; ra.mType = "我了个去"; mRedAssetMap.Add(depName, ra); ra.mUsers.Add(abName); } else { CRedAsset ra = mRedAssetMap[depName]; ra.mUsers.Add(abName); } } } // mRedAssetMap 中 CRedAsset 的 mUsers 只有一个的,视为不冗余的资源,直接打到了该 ab 中 void CullNotRed() { List<string> keys = new List<string>(); foreach (var item in mRedAssetMap) { if (item.Value.mUsers.Count == 1) keys.Add(item.Key); } foreach (var value in keys) mRedAssetMap.Remove(value); } List<string> GetAllFiles(List<string> files, string folder, string pattern) { folder = folder.Replace("\\", "/"); System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(folder); foreach (var file in dir.GetFiles(pattern)) { files.Add((System.IO.Path.Combine(folder, file.Name).Replace("\\", "/")).ToLower()); } foreach (var sub in dir.GetDirectories()) { files = GetAllFiles(files, System.IO.Path.Combine(folder, sub.Name), pattern); } return files; } string GetObjectType(UnityEngine.Object obj) { string longType = obj.GetType().ToString(); string[] longTypeArr = longType.Split('.'); return longTypeArr[longTypeArr.Length - 1]; } private string AppendSlash(string path) { if (path == null || path == "") return ""; int idx = path.LastIndexOf('/'); if (idx == -1) return path + "/"; if (idx == path.Length - 1) return path; return path + "/"; } string Export() { if (mRedAssetMap.Count == 0) return "未检查到有资源冗余"; List<CRedAsset> raList = mRedAssetMap.Values.ToList<CRedAsset>(); string currTime = System.DateTime.Now.ToString("yyyyMMdd_HHmmss"); string path = string.Format("{0}/{1}_{2}.md", kResultPath, "ABRedundency", currTime); if (!System.IO.Directory.Exists(kResultPath)) System.IO.Directory.CreateDirectory(kResultPath); using (FileStream fs = File.Create(path)) { StringBuilder sb = new StringBuilder(); sb.Append(string.Format("## 资源总量:{0},冗余总量:{1},检测时间:{2},耗时:{3:F2}s\r\n---\r\n", mAllABFiles.Count, raList.Count, currTime, mCheckTime)); sb.Append("| 排序 | 资源名称 | 资源类型 | AB文件数量 | AB文件名 |\r\n"); sb.Append("|---|---|:---:|:---:|---|\r\n"); CRedAsset ra = null; StringBuilder abNames = new StringBuilder(); raList.Sort((CRedAsset ra1, CRedAsset ra2) => {//排序优先级: ab文件个数 -> 名字 int ret = ra2.mUsers.Count.CompareTo(ra1.mUsers.Count); if (ret == 0) ret = ra1.mName.CompareTo(ra2.mName); return ret; }); for (int i = 0; i < raList.Count; i++) { ra = raList[i]; foreach (var abName in ra.mUsers) abNames.Append(string.Format("**{0}**, ", abName)); //abNames.Append(string.Format("{0}<br>", abName)); //另一种使用换行 sb.Append(string.Format("| {0} | **{1}** | {2} | {3} | {4} |\r\n" , i + 1, ra.mName, ra.mType, ra.mUsers.Count, abNames.ToString())); abNames.Length = 0; } byte[] info = new UTF8Encoding(true).GetBytes(sb.ToString()); fs.Write(info, 0, info.Length); } return "有冗余,导出结果:" + path.Replace("\\", "/"); } //---------------- gui begin ------------ [MenuItem("AB冗余检测/AB检测")] static void Init() { EditorWindow.GetWindow(typeof(ABRedundancyChecker), false, "AB资源冗余检测"); } void Awake() { mInstance = this; } string mSelPath = ""; public void OnGUI() { EditorGUILayout.Space(); EditorGUILayout.LabelField("路径:", EditorStyles.boldLabel); EditorGUILayout.Space(); GUILayout.Label(mSelPath); EditorGUILayout.Space(); if (GUILayout.Button("选择主AB文件")) mSelPath = EditorUtility.OpenFilePanelWithFilters("选择主AB文件", mSelPath, null); EditorGUILayout.Space(); //mIsForQ6 = EditorGUILayout.Toggle("是否Q6(Q6有解密机制)", mIsForQ6); EditorGUILayout.Space(); if (GUILayout.Button("开始检测")) { if (mSelPath == "") EditorUtility.DisplayDialog("错误", "请先 选择主AB文件", "Ok"); else { mSelPath = mSelPath.Replace("\\", "/"); string[] arr = mSelPath.Split('/'); mMainAb = arr[arr.Length - 1]; mABPath = mSelPath.Substring(0, mSelPath.LastIndexOf('/')); StartCheck(); } } } }
作者:yangxuan0261 发表于2017/3/23 14:49:42 原文链接
阅读:4 评论:0 查看评论