[Unity热更新]tolua# & LuaFramework(八):更新下载(上)

效果图:

未更新前的:


重新启动后:



更新流程分析:

1.找到GameManager.cs,这个类包含了解包和更新下载的操作,所以很值得分析一下。找到CheckExtractResource方法,如果是第一次运行游戏,则进行解包;否则就跳过解包流程。无论是哪种情况,最终都会开启协程,执行OnUpdateResource方法。此时如果AppConst.UpdateMode为true,那么就会进行更新下载的流程;否则就不会更新了。无论是哪种情况,最终都会执行OnResourceInited方法,加载lua代码,进行lua逻辑。


那么重点显然就是OnUpdateResource这个方法了。它向服务器发送的第一条信息为:http://localhost:6688/files.txt?v=当前时间,这条信息到达服务器后,会由HttpServer中的OnGetRequest进行处理,首先这条信息会被截去http://localhost:6688/和?v=当前时间,剩下files.txt,然后会在Server的同级目录下找Assets/StreamingAssets下找files.txt,如果找到就会发送给客户端。客户端得到这个从服务器下来的最新files.txt,就会进行MD5值的比较。如果发现本地没有这个文件,又或者MD5值不同,那么就会进行下载了。


找到要下载的文件后,就会发送一条NotiConst.UPDATE_MESSAGE的信息,此时AppView就会收到这条信息,在UpdateMessage方法我们可以添加处理方法。但是运行demo,我们并没有发现AppView这个类。这个类是动态挂上去的,具体的看StartUpCommand。



2.那么下载的过程是怎样的呢?GameManager提供两种方法,一种是WWW,另一种是线程下载。如果使用线程下载,那么会把要下载的任务放到一个队列中,在ThreadManager.cs中的OnUpdate方法中进行处理。在ProgressChanged方法可以获取下载进度。


如果我们想添加一些自定义的东西的话,可以考虑扩充一下AppView.cs。注意要先在场景建一个空物体,命名为GlobalGenerator。



3.通过上面的分析,不难看出,它是先解包,然后更新资源,接着进行lua逻辑,最后展示界面。而很多情况下,我们是先展示主界面(即选择服务器及显示下载进度的那个界面),然后才解包并更新资源的,这就显得有些矛盾了。写到这里,我不禁想到一个问题,那就是lua代码和资源的加载方式是怎样的呢?

找到LuaManager.cs,默认加载的是Util.DataPath下的lua文件。

找到ResourceManager.cs,同样加载的是Util.DataPath下的资源。


那么,整理一下思路。

a.启动游戏后,需要把主界面先显示出来,要显示主界面,就需要解包,但是不是全部解包,而是对lua文件和主界面进行解包。对lua文件解包是因为,主界面的lua代码需要依赖一些核心的lua文件,所以干脆就把lua文件全都解包吧。

b.显示完主界面后,就可以尽情地解包更新下载了。这里可能会有疑问,就是如果主界面的资源和lua逻辑要更新,要怎么办呢?其实并不影响,因为资源已经躺在内存中,而逻辑几乎是不变的。

总结:解包过程分两次。第一次解包后执行lua逻辑,第二次解包后就更新。为什么要解包两次呢?因为apk体积都较大,解包耗时,因此建议以进度条形式表现。



4.那么分析完就可以进行实践了。

a.首先把AppView这个东西弄出来,上面说了。然后Build,然后把c盘的luaframework文件夹整个删掉。

如果是NGUI版本,需要修改一下ResourceManager.cs的initialize方法,把if (AppConst.ExampleMode)里的东西都注释掉,否则会报FileNotFoundException: Could not find file "c:\luaframework\shared.unity3d".的错误。

修改GameManager.cs:

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using LuaInterface;
using System.Reflection;
using System.IO;

namespace LuaFramework {
    public class GameManager : Manager {
        protected static bool initialize = false;
        private List<string> downloadFiles = new List<string>();

        /// <summary>
        /// 初始化游戏管理器
        /// </summary>
        void Awake() {
            Init();
        }

        /// <summary>
        /// 初始化
        /// </summary>
        void Init() {
            if (AppConst.ExampleMode) {
                InitGui();
            }
            DontDestroyOnLoad(gameObject);  //防止销毁自己

            CheckExtractResource(); //释放资源
            Screen.sleepTimeout = SleepTimeout.NeverSleep;
            Application.targetFrameRate = AppConst.GameFrameRate;
        }

        /// <summary>
        /// 初始化GUI
        /// </summary>
        public void InitGui() {
            string name = "UI Root";
            GameObject gui = GameObject.Find(name);
            if (gui != null) return;

            GameObject prefab = Util.LoadPrefab(name);
            gui = Instantiate(prefab) as GameObject;
            gui.name = name;
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void CheckExtractResource() {
            hadExtractResource = Directory.Exists(Util.DataPath) &&
              Directory.Exists(Util.DataPath + "lua/") && File.Exists(Util.DataPath + "files.txt");
            if (hadExtractResource || AppConst.DebugMode)
            {
                ResManager.initialize(OnResourceInited);
                //StartCoroutine(OnUpdateResource());//在第二次解包后就更新
                return;   //文件已经解压过了,自己可添加检查文件列表逻辑
            }
            StartCoroutine(OnExtractResource());    //启动释放协成 
        }

        bool hadExtractResource;
        bool firstExtractResource = true;

        IEnumerator OnExtractResource() {
            string dataPath = Util.DataPath;  //数据目录
            string resPath = Util.AppContentPath(); //游戏包资源目录

            string infile = resPath + "files.txt";
            string outfile = dataPath + "files.txt";
            string message = "";

            if (firstExtractResource)
            {
                //创建Util.DataPath目录
                if (Directory.Exists(dataPath)) Directory.Delete(dataPath, true);
                Directory.CreateDirectory(dataPath);

                if (File.Exists(outfile)) File.Delete(outfile);

                //解包files.txt
                message = "正在解包文件:>files.txt";
                Debug.Log("正在解包文件:>files.txt");
                facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message);

                if (Application.platform == RuntimePlatform.Android)
                {
                    WWW www = new WWW(infile);
                    yield return www;

                    if (www.isDone)
                    {
                        File.WriteAllBytes(outfile, www.bytes);
                    }
                    yield return 0;
                }
                else File.Copy(infile, outfile, true);
                yield return new WaitForEndOfFrame();
            }

            //释放文件到数据目录
            string[] files = File.ReadAllLines(outfile);
            foreach (var file in files) 
            {
                string[] fs = file.Split('|');
                infile = resPath + fs[0];  
                outfile = dataPath + fs[0];

                //start是主界面的包,需要自行修改
                bool a = fs[0].StartsWith("lua/") || fs[0].StartsWith("StreamingAssets") ||fs[0].StartsWith("start");
                if (firstExtractResource && !a) continue;
                if (!firstExtractResource && a) continue;

                message = "正在解包文件:>" + fs[0];
                Debug.Log("正在解包文件:>" + infile);
                facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message);

                string dir = Path.GetDirectoryName(outfile);
                if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);

                if (Application.platform == RuntimePlatform.Android) {
                    WWW www = new WWW(infile);
                    yield return www;

                    if (www.isDone) {
                        File.WriteAllBytes(outfile, www.bytes);
                    }
                    yield return 0;
                } else {
                    if (File.Exists(outfile)) {
                        File.Delete(outfile);
                    }
                    File.Copy(infile, outfile, true);
                }
                yield return new WaitForEndOfFrame();
            }
            if (firstExtractResource)
            {
                ResManager.initialize(OnResourceInited);
            }
            else
            {
                message = "解包完成!!!";
                facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message);

                //释放完成,开始启动更新资源
                StartCoroutine(OnUpdateResource());
            }

            yield return new WaitForSeconds(0.1f);
            message = string.Empty;
        }

        /// <summary>
        /// 启动更新下载,这里只是个思路演示,此处可启动线程下载更新
        /// </summary>
        IEnumerator OnUpdateResource() {
            downloadFiles.Clear();

            if (!AppConst.UpdateMode) {
                //ResManager.initialize(OnResourceInited);
                yield break;
            }
            string dataPath = Util.DataPath;  //数据目录
            string url = AppConst.WebUrl;
            string random = DateTime.Now.ToString("yyyymmddhhmmss");
            string listUrl = url + "files.txt?v=" + random;
            Debug.LogWarning("LoadUpdate---->>>" + listUrl);

            WWW www = new WWW(listUrl); yield return www;
            if (www.error != null) {
                OnUpdateFailed(string.Empty);
                yield break;
            }
            if (!Directory.Exists(dataPath)) {
                Directory.CreateDirectory(dataPath);
            }
            File.WriteAllBytes(dataPath + "files.txt", www.bytes);

            string filesText = www.text;
            string[] files = filesText.Split('\n');

            string message = string.Empty;
            for (int i = 0; i < files.Length; i++) {
                if (string.IsNullOrEmpty(files[i])) continue;
                string[] keyValue = files[i].Split('|');
                string f = keyValue[0];
                string localfile = (dataPath + f).Trim();
                string path = Path.GetDirectoryName(localfile);
                if (!Directory.Exists(path)) {
                    Directory.CreateDirectory(path);
                }
                string fileUrl = url + keyValue[0] + "?v=" + random;
                bool canUpdate = !File.Exists(localfile);
                if (!canUpdate) {
                    string remoteMd5 = keyValue[1].Trim();
                    string localMd5 = Util.md5file(localfile);
                    canUpdate = !remoteMd5.Equals(localMd5);
                    if (canUpdate) File.Delete(localfile);
                }
                if (canUpdate) {   //本地缺少文件
                    Debug.Log(fileUrl);
                    message = "downloading>>" + fileUrl;
                    facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message);
                    /*
                    www = new WWW(fileUrl); yield return www;
                    if (www.error != null) {
                        OnUpdateFailed(path);   //
                        yield break;
                    }
                    File.WriteAllBytes(localfile, www.bytes);
                     * */
                    //这里都是资源文件,用线程下载
                    BeginDownload(fileUrl, localfile);
                    while (!(IsDownOK(localfile))) { yield return new WaitForEndOfFrame(); }
                }
            }
            yield return new WaitForEndOfFrame();
            message = "更新完成!!";
            facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message);

            //ResManager.initialize(OnResourceInited);
        }

        /// <summary>
        /// 是否下载完成
        /// </summary>
        bool IsDownOK(string file) {
            return downloadFiles.Contains(file);
        }

        /// <summary>
        /// 线程下载
        /// </summary>
        void BeginDownload(string url, string file) {     //线程下载
            object[] param = new object[2] {url, file};

            ThreadEvent ev = new ThreadEvent();
            ev.Key = NotiConst.UPDATE_DOWNLOAD;
            ev.evParams.AddRange(param);
            ThreadManager.AddEvent(ev, OnThreadCompleted);   //线程下载
        }

        /// <summary>
        /// 线程完成
        /// </summary>
        /// <param name="data"></param>
        void OnThreadCompleted(NotiData data) {
            switch (data.evName) {
                case NotiConst.UPDATE_EXTRACT:  //解压一个完成
                    //
                break;
                case NotiConst.UPDATE_DOWNLOAD: //下载一个完成
                    downloadFiles.Add(data.evParam.ToString());
                break;
            }
        }

        void OnUpdateFailed(string file) {
            string message = "更新失败!>" + file;
            facade.SendMessageCommand(NotiConst.UPDATE_MESSAGE, message);
        }

        /// <summary>
        /// 资源初始化结束
        /// </summary>
        public void OnResourceInited() {
            LuaManager.InitStart();
            LuaManager.DoFile("Logic/Game");            //加载游戏
            LuaManager.DoFile("Logic/Network");         //加载网络
            NetManager.OnInit();                        //初始化网络

            Util.CallMethod("Game", "OnInitOK");          //初始化完成
            initialize = true;                          //初始化完 

            //类对象池测试
            var classObjPool = ObjPoolManager.CreatePool<TestObjectClass>(OnPoolGetElement, OnPoolPushElement);
            //方法1
            //objPool.Release(new TestObjectClass("abcd", 100, 200f));
            //var testObj1 = objPool.Get();

            //方法2
            ObjPoolManager.Release<TestObjectClass>(new TestObjectClass("abcd", 100, 200f));
            var testObj1 = ObjPoolManager.Get<TestObjectClass>();

            Debugger.Log("TestObjectClass--->>>" + testObj1.ToString());

            //游戏对象池测试
            var prefab = Resources.Load("TestGameObjectPrefab", typeof(GameObject)) as GameObject;
            var gameObjPool = ObjPoolManager.CreatePool("TestGameObject", 5, 10, prefab);

            var gameObj = Instantiate(prefab) as GameObject;
            gameObj.name = "TestGameObject_01";
            gameObj.transform.localScale = Vector3.one;
            gameObj.transform.localPosition = Vector3.zero;

            ObjPoolManager.Release("TestGameObject", gameObj);
            var backObj = ObjPoolManager.Get("TestGameObject");
            backObj.transform.SetParent(null);

            Debug.Log("TestGameObject--->>>" + backObj);

            if (hadExtractResource)
            {
                StartCoroutine(OnUpdateResource());
            }
            else
            {
                firstExtractResource = false;//进行二次解包
                hadExtractResource = true;
                StartCoroutine(OnExtractResource());
            }
        }

        /// <summary>
        /// 当从池子里面获取时
        /// </summary>
        /// <param name="obj"></param>
        void OnPoolGetElement(TestObjectClass obj) {
            Debug.Log("OnPoolGetElement--->>>" + obj);
        }

        /// <summary>
        /// 当放回池子里面时
        /// </summary>
        /// <param name="obj"></param>
        void OnPoolPushElement(TestObjectClass obj) {
            Debug.Log("OnPoolPushElement--->>>" + obj);
        }

        /// <summary>
        /// 析构函数
        /// </summary>
        void OnDestroy() {
            if (NetManager != null) {
                NetManager.Unload();
            }
            if (LuaManager != null) {
                LuaManager.Close();
            }
            Debug.Log("~GameManager was destroyed");
        }
    }
}


上面代码的意思就是,如果已经解包了,那么就把主界面展示出来,并且检查更新;如果还没解包,那么就先解包一部分,把主界面展示出来,再解包剩下的,并且检查更新。

如无意外,运行几次游戏都会正常的。



b.因为需要更新,所以说一下测试更新方法:将原有的StreamingAssets进行备份,修改项目中的资源,重新生成,在服务器Server的同级目录下建一个Assets文件夹,再将新的StreamingAssets放进去,还原旧的StreamingAssets。那么,如何反复测试呢?把c盘的luaframework文件夹删除即可。运行服务器,设置AppConst.UpdateMode为true,再运行游戏即可。



  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值