Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

unity-AssetBundle资源冗余检测

$
0
0
  • 最近搞了下 unity 的 AssetBundle(以下简称 AB) 资源冗余检测,并导出一个md文件列出冗余的资源及其被打入进哪几个AB中,方便排除冗余

思路

  • 科普下AB资源冗余,当然这里并不会详细说明,只会放个链接 点我 (需要科学上网)
  • 可以搜到大部分的文章都是在说一个 侑虎科技 的第三方检测平台,我也去上面检测了下正确检测出来,据说是免费几次后就需要付费。但是资源不加密就上传上去貌似不是很靠谱,资源大了上传也麻烦。所以就自己实现了一个检测。
    • 检测的思路也很简单
      1. 先用主AB生产一个所有 资源及所在AB 的一个 映射表
      2. 递归遍历打出来的包下的所有的AB,通过 AssetDatabase.GetDependencies 获取到 AB中资源名字在 AssetDatabase 中所有的依赖。(此时要求工程下Asset下有正常的资源)
      3. 遍历所有依赖,是否在 映射表 中,如果 不存在超过两次,相同 资源A 被打进了两个AB中,而不是 资源A 打成一个AB,被其他AB依赖进去。
      4. 最后会收集到这些 不存在超过两次 的资源名及被打进去的AB文件名,导出到一个md文件中,使用md编辑器查看(这里推荐个md客户端叫 Haroopad,平常都用这个写md)

源码 及 使用

  • 鉴于源码就一个cs文件,就不上传到git,直接这里贴了,同时也会上传几个测试的 源资源(test_res.rar)、打包出的 有冗余(ABoutput.rar)、无冗余(ABoutput_red.rar) 的资源
  • 使用:

    1. 分别解压出来,test_res目录放到工程Asset目录下(AssetDatabase才能找到资源,获取依赖),打包出的资源随意放(最好英文路径)
    2. 选择打包出的资源的主AB
      这里写图片描述

      这里写图片描述

    3. 开始检测,有冗余会导出md文件
      这里写图片描述

      这里写图片描述

  • 资源传送门: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 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>