diff --git a/HotUpdateDemo/README.md b/HotUpdateDemo/README.md index 685f3d6..84d9db0 100644 --- a/HotUpdateDemo/README.md +++ b/HotUpdateDemo/README.md @@ -10,7 +10,7 @@ ~~~ npm install ~~~ -安装Node的库 +安装Node的库 fs-jetpack 用于对文件操作 * 构建Android 工程 选择link模式 ![link](https://upload-images.jianshu.io/upload_images/2315803-f28aec8ccdff1858.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) diff --git a/HotUpdateDemo/tools/BuildBeforeSetting.js b/HotUpdateDemo/tools/BuildBeforeSetting.js new file mode 100644 index 0000000..04463d8 --- /dev/null +++ b/HotUpdateDemo/tools/BuildBeforeSetting.js @@ -0,0 +1,21 @@ +const fileUtil = require('FileUtil'); +let version, androidVersionXml; + +function initParams(configPath) { + let data = JSON.parse(fileUtil.read(configPath)); + version = data.version; + androidVersionXml = data.root + 'frameworks/runtime-src/proj.android/AndroidManifest.xml'; +} + +function repreatAndroidVersion(path) { + let old = fileUtil.read(path); + old = old.replace(/android:versionName="([1-9]\d|[1-9])(\.([1-9]\d|\d)){2}"/, `android:versionName="${version}"`); + let result = Buffer.from(old); + return fileUtil.write(path, result) +} + +function main(params) { + initParams('./GameConfig.json'); + //修改android 版本号 + repreatAndroidVersion(androidVersionXml); +} \ No newline at end of file diff --git a/HotUpdateDemo/tools/CopyHotFiles.js b/HotUpdateDemo/tools/CopyHotFiles.js new file mode 100644 index 0000000..7087486 --- /dev/null +++ b/HotUpdateDemo/tools/CopyHotFiles.js @@ -0,0 +1,31 @@ +const fileUtil = require('FileUtil'); + +let hotUpdateRoot, hotUpdateDesRoot; +//要复制的目录 +const targetDir = ['res', 'src']; + +function initParams(configPath) { + let data = JSON.parse(fileUtil.read(configPath)); + hotUpdateRoot = data.src; + hotUpdateDesRoot = data.hotUpdateDirRoot; +} + +function forEachDir(list) { + for (let i = 0; i < list.length; i++) { + let item = list[i]; + let desPath = hotUpdateDesRoot + item; + let srcPath = hotUpdateRoot + item; + fileUtil.copyDir(srcPath, desPath); + } +} + +function main() { + initParams('./GameConfig.json'); + //消除目录文件 + fileUtil.checkDir(hotUpdateDesRoot); + forEachDir(targetDir); +} + + + + diff --git a/HotUpdateDemo/tools/ExportHotupdateDir.js b/HotUpdateDemo/tools/ExportHotupdateDir.js new file mode 100644 index 0000000..3f5588e --- /dev/null +++ b/HotUpdateDemo/tools/ExportHotupdateDir.js @@ -0,0 +1,47 @@ +const fileUtil = require('FileUtil'); + +let hotUpdateRoot, hotUpdateFile, exportHotUpdateDir, version, packageUrl; +const hotUpdateDir = 'hotUpdate', dirNameTemplate = 'hotUpdate-v', time = Date.parse(new Date()) / 1000; + +function initParams(configPath) { + let data = JSON.parse(fileUtil.read(configPath)); + hotUpdateRoot = data.src; + hotUpdateFile = hotUpdateRoot + data.hotUpdateDirName; + exportHotUpdateDir = hotUpdateRoot + data.hotupdateDirExport; + version = data.version; + packageUrl = packageUrl; +} + +function getVersion() { + return version.replace(/\./g, '-'); +} + +const versionNo = getVersion(); + +let dirName = `${dirNameTemplate}${versionNo}-${time}`; + +function reWritePackageUrl(dir, replaceName) { + let desPath = hotUpdateFile + '/' + dir; + let game = fileUtil.read(desPath); + let conf = JSON.parse(game); + conf.packageUrl = conf.packageUrl.replace(hotUpdateDir, replaceName); + fileUtil.write(desPath, JSON.stringify(conf)); + return conf.packageUrl; +} + +function main() { + initParams('./GameConfig.json'); + //消除目录文件 + fileUtil.checkDir(exportHotUpdateDir); + + const time = Date.parse(new Date()) / 1000; + let desPath = exportHotUpdateDir + '/' + dirName; + //复制到新目录 + fileUtil.copyDir(hotUpdateFile, desPath); + + reWritePackageUrl('project.manifest', dirName); +} + + + + diff --git a/HotUpdateDemo/tools/FileUtil.js b/HotUpdateDemo/tools/FileUtil.js new file mode 100644 index 0000000..1c3873d --- /dev/null +++ b/HotUpdateDemo/tools/FileUtil.js @@ -0,0 +1,147 @@ +const jetpack = require('fs-jetpack'); + +/** 以下所有方法均为同步 **/ +//参考 https://www.npmjs.com/package/fs-jetpack#removepath + +/** + * 读取文件 + * @param {*} path + * return string + */ +function read(path) { + const data = jetpack.read(path); + return data; +} + +function readDir(dir, fn) { + if (jetpack.exits() == 'dir') { + let list = jetpack.list(dir); + for (let i = 0; i < list.length; i++) { + let name = list[i]; + if (name[0] === '.') continue; + let subPath = jetpach.path(dir, name); + if (jetpack.exits(subPath) == 'dir') { + readDir(subPath, fn); + } else if (jetpack.exits(subPath) == 'file') { + fn && fn(subpath); + } + } + } +} + +/** + * + * @param {*} path + * @param {*} data String, Buffer, Object or Array + */ +function write(path, data) { + jetpack.write(path, data); +} + +/** + * 取得该路径 文件类型 + * @param {*} path + * return + * false if path doesn't exist. + * "dir" if path is a directory. + * "file" if path is a file. + * "other" if none of the above. + */ +function exits(path) { + const state = jetpack.exits(path); + return state; +} + +/** + * 取得该路径下的文件与目录 + * 返回一个数组 + * 等同于 fs.readdir + * @param {*} path + */ +function list(path) { + const list = jetpack.list(path); + return list || []; +} + +/** + * 新建目录 + * @param {*} path + */ +function dir(path) { + jetpack.dir(path); +} + +/** + * 复制 文件 or 目录 + * @param {*} src + * @param {*} des + */ +function copy(src, des) { + if (jetpack.exits(des)) { + jetpack.remove(des); + } + //过滤文件 jetpack.copy('foo', 'bar', { matching: ['*.md', '!top-secret.md'] }); + jetpack.copy(src, des, { + overwrite: true + }); +} + +/** + * 删除 文件 or 目录 + * @param {*} params + */ +function remove(path) { + jetpack.remove(path); +} + +/** + * 返回一个所在路径 + * @param {*} arg1 + * @param {*} arg2 + */ +function path(arg1, arg2) { + if (arguments.length == 0) {//当前路径 + const result = jetpack.path(); + return result; + } else if (arguments.length == 1) {//当前路径下的子目录 + const result = jetpack.path(arg1); + return result; + } else if (arguments.length = 2) { + const result = jetpack.path(arg1, arg2); + return result; + } +} + +function checkDir(dir) { + if (jetpack.exists(dir)) { + console.log('clear dir'); + jetpack.remove(dir); + } + jetpack.dir(dir); +} + +function copyDir(src, des) { + if (jetpack.exists(des)) { + jetpack.remove(des); + } + jetpack.copy(src, des); +} + +module.exports = { + read, + readDir, + write, + exits, + list, + dir, + copy, + remove, + path, + checkDir, + copyDir +} + + + + + diff --git a/HotUpdateDemo/tools/GameConfig.json b/HotUpdateDemo/tools/GameConfig.json new file mode 100644 index 0000000..c8f55fe --- /dev/null +++ b/HotUpdateDemo/tools/GameConfig.json @@ -0,0 +1,15 @@ +{ + "type": 1, + "version": "1.0.1", + "packageUrl": "http://localhost/hotUpdate/", + "root": "../build/jsb-link/", + "src": "../build/jsb-link/", + "hotUpdateDirName": "hotUpdateFiles", + "hotupdateDirExport": "exportHotUpdate", + "des": [ + "../build/jsb-link/" + ], + "zipDir": [ + "/res/import" + ] +} \ No newline at end of file diff --git a/HotUpdateDemo/tools/ModifyFileTime.py b/HotUpdateDemo/tools/ModifyFileTime.py new file mode 100644 index 0000000..665d556 --- /dev/null +++ b/HotUpdateDemo/tools/ModifyFileTime.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import sys +import os.path +import json + +relativePath = '' +zipDirList = [] +TIMESTAMP = 1330712292 + + +def modifyFileTime(filePath, time): + # 修改访问和修改时间 + os.utime(filePath, (time, time)) + + +def modify(rootdir): + for parent, dirnames, filenames in os.walk(rootdir): + for filename in filenames: + print(os.path.join(parent, filename)) + path1 = os.path.join(parent, filename) + modifyFileTime(path1, TIMESTAMP) + + for parent, dirnames, filenames in os.walk(rootdir): + for filename in dirnames: + print(os.path.join(parent, filename)) + path1 = os.path.join(parent, filename) + modifyFileTime(path1, TIMESTAMP) + + +def initParams(configPath): + data = open(configPath, 'r') + data = json.load(data) + relativePath = data.root + data.hotUpdateDirName + zipDirList = data.zipDir + + +def main(): + configPath = os.path.abspath(os.path.join( + os.getcwd(), "./GameConfig.json")) + initParams(configPath) + for dir in zipDirList: + abspath = os.path.abspath(os.path.join(os.getcwd(), relativePath+dir)) + print(abspath) + modify(abspath) + pass + + +main() diff --git a/HotUpdateDemo/tools/README.md b/HotUpdateDemo/tools/README.md new file mode 100644 index 0000000..2e43c95 --- /dev/null +++ b/HotUpdateDemo/tools/README.md @@ -0,0 +1,38 @@ +# 热更新工具说明 +* GameConfig.json 游戏版本、打包配置 +* BuildBeforeSetting.js 构建前准备工作 如:设置版本号,设置为正式服,等等~ +* FileUtil.js 文件操作工具类 +* CopyHotFiles.js 把需要热更文件复制到新文件夹 +* VersionGenerator.js 生成文件信息,作为版本资源对比。十分重要 +* ModifyFileTime.py 修改文件的生成时间 +* ZipFile.py 把文件压缩 + +### 准备 +* 操作系统:window +* 安装Node https://nodejs.org/zh-cn/ +* 安装Python 2.7 or 3.7 +* 以下操作都是基于android 打包 且模式为[link](https://docs.cocos.com/creator/manual/zh/publish/publish-native.html?h=link)。 +### 安装依赖 +* 打开命令行输入: +~~~ +npm install +~~~ +安装Node的库 fs-jetpack 用于对文件操作 + +### 操作 +首先 +* 运行 BuildBeforeSetting.js +对项目初始化 +然后进行构建工程, +* 构建Android 工程 选择link模式 +![link](https://upload-images.jianshu.io/upload_images/2315803-f28aec8ccdff1858.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +进入tools后 +* 运行CopyHotFiles.js 把热更新文件放到新文件夹 +* ModifyFileTime.js and ZipFile 对文件修改时间(用于生成zip后,进行MD5后,md5信息不变)、zip压缩(用于更新时可以减少下载请求,提高热更速度、与更新文件的稳定性) + +* 运行VersionGenerator.js 得到版本信息。 + +### 支持https +* 打开工程,修改libcocos2dx工程中Cocos2dxDownloader.java类。找到new AsyncHttpClient() 修改成new AsyncHttpClient(true, 80, 443) +* 参考文章:https://forum.cocos.com/t/https/52302 + diff --git a/HotUpdateDemo/tools/VersionGenerator.js b/HotUpdateDemo/tools/VersionGenerator.js new file mode 100644 index 0000000..9a8aa4a --- /dev/null +++ b/HotUpdateDemo/tools/VersionGenerator.js @@ -0,0 +1,84 @@ +const fileUtil = require('FileUtil'); +const crypto = require('crypto'); +const fs = require('fs'); +var path = require('path'); + +let manifest = { + packageUrl: 'http://localhost/tutorial-hot-update/remote-assets/', + remoteManifestUrl: 'http://localhost/tutorial-hot-update/remote-assets/project.manifest', + remoteVersionUrl: 'http://localhost/tutorial-hot-update/remote-assets/version.manifest', + version: '1.0.0', + assets: {}, + searchPaths: [] +}; + +let dest = './remote-assets/'; +let src = './jsb/'; + +function initManifest(configPath) { + let data = JSON.parse(fileUtil.read(configPath)); + //得到远程url + manifest.packageUrl = data.packageUrl; + manifest.remoteManifestUrl = data.packageUrl + 'project.manifest'; + manifest.remoteVersionUrl = data.packageUrl + 'version.manifest'; + //版本信息 + manifest.version = data.version; + //构建后的对应目录 + src = data.src; + + //目的目录 + desp = data.des; + + +} + +function md5InfoFromDir(dir, obj) { + function fn(subpath) { + let stat, size, md5, compressed, relative; + stat = fs.statSync(subpath); + size = stat['size']; + // md5 = crypto.createHash('md5').update(fs.readFileSync(subpath, 'binary')).digest('hex');//返回的并非二进制类型,而是String。这会导致非文本文件md5计算错误 + md5 = crypto.createHash('md5').update(fs.readFileSync(subPath)).digest('hex'); + compressed = path.extname(subpath).toLowerCase() === '.zip'; + relative = path.relative(src, subpath); + relative = relative.replace(/\\/g, '/'); + relative = encodeURI(relative); + obj[relative] = { + 'size': size, + 'md5': md5 + }; + if (compressed) { + obj[relative].compressed = true; + } + } + fileUtil.readDir(dir, fn); +} + +function main() { + initManifest('./GameConfig.json'); + + md5InfoFromDir(path.join(src, 'src'), manifest.assets); + md5InfoFromDir(path.join(src, 'res'), manifest.assets); + + let length = desp.length; + for (let i = 0; i < length; i++) { + let des = desp[i], destManifest; + destManifest = path.join(des, 'project.manifest'); + fileUtil.write(destManifest, JSON.stringify(manifest)); + } + + delete manifest.assets; + delete manifest.searchPaths; + + for (let i = 0; i < length; i++) { + let des = desp[i], destVersion; + destVersion = path.join(des, 'version.manifest'); + fileUtil.write(destVersion, JSON.stringify(manifest)); + } +} + + + + + + diff --git a/HotUpdateDemo/tools/ZipFile.py b/HotUpdateDemo/tools/ZipFile.py new file mode 100644 index 0000000..7cace73 --- /dev/null +++ b/HotUpdateDemo/tools/ZipFile.py @@ -0,0 +1,84 @@ +import os +import os.path +import zipfile +import shutil + +relativePath = '' +zipDirList = [] + + +def zip_dir(dirname, zipfilename): + filelist = [] + if os.path.isfile(dirname): + filelist.append(dirname) + else: + for root, dirs, files in os.walk(dirname): + for name in files: + filelist.append(os.path.join(root, name)) + + zf = zipfile.ZipFile(zipfilename, "w", zipfile.zlib.DEFLATED) + for tar in filelist: + arcname = tar[len(dirname):] + # print arcname + zf.write(tar, arcname) + zf.close() + + +def unzip_file(zipfilename, unziptodir): + if not os.path.exists(unziptodir): + os.mkdir(unziptodir, 777) + zfobj = zipfile.ZipFile(zipfilename) + for name in zfobj.namelist(): + name = name.replace('\\', '/') + + if name.endswith('/'): + os.mkdir(os.path.join(unziptodir, name)) + else: + ext_filename = os.path.join(unziptodir, name) + ext_dir = os.path.dirname(ext_filename) + if not os.path.exists(ext_dir): + os.mkdir(ext_dir, 777) + outfile = open(ext_filename, 'wb') + outfile.write(zfobj.read(name)) + outfile.close() + + +def encodeDir(rootdir): + for parent, dirnames, filenames in os.walk(rootdir): + print(parent, dirnames) + for dirname in dirnames: + print(parent) + print(dirname) + abspath = os.path.join(parent, dirname) + # finishPath=os.path.join() + zip_dir(abspath, abspath+'.zip') + deleteDir(abspath) + + +def deleteDir(dir): + if(os.path.exists(dir)): + shutil.rmtree(dir) + + +def checkDir(rootdir): + if os.path.exists(rootdir): + os.removedirs(rootdir) + os.makedirs(rootdir) + + +def initParams(configPath): + data = open(configPath, 'r') + data = json.load(data) + relativePath = data.root + data.hotUpdateDirName + zipDirList = data.zipDir + + +def main(): + configPath = os.path.abspath(os.path.join( + os.getcwd(), "./GameConfig.json")) + initParams(configPath) + for dir in zipDirList: + abspath = os.path.abspath(os.path.join(os.getcwd(), relativePath+dir)) + print(abspath) + encodeDir(abspath) + pass diff --git a/HotUpdateDemo/tools/version_generator.js b/HotUpdateDemo/tools/version_generator.js deleted file mode 100644 index 1a15683..0000000 --- a/HotUpdateDemo/tools/version_generator.js +++ /dev/null @@ -1,250 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var crypto = require('crypto'); -const jetpack = require('fs-jetpack'); - -var manifest = { - packageUrl: 'http://localhost/tutorial-hot-update/remote-assets/', - remoteManifestUrl: 'http://localhost/tutorial-hot-update/remote-assets/project.manifest', - remoteVersionUrl: 'http://localhost/tutorial-hot-update/remote-assets/version.manifest', - version: '1.0.0', - assets: {}, - searchPaths: [] -}; - -var dest = './remote-assets/'; -var src = './jsb/'; -var hotDir = null; -var packageRes = null; -// Parse arguments -var i = 2; -while (i < process.argv.length) { - var arg = process.argv[i]; - - switch (arg) { - case '--url': - case '-u': - var url = process.argv[i + 1]; - manifest.packageUrl = url; - manifest.remoteManifestUrl = url + 'project.manifest'; - manifest.remoteVersionUrl = url + 'version.manifest'; - i += 2; - break; - case '--version': - case '-v': - manifest.version = process.argv[i + 1]; - console.log('version=', manifest.version); - i += 2; - break; - case '--src': - case '-s': - src = process.argv[i + 1]; - hotDir = path.join(src, 'hotUpdate'); - console.log('hotDir=', hotDir); - packageRes = path.join(src, 'res'); - console.log('hpackageRes', packageRes); - i += 2; - break; - case '--dest': - case '-d': - dest = process.argv[i + 1]; - i += 2; - break; - default: - i += 2; - break; - } -} - -/** - * 读取文件到obj中 - * 过滤渠道json - * @param {*} dir - * @param {*} obj - */ -function readDir(dir, obj) { - console.log('readDir = ', dir); - var stat = fs.statSync(dir); - if (!stat.isDirectory()) { - return; - } - var subpaths = fs.readdirSync(dir), - subpath, size, md5, compressed, relative; - for (var i = 0; i < subpaths.length; ++i) { - if (subpaths[i][0] === '.') { - continue; - } - if (subpaths[i] == 'channel.json') { - console.log('---------------channel.json-------------------'); - continue; - } - subpath = path.join(dir, subpaths[i]); - stat = fs.statSync(subpath); - if (stat.isDirectory()) { - readDir(subpath, obj); - } else if (stat.isFile()) { - // Size in Bytes - size = stat['size']; - console.log('md5 ', subpath); - // md5 = crypto.createHash('md5').update(fs.readFileSync(subpath, 'binary')).digest('hex');//返回的并非二进制类型,而是String。这会导致非文本文件md5计算错误 - md5 = crypto.createHash('md5').update(fs.readFileSync(subpath)).digest('hex'); // - compressed = path.extname(subpath).toLowerCase() === '.zip'; - - relative = path.relative(src, subpath); - relative = relative.replace(/\\/g, '/'); - relative = encodeURI(relative); - obj[relative] = { - 'size': size, - 'md5': md5 - }; - if (compressed) { - obj[relative].compressed = true; - } - } - } -} - -//创建文件夹 -var mkdirSync = function (path) { - try { - fs.mkdirSync(path); - } catch (e) { - if (e.code != 'EEXIST') throw e; - } -}; - -// Iterate res and src folder -readDir(path.join(src, 'src'), manifest.assets); -readDir(path.join(src, 'res'), manifest.assets); - -var hotManifest = path.join(hotDir, 'project.manifest'); -var hotVersion = path.join(hotDir, 'version.manifest'); -var tmp = path.join(packageRes, 'raw-assets'); -var packageMenifest = path.join(tmp, 'project.manifest'); -console.log('packageMenifest', packageMenifest); -mkdirSync(dest); -//生成热更新目录 -mkdirSync(hotDir); - - -//生成文件manifest转成hotProject.js -let hotProjectPath = path.join(dest, 'hotManifest.js'); -console.log('create hotProject', hotProjectPath); -isfailed = fs.writeFileSync(hotProjectPath, 'module.exports = ' + JSON.stringify(manifest)); -if (!isfailed) { - console.log('hotManifest successfully generated'); -} - -//生成文件manifest到hotUpdate -isfailed = fs.writeFileSync(hotManifest, JSON.stringify(manifest)); -if (!isfailed) { - console.log('hotManifest successfully generated'); -} - -delete manifest.assets; -delete manifest.searchPaths; - -//生成版本manifest到hotUpdate -isfailed = fs.writeFileSync(hotVersion, JSON.stringify(manifest)); -if (isfailed) { - console.log('hotVersion successfully generated'); -} -/* - * 复制目录、子目录,及其中的文件 - * @param src {String} 要复制的目录 - * @param dist {String} 复制到目标目录 - */ -function copyDirSync(path, dest) { - let flist = jetpack.list(path) - for (let i = 0; i < flist.length; i++) { - let absolutePath = `${path}/${flist[i]}` - if (jetpack.exists(absolutePath) == "file") { // 是文件则复制 - jetpack.copy(absolutePath, `${dest}/${flist[i]}`, { - overwrite: true - }); - } - if (jetpack.exists(absolutePath) == "dir") { // 是目录则递归 - copyDirSync(absolutePath, `${dest}/${flist[i]}`) - } - } -} - -/** - * 删除目录 - */ -var rmdirSync = (function () { - /** - * 删除文件,如果是目录加入dirs 且进入 - * inner函数 - * @param {*} url - * @param {*} dirs - */ - function iterator(url, dirs) { - var stat = fs.statSync(url); - if (stat.isDirectory()) { - //收集目录 - dirs.unshift(url); - inner(url, dirs); - } else if (stat.isFile()) { - fs.unlinkSync(url); - } - } - /** - * - * @param {*} path - * @param {*} dirs - */ - function inner(path, dirs) { - var arr = fs.readdirSync(path); - for (var i = 0; i < arr.length; i++) { - iterator(path + '/' + arr[i], dirs); - } - } - return function (dir, cb) { - cb = cb || function () {}; - var dirs = []; - try { - //删除文件,得到目录 - iterator(dir, dirs); - //一次性删除所有收集到的目录 - for (var i = 0; i < dirs.length; i++) { - fs.rmdirSync(dirs[i]); - } - cb(); - } catch (error) { - //如果文件或目录本来就不存在,fs.statSync会报错,不过我们还是当成没有异常发生 - error.code === 'ENOENT' ? cb() : cb(error); - } - }; -})(); - -const srcPath = path.join(src, 'src'); -const resPath = path.join(src, 'res'); -const distSrcPath = path.join(hotDir, 'src'); -const distResPath = path.join(hotDir, 'res'); - -//复制src目录前,先删除原有目录 -rmdirSync(distSrcPath, (err) => { - err && console.log('err=', err); - console.log('delete success'); -}); - -//复制res目录前,先删除原有目录 -rmdirSync(distResPath, (err) => { - err && console.log('err=', err); - console.log('delete success'); -}); - -//复制src目录 -copyDirSync(srcPath, distSrcPath); -console.log('copy finish ', srcPath); - -//复制res目录 -copyDirSync(resPath, distResPath); -console.log('copy finish ', resPath); - -//把生成的maifest 同步到build\jsb-xxx\res\raw-assets 而不需要再次构建重新生成 -fs.writeFileSync(packageMenifest, fs.readFileSync(hotManifest)); -console.log(`copy hotupdate to ${packageMenifest} successful`); - -console.log('build hotupdte finish'); \ No newline at end of file