Addressable优化解决方案

解决目标

1.可以和常规方案一样,带资源版本号,对于不同的渠道,有不同的资源地址,可以回退版本。对于多个旧版本资源,都可以更新到最新的。
2.热更流程和常规流程一样,首包在包体里,也可以部分在包体里,资源从首包里加载。有热更文件进入游戏走热更预加载,也可以部分文件边玩边下,或者静默下载。
3.带热更大小查看工具,可以查看热更Bundle和资源。
4.资源颗粒度控制工具,自动刷新资源Label工具。
5.本地Host工具,由于Addressable自带的Host工具不好使,自己写了一套本地Host工具。

以上工具不修改Addressable源码,只在其基础上扩展。拿来即用。

Inspector设置

Disable Catalog Update on Startup:禁止Unity一开始自己就去下Catalog文件。
Build Remote Catalog:使用远程目录,勾选。
Player Version Override:填写一个字符串,自己根据这个生成版本。
Max Concurrent Web Requests:最大并发下载AssetBundle数量,不宜设的过大,我这个填了10多个。
首席我们打包位置都设置为远程,这样远程地址有更新后,我们能根据这个地址去下载新的热更文件。然后这个目录是自己定义的:
在这里插入图片描述
打开Addressables Profiles,可以设置路径:
在这里插入图片描述
所有AB设为远程非静态包。Can Change Post Release

打AB包额外操作

AddressablePath是一个静态类,可以代码运行时改变这个路径。这个路径可以一开始从服务器获取。这样我们就能实现不同的渠道设置获取不同的CDN地址了。

public class AddressablePath
{
    
    
    public static string ServerBuild = "ServerData";
    private static string _remoteLoadPath = "http://soccer2res.unparallel.cn:80";
    public static string RemoteLoadPath {
    
     get {
    
     return _remoteLoadPath; } set {
    
    
            _remoteLoadPath = value;
        } 
}

这样我们的包一开始就打到ServerData目录下。但是这些资源不会一开始就放到我们包体里面去。然后我们可以自己写一个编辑器工具,打完AB包就拷贝到Streaming文件夹路径下。
同时我们要往Streaming文件夹里写一个文件,我用的是Dict数据结构,保存的是首包资源里面的每一个文件名。为什么要有这么一个文件呢?这是因为后面我们需要从Streaming文件夹里判断是否有Bundle文件,而在Android平台下是无法直接用File.Exist()判断的。

public class BuildLocalBundleData
{
    
    
    public static Dictionary<string,byte> BuildLocalBundleNames = new Dictionary<string,byte>();

    public static async GTask Init()
    {
    
    
        var requestUrl = $"{
      
      AddressablePath.StreamingLoadPath}/{
      
      AddressablePath.GetPlatformForAssetBundles(Application.platform)}/BuildLocalBundleData.json";
        string result = "";

        //Android
        if (requestUrl.Contains("://"))
        {
    
    
            using (UnityWebRequest webequest = UnityWebRequest.Get(requestUrl))
            {
    
    
                await GAsync.Get(webequest.SendWebRequest());
                if (webequest.error != null)
                {
    
    
#if UNITY_EDITOR
                    Debug.Log(webequest.error + requestUrl + ":包体资源不全");
#else
                    Debug.LogError(webequest.error+requestUrl+":包体资源不全");
#endif
                }
                else
                {
    
    
                    result = webequest.downloadHandler.text;
                }
            }
        }
        //iOS
        else
        {
    
    
            if(File.Exists(requestUrl))
                result = File.ReadAllText(requestUrl);
        }

        BuildLocalBundleNames = CatJson.JsonParser.ParseJson<Dictionary<string, byte>>(result);
        if (BuildLocalBundleNames.Count <= 0)
        {
    
    
#if UNITY_EDITOR
            Debug.Log(requestUrl + ":BuildLocalBundleData 为空");
#else
            Debug.LogError(requestUrl+":BuildLocalBundleData 为空");
#endif
        }
    }

    public static bool Index(string key)
    {
    
    
        return BuildLocalBundleNames.ContainsKey(key);
    }
}

运行时更改地址

在运行时候要要先转换地址,首先从包体里面加载,如果没有,说明这个文件有更新了,如果缓存下载了,我们从缓存下载里去找,再没有再从远程去下载,根据InternalIdTransformFunc这个方法实现。

private string InternalIdTransformFunc(UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation location)
        {
    
    
            if (location.Data is AssetBundleRequestOptions ab)
            {
    
    
                //Debug.Log("load primaryKey:"+location.PrimaryKey);
                if (BuildLocalBundleData.Index(location.PrimaryKey))
                {
    
    
                    var path = Path.Combine(AddressablePath.StreamingLoadPath,AddressablePath.GetPlatformForAssetBundles(Application.platform), location.PrimaryKey);
                    //Debug.Log($"--------exit:{path}---------------");
                    return  path;
                }
                else
                {
    
    
                    var path = Path.Combine(Caching.currentCacheForWriting.path, ab.BundleName, ab.Hash, "__data");
                    if (File.Exists(path))
                    {
    
    
                        return path;
                    }
                }
            }
            return location.InternalId;
        }

Addressable更新流程

1.请求服务器获取到资源服务器地址。
2.请求资源服务器上的catalog.hash文件。如果和本地保存的有变化,则说明有热更。
本地的默认在这个目录

  internal const string kCacheDataFolder = "{UnityEngine.Application.persistentDataPath}/com.unity.addressables/";

要取这个目录把下载的Catalog文件覆盖原来的。
3.有热更的情况下,去下载Catalog.json文件。覆盖本地的。
4.下载热更文件。

热更文件对比工具

可以查看两个版本,那些Bundle文件和资源文件发生了改变:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AddressableToolkit;
using Codice.Client.Common;
using Platform.Editor;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.AddressableAssets.ResourceLocators;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.ResourceProviders;

public class ComFoldEditor : EditorWindow
{
    
    
   GUIStyle mfileStyle;
   private string basPath;
   private string updatePath;
   long totalSize = 0;
   IOrderedEnumerable<KeyValuePair<string, long>> sortResult;
   Dictionary<string, List<string>> bundle4Key = new Dictionary<string, List<string>>();
   ToggleTreeview globaltree;
   TreeViewItem globalRoot;
   
   
   [MenuItem("Tools/@Addressable/比较Bundle")]
   public static void OpenWindow()
   {
    
    
      GetWindow<ComFoldEditor>().Show();
    
   }

   private void OnEnable()
   {
    
    
      mfileStyle = new GUIStyle();
      mfileStyle.normal.background = EditorGUIUtility.FindTexture("Folder Icon");
      string prefBasPath = EditorPrefs.GetString("basPath");
      if (Directory.Exists(prefBasPath))
      {
    
    
         basPath = prefBasPath;
      }

      string prefUpdatePath = EditorPrefs.GetString("updatePath");
      if (Directory.Exists(prefUpdatePath))
      {
    
    
         updatePath = prefUpdatePath;
      }
      
      
    
   }

   public void InitBundle4Key(string catalogPath)
   {
    
    
      Action<string, IList<IResourceLocation>> logBunSzie = (x, y) =>
      {
    
    
         foreach (IResourceLocation location in y)
         {
    
    
            var sizeData = location.Data as AssetBundleRequestOptions;

            if (sizeData != null)
            {
    
    
               if (!bundle4Key.TryGetValue(location.PrimaryKey, out var collectList))
               {
    
    
                  collectList = new List<string>();
                  bundle4Key.Add(location.PrimaryKey, collectList);
               }
               if (!collectList.Contains(x))
               {
    
    
                  collectList.Add(x);
               }
            }

            break;
         }
      };

      try
      {
    
    
         var conetentData = JsonUtility.FromJson<ContentCatalogData>(File.ReadAllText(catalogPath));
         var resMap = conetentData.CreateLocator();
         foreach (var item in resMap.Locations)
         {
    
    
            foreach (IResourceLocation location in item.Value.Distinct())
            {
    
    
               if (location.Data is AssetBundleRequestOptions ab)
               {
    
    
               }
               else
               {
    
    
                  logBunSzie(location.PrimaryKey, location.Dependencies);
               }
            }
         }
      }
      catch (Exception e)
      {
    
    
         Debug.LogError("热更bundle目录下catalog文件不存在");
         Debug.LogError(e);
         throw;
      }
   }

   private void InitTreeData()
   {
    
    
        if (globalRoot == null)
        {
    
    
           int id = -1;
           globalRoot = new TreeViewItem();
           globalRoot.id = id;
           globalRoot.depth = -1;
           foreach (var l in sortResult)
           {
    
    
              var tItem = new TreeViewItem();
              tItem.id = ++id;
              tItem.depth = 0;
              tItem.displayName = $"{
      
      l.Key}   {
      
      GetFileSize(l.Value)}";
              globalRoot.AddChild(tItem);
              
              if(bundle4Key.TryGetValue(l.Key,out var address))
              {
    
    
                 foreach (var VARIABLE in address)
                 {
    
    
                    TreeViewItem addressName = new TreeViewItem();
                    addressName.displayName = VARIABLE;
                    addressName.id = ++id;
                    addressName.depth = 1;
                    tItem.AddChild(addressName);
                 }
              }
           }     
        }
      
        if (globaltree == null)
        {
    
    
           globaltree = new ToggleTreeview("", "", position.width, globalRoot, true, true, 18);
           // globaltree.CellCallBack += DrawItemCell;
           globaltree.Reload();
           globaltree.OnDoubleClicked = (item) =>
           {
    
    
              string copy = item.displayName.Split(' ')[0];
              GUIUtility.systemCopyBuffer =copy;
              GetWindow<ComFoldEditor>().ShowNotification(new GUIContent("已copy"));
           };
        }
   }

   private Vector3 mScrollPos;
   private void OnGUI()
   {
    
    
      GUILayout.BeginHorizontal();
      GUILayout.Label("底包bundle目录:",GUILayout.Width(100));
      basPath = EditorGUILayout.TextField(basPath, GUILayout.Width(420));
      if (GUILayout.Button("选择"))
      {
    
    
         basPath = EditorUtility.OpenFolderPanel("bundle目录", "", "");
         EditorPrefs.SetString("basPath",basPath);
      }
      GUILayout.EndHorizontal();
      
      GUILayout.BeginHorizontal();
      GUILayout.Label("热更bundle目录:",GUILayout.Width(100));
      updatePath = EditorGUILayout.TextField(updatePath, GUILayout.Width(420));
      if (GUILayout.Button("选择"))
      {
    
    
         updatePath = EditorUtility.OpenFolderPanel("bundle目录", "", "");
         EditorPrefs.SetString("updatePath",updatePath);
      }
      GUILayout.EndHorizontal();

      if (GUILayout.Button("比较"))
      {
    
    
         string catalogPath = updatePath + "/catalog_base.json";
         InitBundle4Key(catalogPath);
         CompareFold(basPath,updatePath);
         globalRoot = null;
         globaltree = null;
         InitTreeData();
      }

      if (totalSize > 0)
      {
    
    
         GUILayout.Label("热更大小:"+GetFileSize(totalSize));
      }
      if (sortResult !=null && sortResult.Any())
      {
    
     
         GUILayout.BeginArea(new Rect(10,100,this.position.width-20,this.position.height-100));
         mScrollPos = GUILayout.BeginScrollView(mScrollPos);
         if (globaltree != null)
         {
    
    
            globaltree.OnGUI(new Rect(0, 0, this.position.width - 20, this.position.height - 100));
         }
         GUILayout.EndScrollView();
         GUILayout.EndArea();
      }
  
      
   }

   public void CompareFold(string basePath,string updatePath)
   {
    
    
      if (string.IsNullOrEmpty(basePath) || string.IsNullOrEmpty(updatePath))
      {
    
    
         Debug.LogError("请选择目录!");
         return;
      }
      List<string> baseBundleFullNames = new List<string>();
      CollectAllFilesName(basePath,baseBundleFullNames);
      List<string> baseBundleNames = baseBundleFullNames.ConvertAll(Path.GetFileName);
      // baseBundleNames.ForEach(Debug.Log);

      List<string> updateBundleFullNames = new List<string>();
      CollectAllFilesName(updatePath,updateBundleFullNames);
  

      Dictionary<string,long> result = new Dictionary<string,long>();
      foreach (var VARIABLE in updateBundleFullNames)
      {
    
    
         //名字一样  没有发生变化
         if (baseBundleNames.Contains(Path.GetFileName(VARIABLE)))
         {
    
    
            continue;
         }
         //新增文件
         result.Add(Path.GetFileName(VARIABLE),new FileInfo(VARIABLE).Length);
      }

      
      sortResult = from d in result orderby d.Value descending select d;
      totalSize = 0;
      foreach (var l in sortResult)
      {
    
    
         totalSize += l.Value;
         Debug.Log(l.Key +"            "+ GetFileSize(l.Value));
      }
      Debug.Log("total size:"+ GetFileSize(totalSize));
   }

   public static void CollectAllFilesName(string path, List<string> result)
   {
    
    
      if (result == null)
      {
    
    
         Debug.LogError("result list is null.");
      }

      foreach (var filePath in Directory.GetFiles(path))
      {
    
    
         if (filePath.EndsWith("bundle"))
         {
    
    
            result.Add(filePath);
         }
      }

      foreach (var directory in Directory.GetDirectories(path))
      {
    
    
         CollectAllFilesName(directory,result);
      }
   }
   
   public static string GetFileSize(long byteCount)
   {
    
    
      string size = "0 B";
      if (byteCount >= 1073741824.0)
         size = $"{
      
      byteCount / 1073741824.0:##.##}" + " GB";
      else if (byteCount >= 1048576.0)
         size = $"{
      
      byteCount / 1048576.0:##.##}" + " MB";
      else if (byteCount >= 1024.0)
         size = $"{
      
      byteCount / 1024.0:##.##}" + " KB";
      else if (byteCount > 0 && byteCount < 1024.0)
         size = byteCount.ToString() + " B";

      return size;
   }
}

对比效果如下:

在这里插入图片描述

优化

1.catalog文件太大了,可以用JsonCompress压缩下。

猜你喜欢

转载自blog.csdn.net/KindSuper_liu/article/details/129049424