当前位置:   article > 正文

Unity热更新系列之四: 分包工具_unity 如何实现分包更新

unity 如何实现分包更新

为什么要做小包跟分包,因为玩家对包体的大小还是有敏感的,渠道商拿更小的Apk包去做推广时玩家下载的可能性就更高,对于推广成本来说就更低了,通常是要求包体大小在150M以内。做分包的思路还是比较简单的,把玩家前期用到资源提取出来打包的时候放进包体里,而其它剩余部分则放在我们的资源服务器上。

那我们就可以写个工具把玩家游戏时用到的bundle资源按时间轴的顺序记录下来,这个代码实现还是比较简单的

  1. using System.IO;
  2. using System;
  3. using System.Threading;
  4. using System.Collections.Generic;
  5. using UnityEngine;
  6. public class RecordeBundles
  7. {
  8. #region
  9. private static RecordeBundles _instance;
  10. public static RecordeBundles Instance
  11. {
  12. get
  13. {
  14. if (null == _instance)
  15. {
  16. _instance = new RecordeBundles();
  17. }
  18. return _instance;
  19. }
  20. }
  21. #endregion
  22. private bool _recordeBundle = false;
  23. private Thread _thread;
  24. static readonly object _lockObj = new object();
  25. private string _recordFile = string.Empty;
  26. private Queue<string> _subBundles = new Queue<string>();
  27. private DateTime startTime;
  28. private List<string> _bundles = new List<string>();
  29. private RecordeBundles()
  30. {
  31. _recordeBundle = true; //可以自己设定是否开启记录
  32. if (_recordeBundle)
  33. {
  34. string recordDir = Util.DataPath + "FirstResTool/Config";
  35. if (!Directory.Exists(recordDir))
  36. {
  37. Directory.CreateDirectory(recordDir);
  38. }
  39. string time = DateTime.Now.ToString("MMdd_HHmm");
  40. _recordFile = string.Concat(recordDir, "/", time, "_log.log");
  41. if (File.Exists(_recordFile))
  42. {
  43. File.Delete(_recordFile);
  44. }
  45. startTime = DateTime.UtcNow;
  46. _thread = new Thread(Recorde2Text);
  47. _thread.Start();
  48. }
  49. }
  50. public void Recorde(string path)
  51. {
  52. if (_recordeBundle)
  53. {
  54. lock (_lockObj)
  55. {
  56. string file = path.Replace(Util.DataPath, "").Replace(Util.AppContentPath(), "");
  57. if (!_bundles.Contains(file))
  58. {
  59. _bundles.Add(file);
  60. _subBundles.Enqueue(file);
  61. }
  62. }
  63. }
  64. }
  65. private void Recorde2Text()
  66. {
  67. while (true)
  68. {
  69. lock (_lockObj)
  70. {
  71. if (_subBundles.Count > 0)
  72. {
  73. double minu = DateTime.UtcNow.Subtract(startTime).TotalMinutes;
  74. StreamWriter sw = new StreamWriter(_recordFile, true);
  75. for (int i = 0; i < _subBundles.Count; i++)
  76. {
  77. sw.WriteLine(string.Format("{0}minute:{1}", Mathf.FloorToInt((float)minu) + 1, _subBundles.Dequeue()));
  78. }
  79. sw.Close();
  80. }
  81. }
  82. Thread.Sleep(1000);
  83. }
  84. }
  85. }

然后在加载bundle资源的地方去调用Recorde(string path)接口,每次游戏启动都会在Application.persistentDataPath目录下生成一份记录加载资源的文件直到关闭游戏。

我们就可以通过这些记录文件把需要时间内的资源提取出来放进小包里,写个python脚本处理下

  1. #!user/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import os
  4. import sys
  5. import json
  6. import shutil
  7. import codecs
  8. import time
  9. __author__ = "qingsen"
  10. curDir = ""
  11. configDir = ""
  12. firstDir = ""
  13. sortedDicRes = {}
  14. configTable = {}
  15. minutes = []
  16. platforms = ["android"]
  17. curPlat = "android"
  18. copys = []
  19. resDir = ""
  20. maxMinute = 0
  21. def ReadConfig():
  22. config = os.path.join(curDir,"Config/config.json")
  23. if not os.path.exists(config):
  24. print u"config.json 配置文件不存在"
  25. return False
  26. print u"read config:" + config
  27. global configTable
  28. global platforms
  29. with open(config, "r") as configFile:
  30. configTable = json.load(configFile)
  31. platforms = configTable["platforms"]
  32. return True
  33. def ReadRecords():
  34. global sortedDicRes
  35. sortedDicRes = []
  36. dicRes = {}
  37. num = 0
  38. path = configDir + curPlat
  39. for f in os.listdir(path):
  40. if os.path.isfile(os.path.join(path,f)):
  41. fileType = os.path.splitext(f)[-1]
  42. if fileType == ".log" :
  43. num = num + 1
  44. print u"读取首包记录文件:" + os.path.join(path,f).replace("\\","/")
  45. with codecs.open(os.path.join(path,f).replace("\\","/"), "r", "utf-8-sig") as recordFile:
  46. for line in recordFile.readlines():
  47. lis = line.strip().split(":")
  48. minu = lis[0].replace("minute","")
  49. #print "minute: %d res: %s" % (int(minu),lis[1])
  50. if not "Lua_Bundles" in lis[1]:
  51. if not (int(minu) in dicRes):
  52. dicRes[int(minu)] = []
  53. if not (lis[1] in dicRes[int(minu)]):
  54. dicRes[int(minu)].append(lis[1])
  55. sortedDicRes = sorted(dicRes.items(), key=lambda d:d[0])
  56. if 0 == num:
  57. print u"首包资源记录文件不存在"
  58. def CopyFirsRes():
  59. global maxMinute
  60. global copys
  61. copys = []
  62. lastMinute = 0
  63. for minute in minutes:
  64. for item in sortedDicRes:
  65. minu = item[0]
  66. if (minu > lastMinute) and (minu <= int(minute)):
  67. for res in item[1]:
  68. if not (res in copys):
  69. copys.append(res)
  70. maxMinute = int(minute)
  71. CopyFile(res, lastMinute, minute)
  72. elif minu > int(minute) :
  73. lastMinute = int(minute)
  74. break
  75. # for res in copys:
  76. # CopyFile(res, 0, str(maxMinute))
  77. def CopyOtherRes():
  78. allRes = []
  79. filterFile = [".meta", ".manifest"]
  80. for root,dirs,files in os.walk(resDir):
  81. if not "Lua_Bundles" in root:
  82. for file in files:
  83. filePath = os.path.join(root,file)
  84. ftype = os.path.splitext(filePath)[-1]
  85. if not ftype in filterFile:
  86. allRes.append(filePath.replace(resDir,"").replace("\\","/"))
  87. for res in allRes:
  88. if not (res in copys):
  89. CopyFile(res, maxMinute, "end")
  90. def CopyFile(res, start, end):
  91. destinyFile = resDir + res
  92. if not os.path.exists(destinyFile):
  93. raise ValueError('res not exist: ' + destinyFile)
  94. else:
  95. toDir = "%s/%d-%s" % (firstDir + curPlat, start+1, end)
  96. topath = os.path.join(toDir,res).replace("\\","/")
  97. if not os.path.exists(os.path.dirname(topath)):
  98. os.makedirs(os.path.dirname(topath))
  99. print u"拷贝首包文件:" + destinyFile
  100. if not os.path.exists(topath):
  101. shutil.copyfile(destinyFile,topath)
  102. def ClearDir():
  103. for plat in platforms:
  104. if os.path.exists(firstDir + plat):
  105. shutil.rmtree(firstDir + plat)
  106. if __name__ == '__main__':
  107. curDir = sys.path[0]
  108. configDir = curDir + "/Config/"
  109. configDir = configDir.replace("\\","/")
  110. firstDir = os.path.dirname(curDir)+"/FirstRes_"
  111. firstDir = firstDir.replace("\\","/")
  112. if ReadConfig():
  113. ClearDir()
  114. for plat in platforms:
  115. curPlat = plat
  116. minutes = configTable[curPlat]
  117. print minutes
  118. resDir = (os.path.dirname(curDir) + "/" + curPlat + "/").replace("\\","/")
  119. ReadRecords()
  120. CopyFirsRes()
  121. CopyOtherRes()
  122. print u"首包资源处理完毕-----------"

筛选配置config.json如下,会根据你的时间段把资源分成多个包,第一个时间段的资源就是小包的资源。

  1. {
  2. "platforms": ["android"],
  3. "android": [5,7,11,20,27,46,56,66,76,86],
  4. "ios": [5,7,11,20,27,46,56,66,76,86]
  5. }

工具代码有兴趣的可以参考下,提供个思路

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/82124
推荐阅读
相关标签
  

闽ICP备14008679号