unity 熱更新思路和實現

聲明:本文介紹的熱更新方案是我在網上搜索到的,然後自己修改了一下,相當於是借鑑了別人的思路,加工成了自己的,在此感謝無私分享經驗的朋友們。

想要使用熱更新技術,需要規劃設計好資源比較策略,資源版本,確保增加新資源後可以下載到本地,有資源更新的時候可以替換掉本地舊資源。我在前面寫了一篇“unity 打包AssetBundle”的文章,裏面生成了一個資源版本文件,不多解釋了,上圖。至於怎麼生成這個文件的,可以看一下我前面寫的文章。


廢話不多說。


wKioL1er5uqQ3DVFAAI62J8DbGQ610.png-wh_50


先介紹熱更新步驟,後上代碼

步驟一、在Resources目錄下新建一個文本,名稱是bundle_list(後綴是.txt),內容如下:

    {"id":0,"version":"1.0","manifest":"android","resource":{}},當然您可以根據自己項目

    實際情況來設計json格式。資源服務器上也會有一份格式相同的bundle_list

步驟二、如果是第一次進入遊戲,Application.persistentDataPath目錄下還沒有bundle_list文件,這

            時候就需要用Resources.Load方法從Resources目錄中加載出來。否則

            加載Application.persistentDataPath目錄下的bundle_list

步驟三、從資源服務器下載bundle_list文件

步驟四、獲取本地bundle_list的id和資源服務器下載的bundle_list中的id,做對比,如果前者等於後者,

            則不需要更新,如果前者小於後者,則需要更新。

步驟五、分別解析出本地和資源服務器bundle_list中的資源路徑名稱,名稱相同的,對比hash值,相同

    則不需要更新,反之,更新。如果資源服務器有的名稱本地沒有,則表示是新增資源,需要

    下載到本地。

步驟六、把資源服務器的bundle_list覆蓋本地bundle_list。熱更新完成。


代碼:


using UnityEngine;

using System.Collections;

using System.Collections.Generic;

using System.Text;

using System.IO;

using LitJson;


/**

 * 資源增量更新

 * 1.檢查本地Application.persistentDataPath目錄中是否有bundle_list文件

 * 2.如果沒有,則從Resources目錄中讀取bundle_list文件

 * 3.從服務器上下載bundle_list文件,判斷版本是否一致,如果一致就不用更新

 * 4.版本不一致,需要更新,更新

 * 5.將最新的bundle_list存入Application.persistentDataPath目錄中

 **/

public class BundleUpdate : MonoBehaviour

{

    private static readonly string VERSION_FILE = "bundle_list";

    private string SERVER_RES_URL = "";

    private string LOCAL_RES_URL = "";

    private string LOCAL_RES_PATH = "";



    /// <summary>

    /// 本地版本json對象

    /// </summary>

    private JsonData jdLocalFile;

    /// <summary>

    /// 服務端版本json對象

    /// </summary>

    private JsonData jdServerFile;

    /// <summary>

    /// 本地資源名和路徑字典

    /// </summary>

    private Dictionary<string, string> LocalBundleVersion;

    /// <summary>

    /// 服務器資源名和路徑字典

    /// </summary>

    private Dictionary<string, string> ServerBundleVersion;

    /// <summary>

    /// 需要下載的文件List

    /// </summary>

    private List<string> NeedDownFiles;

    /// <summary>

    /// 是否需要更新本地版本文件

    /// </summary>

    private bool NeedUpdateLocalVersionFile = false;

    /// <summary>

    /// 下載完成委託

    /// </summary>

    /// <param name="www"></param>

    public delegate void HandleFinishDownload(WWW www);

    /// <summary>

    /// 本次一共需要更新的資源數

    /// </summary>

    int totalUpdateFileCount = 0;


    void Start()

    {


#if UNITY_EDITOR && UNITY_ANDROID                                                                         

        SERVER_RES_URL = "file:///" + Application.streamingAssetsPath + "/android/";

        LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/";

        LOCAL_RES_PATH = Application.persistentDataPath + "/res/";

#elif UNITY_EDITOR && UNITY_IOS

        SERVER_RES_URL = "file://" + Application.streamingAssetsPath + "/ios/";

        LOCAL_RES_URL =  "file:///" + Application.persistentDataPath + "/res/";

        LOCAL_RES_PATH =  Application.persistentDataPath + "/res/";

#elif UNITY_ANDROID

        //安卓下需要使用www加載StreamingAssets裏的文件,Streaming Assets目錄在安卓下的路徑爲 "jar:file://" + Application.dataPath + "!/assets/"

        SERVER_RES_URL =  "jar:file://" + Application.dataPath + "!/assets/" + "android/";

        LOCAL_RES_URL =  "jar:file://" + Application.persistentDataPath + "!/assets/" + "/res/";

        //LOCAL_RES_URL =  "file://" + Application.persistentDataPath + "/res/";

        LOCAL_RES_PATH =  Application.persistentDataPath + "/res/";

#elif UNITY_IOS

        SERVER_RES_URL = "http://127.0.0.1/resource/ios/"

        LOCAL_RES_URL =  "file:///" + Application.persistentDataPath + "/res/";

        LOCAL_RES_PATH =  Application.persistentDataPath + "/res/";

#endif


        //初始化    

        LocalBundleVersion = new Dictionary<string, string>();

        ServerBundleVersion = new Dictionary<string, string>();

        NeedDownFiles = new List<string>();


        //加載本地version配置    

        string tmpLocalVersion = "";

        if (!File.Exists(LOCAL_RES_PATH + VERSION_FILE))

        {

            TextAsset text = Resources.Load(VERSION_FILE) as TextAsset;

            tmpLocalVersion = text.text;

        }

        else

        {

            tmpLocalVersion = File.ReadAllText(LOCAL_RES_PATH + VERSION_FILE);

        }


        //保存本地的version    

        ParseVersionFile(tmpLocalVersion, LocalBundleVersion, 0);

        //加載服務端version配置    

        StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate (WWW serverVersion)

        {

            //保存服務端version    

            ParseVersionFile(serverVersion.text, ServerBundleVersion, 1);

            //計算出需要重新加載的資源    

            CompareVersion();

            //加載需要更新的資源    

            DownLoadRes();

        }));

    }


    //依次加載需要更新的資源    

    private void DownLoadRes()

    {

        if (NeedDownFiles.Count == 0)

        {

            UpdateLocalVersionFile();

            return;

        }


        string file = NeedDownFiles[0];

        NeedDownFiles.RemoveAt(0);


        StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate (WWW w)

        {

            //將下載的資源替換本地就的資源    

            ReplaceLocalRes(file, w.bytes);

            DownLoadRes();

        }));

    }


    private void ReplaceLocalRes(string fileName, byte[] data)

    {

        try

        {

            string filePath = LOCAL_RES_PATH + fileName;

            if (!File.Exists(filePath))

            {

                string p = Path.GetDirectoryName(filePath);

                if (!Directory.Exists(p))

                    Directory.CreateDirectory(p);

            }


            File.WriteAllBytes(filePath, data);

        }

        catch (System.Exception e)

        {

            Debug.Log("e is " + e.Message);

        }

    }


    //更新本地的version配置    

    private void UpdateLocalVersionFile()

    {

        if (NeedUpdateLocalVersionFile)

        {

            if (!Directory.Exists(LOCAL_RES_PATH))

                Directory.CreateDirectory(LOCAL_RES_PATH);


            StringBuilder versions = new StringBuilder(jdServerFile.ToJson());


            FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create);

            byte[] data = Encoding.UTF8.GetBytes(versions.ToString());

            stream.Write(data, 0, data.Length);

            stream.Flush();

            stream.Close();

        }

    }


    private void CompareVersion()

    {

        int localVersionId;

        int serverVersionId;

        if (jdLocalFile != null && jdLocalFile.Keys.Contains("id"))

            localVersionId = (int)jdLocalFile["id"];

        if (jdServerFile != null && jdServerFile.Keys.Contains("id"))

            serverVersionId = (int)jdServerFile["id"];



#if UNITY_ANDROID || UNITY_EDITOR

        NeedDownFiles.Add("android");

#endif


#if UNITY_IOS


#endif

        foreach (var version in ServerBundleVersion)

        {

            string fileName = version.Key;

            string serverHash = version.Value;

            //新增的資源    

            if (!LocalBundleVersion.ContainsKey(fileName))

            {

                NeedDownFiles.Add(fileName);

            }

            else

            {

                //需要替換的資源    

                string localHash;

                LocalBundleVersion.TryGetValue(fileName, out localHash);

                if (!serverHash.Equals(localHash))

                {

                    NeedDownFiles.Add(fileName);

                }

            }

        }

        totalUpdateFileCount = NeedDownFiles.Count;


        //本次有更新,同時更新本地的version.txt    

        NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;

    }


    /// <summary>

    /// 

    /// </summary>

    /// <param name="content"></param>

    /// <param name="dict"></param>

    /// <param name="flag">0表示本地版本文件,1表示服務器版本文件</param>

    private void ParseVersionFile(string content, Dictionary<string, string> dict, int flag)

    {

        if (content == null || content.Length == 0)

        {

            return;

        }

        JsonData jd = null;

        try

        {

            jd = JsonMapper.ToObject(content);

        }

        catch (System.Exception e)

        {

            Debug.LogError(e.Message);

            return;

        }


        if (flag == 0)//本地

        {

            jdLocalFile = jd;

        }

        else if (flag == 1)//服務器

        {

            jdServerFile = jd;

        }

        else

            return;


        //獲取資源對象

        JsonData resObjs = null;

        if (jd.Keys.Contains("resource"))

            resObjs = jd["resource"];


        if (resObjs != null && resObjs.IsObject && resObjs.Count > 0)

        {

            string[] resNames = new string[resObjs.Count];

            resObjs.Keys.CopyTo(resNames, 0);


            for (int i = 0; i < resNames.Length; i++)

            {

                if (resObjs.Keys.Contains(resNames[i]))

                    dict.Add(resNames[i], resObjs[resNames[i]].ToString());

            }

        }


    }


    private IEnumerator DownLoad(string url, HandleFinishDownload finishFun)

    {

        WWW www = new WWW(url);

        yield return www;

        if (!string.IsNullOrEmpty(www.error))

        {

            Debug.LogError("www.error is " + www.error);

            yield break;

        }

        if (finishFun != null)

        {

            finishFun(www);

        }

        www.Dispose();

    }


}


以上是提供了一個熱更新思路,水平有限,有寫的不對和需要優化的地方,還請不吝賜教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章