为什么要做小包跟分包,因为玩家对包体的大小还是有敏感的,渠道商拿更小的Apk包去做推广时玩家下载的可能性就更高,对于推广成本来说就更低了,通常是要求包体大小在150M以内。做分包的思路还是比较简单的,把玩家前期用到资源提取出来打包的时候放进包体里,而其它剩余部分则放在我们的资源服务器上。
那我们就可以写个工具把玩家游戏时用到的bundle资源按时间轴的顺序记录下来,这个代码实现还是比较简单的
using System.IO;
using System;
using System.Threading;
using System.Collections.Generic;
using UnityEngine;
public class RecordeBundles
{
#region
private static RecordeBundles _instance;
public static RecordeBundles Instance
{
get
{
if (null == _instance)
{
_instance = new RecordeBundles();
}
return _instance;
}
}
#endregion
private bool _recordeBundle = false;
private Thread _thread;
static readonly object _lockObj = new object();
private string _recordFile = string.Empty;
private Queue<string> _subBundles = new Queue<string>();
private DateTime startTime;
private List<string> _bundles = new List<string>();
private RecordeBundles()
{
_recordeBundle = true; //可以自己设定是否开启记录
if (_recordeBundle)
{
string recordDir = Util.DataPath + "FirstResTool/Config";
if (!Directory.Exists(recordDir))
{
Directory.CreateDirectory(recordDir);
}
string time = DateTime.Now.ToString("MMdd_HHmm");
_recordFile = string.Concat(recordDir, "/", time, "_log.log");
if (File.Exists(_recordFile))
{
File.Delete(_recordFile);
}
startTime = DateTime.UtcNow;
_thread = new Thread(Recorde2Text);
_thread.Start();
}
}
public void Recorde(string path)
{
if (_recordeBundle)
{
lock (_lockObj)
{
string file = path.Replace(Util.DataPath, "").Replace(Util.AppContentPath(), "");
if (!_bundles.Contains(file))
{
_bundles.Add(file);
_subBundles.Enqueue(file);
}
}
}
}
private void Recorde2Text()
{
while (true)
{
lock (_lockObj)
{
if (_subBundles.Count > 0)
{
double minu = DateTime.UtcNow.Subtract(startTime).TotalMinutes;
StreamWriter sw = new StreamWriter(_recordFile, true);
for (int i = 0; i < _subBundles.Count; i++)
{
sw.WriteLine(string.Format("{0}minute:{1}", Mathf.FloorToInt((float)minu) + 1, _subBundles.Dequeue()));
}
sw.Close();
}
}
Thread.Sleep(1000);
}
}
}
然后在加载bundle资源的地方去调用Recorde(string path)接口,每次游戏启动都会在Application.persistentDataPath目录下生成一份记录加载资源的文件直到关闭游戏。
我们就可以通过这些记录文件把需要时间内的资源提取出来放进小包里,写个python脚本处理下
#!user/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import json
import shutil
import codecs
import time
__author__ = "qingsen"
curDir = ""
configDir = ""
firstDir = ""
sortedDicRes = {}
configTable = {}
minutes = []
platforms = ["android"]
curPlat = "android"
copys = []
resDir = ""
maxMinute = 0
def ReadConfig():
config = os.path.join(curDir,"Config/config.json")
if not os.path.exists(config):
print u"config.json 配置文件不存在"
return False
print u"read config:" + config
global configTable
global platforms
with open(config, "r") as configFile:
configTable = json.load(configFile)
platforms = configTable["platforms"]
return True
def ReadRecords():
global sortedDicRes
sortedDicRes = []
dicRes = {}
num = 0
path = configDir + curPlat
for f in os.listdir(path):
if os.path.isfile(os.path.join(path,f)):
fileType = os.path.splitext(f)[-1]
if fileType == ".log" :
num = num + 1
print u"读取首包记录文件:" + os.path.join(path,f).replace("\\","/")
with codecs.open(os.path.join(path,f).replace("\\","/"), "r", "utf-8-sig") as recordFile:
for line in recordFile.readlines():
lis = line.strip().split(":")
minu = lis[0].replace("minute","")
#print "minute: %d res: %s" % (int(minu),lis[1])
if not "Lua_Bundles" in lis[1]:
if not (int(minu) in dicRes):
dicRes[int(minu)] = []
if not (lis[1] in dicRes[int(minu)]):
dicRes[int(minu)].append(lis[1])
sortedDicRes = sorted(dicRes.items(), key=lambda d:d[0])
if 0 == num:
print u"首包资源记录文件不存在"
def CopyFirsRes():
global maxMinute
global copys
copys = []
lastMinute = 0
for minute in minutes:
for item in sortedDicRes:
minu = item[0]
if (minu > lastMinute) and (minu <= int(minute)):
for res in item[1]:
if not (res in copys):
copys.append(res)
maxMinute = int(minute)
CopyFile(res, lastMinute, minute)
elif minu > int(minute) :
lastMinute = int(minute)
break
# for res in copys:
# CopyFile(res, 0, str(maxMinute))
def CopyOtherRes():
allRes = []
filterFile = [".meta", ".manifest"]
for root,dirs,files in os.walk(resDir):
if not "Lua_Bundles" in root:
for file in files:
filePath = os.path.join(root,file)
ftype = os.path.splitext(filePath)[-1]
if not ftype in filterFile:
allRes.append(filePath.replace(resDir,"").replace("\\","/"))
for res in allRes:
if not (res in copys):
CopyFile(res, maxMinute, "end")
def CopyFile(res, start, end):
destinyFile = resDir + res
if not os.path.exists(destinyFile):
raise ValueError('res not exist: ' + destinyFile)
else:
toDir = "%s/%d-%s" % (firstDir + curPlat, start+1, end)
topath = os.path.join(toDir,res).replace("\\","/")
if not os.path.exists(os.path.dirname(topath)):
os.makedirs(os.path.dirname(topath))
print u"拷贝首包文件:" + destinyFile
if not os.path.exists(topath):
shutil.copyfile(destinyFile,topath)
def ClearDir():
for plat in platforms:
if os.path.exists(firstDir + plat):
shutil.rmtree(firstDir + plat)
if __name__ == '__main__':
curDir = sys.path[0]
configDir = curDir + "/Config/"
configDir = configDir.replace("\\","/")
firstDir = os.path.dirname(curDir)+"/FirstRes_"
firstDir = firstDir.replace("\\","/")
if ReadConfig():
ClearDir()
for plat in platforms:
curPlat = plat
minutes = configTable[curPlat]
print minutes
resDir = (os.path.dirname(curDir) + "/" + curPlat + "/").replace("\\","/")
ReadRecords()
CopyFirsRes()
CopyOtherRes()
print u"首包资源处理完毕-----------"
筛选配置config.json如下,会根据你的时间段把资源分成多个包,第一个时间段的资源就是小包的资源。
{
"platforms": ["android"],
"android": [5,7,11,20,27,46,56,66,76,86],
"ios": [5,7,11,20,27,46,56,66,76,86]
}
工具代码有兴趣的可以参考下,提供个思路