Unity热更新系列之四: 分包工具

为什么要做小包跟分包,因为玩家对包体的大小还是有敏感的,渠道商拿更小的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]
}

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值