大家在使用Cococ Creator提供的热更新 assetsManagers ,做md5校验的时候,一定会遇到卡顿的问题。

备注:文末有完整实现源码

原因是 Cococ Creator 官方提供的热更新校验回调是在ui线程进行,如下代码所示:

assetsManagers.setVerifyCallback(function (path, asset)) {let compressed = asset.compressed;let expectedMD5 = asset.md5;let relativePath = asset.path;let size = asset.size;if (compressed) {cc.log(`Verification passed : ${relativePath}`);.... return true;}else {cc.log(`Verification passed : ${relativePath} ( ${expectedMD5} )`);.... return true;}
});

为了解决这个问题,需要对引擎热更新部分稍加改造。

下面以android 环境为例进行详细说明。

第一步:增加 md5库, 放在 cocos2d-x/extensions/assets-manager文件夹下 。

第二步,改造AssetsManagerEx.h 和 AssetsManagerEx.cpp 增加默认校验方法

在AssetsManagerEx.h 增加函数声明:

private:virtual bool onVerifyDefault(const std::string storagePath, Manifest::Asset asset);

在AssetsManagerEx.cpp 实现函数:

bool AssetsManagerEx::onVerifyDefault(const std::string storagePath,Manifest::Asset asset)
{//cocos2d::log("onVerifyDefault 0");Data data = cocos2d::FileUtils::getInstance()->getDataFromFile(storagePath);if (data.isNull() ||(data.getSize()== 0 )){CCLOG("onVerifyDefault 1");return false;}std::string result = md5(data.getBytes(), data.getSize());//cocos2d::log("onVerifyDefault:%s - assetmd5:%s-resultmd5:%s",storagePath.c_str(),asset.md5.c_str(),result.c_str());if (memcmp(result.c_str(), asset.md5.c_str(), result.length()) == 0){CCLOG("onVerifyDefault 2");return  true;}// cocos2d::log("onVerifyDefault 3");return false;
}

在 AssetsManagerEx.cpp 的 onSuccess方法中,做md5比较部分改造。

当_verifyCallback 为nullptr 时,采用默认 onVerifyDefault 进行校验,如下:

void AssetsManagerEx::onSuccess(const std::string &/*srcUrl*/, const std::string &storagePath, const std::string &customId)
{if (customId == VERSION_ID){_updateState = State::VERSION_LOADED;parseVersion();}else if (customId == MANIFEST_ID){_updateState = State::MANIFEST_LOADED;parseManifest();}else{if (_downloadingTask.find(customId) != _downloadingTask.end()) {_downloadingTask.erase(customId);}bool ok = true;auto &assets = _remoteManifest->getAssets();auto assetIt = assets.find(customId);if (assetIt != assets.end()){Manifest::Asset asset = assetIt->second;if (_verifyCallback != nullptr){ok = _verifyCallback(storagePath, asset);} else{ok =onVerifyDefault( storagePath,asset);}}if (ok){bool compressed = assetIt != assets.end() ? assetIt->second.compressed : false;if (compressed){decompressDownloadedZip(customId, storagePath);}else{fileSuccess(customId, storagePath);}}else{fileError(customId, "Asset file verification failed after downloaded");}}
}

第三步,修改 Android.mk,增加MD5.cpp 文件的编译

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)LOCAL_MODULE    := cocos_extension_staticLOCAL_MODULE_FILENAME := libextensionifeq ($(USE_ARM_MODE),1)
LOCAL_ARM_MODE := arm
endifLOCAL_SRC_FILES := \
assets-manager/MD5.cpp \
assets-manager/Manifest.cpp \
assets-manager/AssetsManagerEx.cpp \
assets-manager/CCEventAssetsManagerEx.cpp \
assets-manager/CCAsyncTaskPool.cpp \LOCAL_CXXFLAGS += -fexceptionsLOCAL_C_INCLUDES := $(LOCAL_PATH)/. \$(LOCAL_PATH)/.. \$(LOCAL_PATH)/../cocos \$(LOCAL_PATH)/../cocos/platform \$(LOCAL_PATH)/../external/sourcesLOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/. \$(LOCAL_PATH)/..include $(BUILD_STATIC_LIBRARY)

第四步,删除上层校验回调实现,默认采用c++新增的校验方法。

即:js 或ts 代码中不再调用 assetsManagers.setVerifyCallback 方法。

以上就是 Cocos Creator 热更新资源 md5值比较的调整,此方案可有效解决渲染线程卡顿问题。

下面是完整源码

MD5.h源码

#ifndef _LUA_MD5_H__
#define _LUA_MD5_H__#include <stdio.h>
#include <string>
using namespace std;class MD5 {
public:typedef unsigned int size_type; // must be 32bitMD5();MD5(const std::string& text);void update(const unsigned char *buf, size_type length);void update(const char *buf, size_type length);MD5& finalize();std::string hexdigest(bool bUpper = false) const;friend std::ostream& operator<<(std::ostream&, MD5 md5);private:void init();typedef unsigned char uint1; //  8bittypedef unsigned int uint4;  // 32bitenum {blocksize = 64}; // VC6 won't eat a const static int herevoid transform(const uint1 block[blocksize]);static void decode(uint4 output[], const uint1 input[], size_type len);static void encode(uint1 output[], const uint4 input[], size_type len);bool finalized;uint1 buffer[blocksize];uint4 count[2];uint4 state[4];uint1 digest[16];static inline uint4 F(uint4 x, uint4 y, uint4 z);static inline uint4 G(uint4 x, uint4 y, uint4 z);static inline uint4 H(uint4 x, uint4 y, uint4 z);static inline uint4 I(uint4 x, uint4 y, uint4 z);static inline uint4 rotate_left(uint4 x, int n);static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,uint4 ac);static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,uint4 ac);static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,uint4 ac);static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s,uint4 ac);
};extern std::string md5(const std::string str);
extern std::string md5(const wchar_t* pwstr);
extern std::string md5(const unsigned char *buf, const unsigned int length);#endif

MD5.cpp文件源码

//MD5.cpp
/* MD5based on:md5.h and md5.c
reference implemantion of RFC 1321Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.These notices must be retained in any copies of any part of this
documentation and/or software.*/
/* interface header */
#include "MD5.h"
#include <stdlib.h>
/* system implementation headers */// Constants for MD5Transform routine.
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21///// F, G, H and I are basic MD5 functions.
inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) {return x&y | ~x&z;
}inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) {return x&z | y&~z;
}inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) {return x^y^z;
}inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) {return y ^ (x | ~z);
}// rotate_left rotates x left n bits.
inline MD5::uint4 MD5::rotate_left(uint4 x, int n) {return (x << n) | (x >> (32-n));
}// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
// Rotation is separate from addition to prevent recomputation.
inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {a = rotate_left(a+ F(b,c,d) + x + ac, s) + b;
}inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {a = rotate_left(a + G(b,c,d) + x + ac, s) + b;
}inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {a = rotate_left(a + H(b,c,d) + x + ac, s) + b;
}inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) {a = rotate_left(a + I(b,c,d) + x + ac, s) + b;
}//// default ctor, just initailize
MD5::MD5()
{init();
}//// nifty shortcut ctor, compute MD5 for string and finalize it right away
MD5::MD5(const std::string &text)
{init();update(text.c_str(), text.length());finalize();
}//void MD5::init()
{finalized=false;count[0] = 0;count[1] = 0;// load magic initialization constants.state[0] = 0x67452301;state[1] = 0xefcdab89;state[2] = 0x98badcfe;state[3] = 0x10325476;
}//// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4.
void MD5::decode(uint4 output[], const uint1 input[], size_type len)
{for (unsigned int i = 0, j = 0; j < len; i++, j += 4)output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) |(((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24);
}//// encodes input (uint4) into output (unsigned char). Assumes len is
// a multiple of 4.
void MD5::encode(uint1 output[], const uint4 input[], size_type len)
{for (size_type i = 0, j = 0; j < len; i++, j += 4) {output[j] = input[i] & 0xff;output[j+1] = (input[i] >> 8) & 0xff;output[j+2] = (input[i] >> 16) & 0xff;output[j+3] = (input[i] >> 24) & 0xff;}
}//// apply MD5 algo on a block
void MD5::transform(const uint1 block[blocksize])
{uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16];decode (x, block, blocksize);/* Round 1 */FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 *//* Round 2 */GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */GG (d, a, b, c, x[10], S22,  0x2441453); /* 22 */GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 *//* Round 3 */HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */HH (b, c, d, a, x[ 6], S34,  0x4881d05); /* 44 */HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 *//* Round 4 */II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */state[0] += a;state[1] += b;state[2] += c;state[3] += d;// Zeroize sensitive information.memset(x, 0, sizeof x);
}//// MD5 block update operation. Continues an MD5 message-digest
// operation, processing another message block
void MD5::update(const unsigned char input[], size_type length)
{// compute number of bytes mod 64size_type index = count[0] / 8 % blocksize;// Update number of bitsif ((count[0] += (length << 3)) < (length << 3))count[1]++;count[1] += (length >> 29);// number of bytes we need to fill in buffersize_type firstpart = 64 - index;size_type i;// transform as many times as possible.if (length >= firstpart){// fill buffer first, transformmemcpy(&buffer[index], input, firstpart);transform(buffer);// transform chunks of blocksize (64 bytes)for (i = firstpart; i + blocksize <= length; i += blocksize)transform(&input[i]);index = 0;}elsei = 0;// buffer remaining inputmemcpy(&buffer[index], &input[i], length-i);
}//// for convenience provide a verson with signed char
void MD5::update(const char input[], size_type length)
{update((const unsigned char*)input, length);
}//// MD5 finalization. Ends an MD5 message-digest operation, writing the
// the message digest and zeroizing the context.
MD5& MD5::finalize()
{static unsigned char padding[64] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};if (!finalized) {// Save number of bitsunsigned char bits[8];encode(bits, count, 8);// pad out to 56 mod 64.size_type index = count[0] / 8 % 64;size_type padLen = (index < 56) ? (56 - index) : (120 - index);update(padding, padLen);// Append length (before padding)update(bits, 8);// Store state in digestencode(digest, state, 16);// Zeroize sensitive information.memset(buffer, 0, sizeof buffer);memset(count, 0, sizeof count);finalized=true;}return *this;
}//// return hex representation of digest as string
std::string MD5::hexdigest(bool bUpper) const
{if (!finalized)return "";char buf[33];for (int i=0; i<16; i++){sprintf(buf+i*2, "%02x", digest[i]);}if (bUpper){for(int i = 0;i<33;i++){buf[i] = toupper((unsigned char)buf[i]);}}buf[32]=0;return std::string(buf);
}//std::ostream& operator<<(std::ostream& out, MD5 md5)
{return out << md5.hexdigest();
}//std::string md5(const std::string str)
{MD5 md5 = MD5(str);return md5.hexdigest();
}std::string md5(const wchar_t* pwstr)
{if(pwstr == NULL)return "";char *ptmp = (char *)new char[2*wcslen(pwstr)+1];memset(ptmp,0,2*wcslen(pwstr)+1);wcstombs(ptmp,pwstr,2*wcslen(pwstr)+1);MD5 md5 = MD5(ptmp);delete [] ptmp;return md5.hexdigest();
}std::string md5(const unsigned char *buf, const unsigned int length){MD5 md5 = MD5();md5.update(buf, length);md5.finalize();return md5.hexdigest();}

AssetsManagerEx.h 源码

/****************************************************************************Copyright (c) 2013 cocos2d-x.orgCopyright (c) 2014-2016 Chukong Technologies Inc.Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.http://www.cocos2d-x.orgPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included inall copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE.****************************************************************************/#ifndef __AssetsManagerEx__
#define __AssetsManagerEx__#include <string>
#include <unordered_map>
#include <vector>#include "platform/CCFileUtils.h"
#include "network/CCDownloader.h"#include "CCEventAssetsManagerEx.h"#include "Manifest.h"
#include "extensions/ExtensionMacros.h"
#include "extensions/ExtensionExport.h"
#include "json/document-wrapper.h"NS_CC_EXT_BEGIN/*** @brief   This class is used to auto update resources, such as pictures or scripts.*/
class CC_EX_DLL AssetsManagerEx : public Ref
{
public://! Update statesenum class State{UNINITED,UNCHECKED,PREDOWNLOAD_VERSION,DOWNLOADING_VERSION,VERSION_LOADED,PREDOWNLOAD_MANIFEST,DOWNLOADING_MANIFEST,MANIFEST_LOADED,NEED_UPDATE,READY_TO_UPDATE,UPDATING,UNZIPPING,UP_TO_DATE,FAIL_TO_UPDATE};const static std::string VERSION_ID;const static std::string MANIFEST_ID;typedef std::function<int(const std::string& versionA, const std::string& versionB)> VersionCompareHandle;typedef std::function<bool(const std::string& path, Manifest::Asset asset)> VerifyCallback;typedef std::function<void(EventAssetsManagerEx *event)> EventCallback;/** @brief Create function for creating a new AssetsManagerEx@param manifestUrl   The url for the local manifest file@param storagePath   The storage path for downloaded assets@warning   The cached manifest in your storage path have higher priority and will be searched first,only if it doesn't exist, AssetsManagerEx will use the given manifestUrl.*/static AssetsManagerEx* create(const std::string &manifestUrl, const std::string &storagePath);/** @brief  Check out if there is a new version of manifest.*          You may use this method before updating, then let user determine whether*          he wants to update resources.*/void checkUpdate();/** @brief Prepare the update process, this will cleanup download process flags, fill up download units with temporary manifest or remote manifest*/void prepareUpdate();/** @brief Update with the current local manifest.*/void update();/** @brief Reupdate all failed assets under the current AssetsManagerEx context*/void downloadFailedAssets();/** @brief Gets the current update state.*/State getState() const;/** @brief Gets storage path.*/const std::string& getStoragePath() const;/** @brief Function for retrieving the local manifest object*/const Manifest* getLocalManifest() const;/** @brief Load a custom local manifest object, the local manifest must be loaded already.* You can only manually load local manifest when the update state is UNCHECKED, it will fail once the update process is began.* This API will do the following things:* 1. Reset storage path* 2. Set local storage* 3. Search for cached manifest and compare with the local manifest* 4. Init temporary manifest and remote manifest* If successfully load the given local manifest and inited other manifests, it will return true, otherwise it will return false* @param localManifest    The local manifest object to be set* @param storagePath    The local storage path*/bool loadLocalManifest(Manifest* localManifest, const std::string& storagePath);/** @brief Load a local manifest from url.* You can only manually load local manifest when the update state is UNCHECKED, it will fail once the update process is began.* This API will do the following things:* 1. Reset storage path* 2. Set local storage* 3. Search for cached manifest and compare with the local manifest* 4. Init temporary manifest and remote manifest* If successfully load the given local manifest and inited other manifests, it will return true, otherwise it will return false* @param manifestUrl    The local manifest url*/bool loadLocalManifest(const std::string& manifestUrl);/** @brief Function for retrieving the remote manifest object*/const Manifest* getRemoteManifest() const;/** @brief Load a custom remote manifest object, the manifest must be loaded already.* You can only manually load remote manifest when the update state is UNCHECKED and local manifest is already inited, it will fail once the update process is began.* @param remoteManifest    The remote manifest object to be set*/bool loadRemoteManifest(Manifest* remoteManifest);/** @brief Gets whether the current download is resuming previous unfinished job, this will only be available after READY_TO_UPDATE state, under unknown states it will return false by default.*/bool isResuming() const {return _downloadResumed;};/** @brief Gets the total byte size to be downloaded of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.*/double getTotalBytes() const {return _totalSize;};/** @brief Gets the current downloaded byte size of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.*/double getDownloadedBytes() const {return _totalDownloaded;};/** @brief Gets the total files count to be downloaded of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.*/int getTotalFiles() const {return _totalToDownload;};/** @brief Gets the current downloaded files count of the update, this will only be available after READY_TO_UPDATE state, under unknown states it will return 0 by default.*/int getDownloadedFiles() const {return _totalToDownload - _totalWaitToDownload;};/** @brief Function for retrieving the max concurrent task count*/const int getMaxConcurrentTask() const {return _maxConcurrentTask;};/** @brief Function for setting the max concurrent task count*/void setMaxConcurrentTask(const int max) {_maxConcurrentTask = max;};/** @brief Set the handle function for comparing manifests versions* @param handle    The compare function*/void setVersionCompareHandle(const VersionCompareHandle& handle) {_versionCompareHandle = handle;};/** @brief Set the verification function for checking whether downloaded asset is correct, e.g. using md5 verification* @param callback  The verify callback function*/void setVerifyCallback(const VerifyCallback& callback) {_verifyCallback = callback;};/** @brief Set the event callback for receiving update process events* @param callback  The event callback function*/void setEventCallback(const EventCallback& callback) {_eventCallback = callback;};/** @brief Cancel update*/void cancelUpdate();/*** @brief 设置热更新地址,由于热更新地址会动态的发生变化,热更新的地址以下发的地址为准,设置热更新地址后,会自动的替换所有热更新的源地址*/void setHotUpdateUrl(const std::string& url) { _hotUpdateUrl = url; };CC_CONSTRUCTOR_ACCESS:AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath);AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath, const VersionCompareHandle& handle);virtual ~AssetsManagerEx();protected:void init(const std::string& manifestUrl, const std::string& storagePath);std::string basename(const std::string& path) const;std::string get(const std::string& key) const;void initManifests();void prepareLocalManifest();void setStoragePath(const std::string& storagePath);void adjustPath(std::string &path);void dispatchUpdateEvent(EventAssetsManagerEx::EventCode code, const std::string &message = "", const std::string &assetId = "", int curle_code = 0, int curlm_code = 0);void downloadVersion();void parseVersion();void downloadManifest();void parseManifest();void startUpdate();void updateSucceed();bool decompress(const std::string &filename);void decompressDownloadedZip(const std::string &customId, const std::string &storagePath);/** @brief Update a list of assets under the current AssetsManagerEx context*/void updateAssets(const DownloadUnits& assets);/** @brief Retrieve all failed assets during the last update*/const DownloadUnits& getFailedAssets() const;/** @brief Function for destroying the downloaded version file and manifest file*/void destroyDownloadedVersion();/** @brief Download items in queue with max concurrency setting*/void queueDowload();void fileError(const std::string& identifier, const std::string& errorStr, int errorCode = 0, int errorCodeInternal = 0);void fileSuccess(const std::string &customId, const std::string &storagePath);/** @brief  Call back function for error handling,the error will then be reported to user's listener registed in addUpdateEventListener@param error   The error object contains ErrorCode, message, asset url, asset key@warning AssetsManagerEx internal use only* @js NA* @lua NA*/virtual void onError(const network::DownloadTask& task,int errorCode,int errorCodeInternal,const std::string& errorStr);/** @brief  Call back function for recording downloading percent of the current asset,the progression will then be reported to user's listener registed in addUpdateProgressEventListener@param total       Total size to download for this asset@param downloaded  Total size already downloaded for this asset@param url         The url of this asset@param customId    The key of this asset@warning AssetsManagerEx internal use only* @js NA* @lua NA*/virtual void onProgress(double total, double downloaded, const std::string &url, const std::string &customId);/** @brief  Call back function for success of the current assetthe success event will then be send to user's listener registed in addUpdateEventListener@param srcUrl      The url of this asset@param customId    The key of this asset@warning AssetsManagerEx internal use only* @js NA* @lua NA*/virtual void onSuccess(const std::string &srcUrl, const std::string &storagePath, const std::string &customId);
private:virtual bool onVerifyDefault(const std::string storagePath, Manifest::Asset asset);private:void batchDownload();// Called when one DownloadUnits finishedvoid onDownloadUnitsFinished();//! The event of the current AssetsManagerEx in event dispatcherstd::string _eventName;//! Reference to the global event dispatcher
//    EventDispatcher *_eventDispatcher;//! Reference to the global file utilsFileUtils *_fileUtils;//! State of updateState _updateState;//! Downloaderstd::shared_ptr<network::Downloader> _downloader;//! The reference to the local assetsconst std::unordered_map<std::string, Manifest::Asset> *_assets;//! The path to store successfully downloaded version.std::string _storagePath;//! The path to store downloading version.std::string _tempStoragePath;//! The local path of cached temporary version filestd::string _tempVersionPath;//! The local path of cached manifest filestd::string _cacheManifestPath;//! The local path of cached temporary manifest filestd::string _tempManifestPath;//! The path of local manifest filestd::string _manifestUrl;//! Local manifestManifest *_localManifest;//! Local temporary manifest for download resumingManifest *_tempManifest;//! Remote manifestManifest *_remoteManifest;//! Whether user have requested to updateenum class UpdateEntry : char{NONE,CHECK_UPDATE,DO_UPDATE};UpdateEntry _updateEntry;//! All assets unit to downloadDownloadUnits _downloadUnits;//! All failed unitsDownloadUnits _failedUnits;//! Download queuestd::vector<std::string> _queue;bool _downloadResumed;//! Max concurrent task count for downloadingint _maxConcurrentTask;//! Current concurrent task countint _currConcurrentTask;//! Download percentfloat _percent;//! Download percent by filefloat _percentByFile;//! Indicate whether the total size should be enabledint _totalEnabled;//! Indicate the number of file whose total size have been collectedint _sizeCollected;//! Total file size need to be downloaded (sum of all files)double _totalSize;//! Total downloaded file size (sum of all downloaded files)double _totalDownloaded;//! Downloaded size for each filestd::unordered_map<std::string, double> _downloadedSize;//! Total number of assets to downloadint _totalToDownload;//! Total number of assets still waiting to be downloadedint _totalWaitToDownload;//! Next target percent for saving the manifest filefloat _nextSavePoint;//! Handle function to compare versions between different manifestsVersionCompareHandle _versionCompareHandle;//! Callback function to verify the downloaded assetsVerifyCallback _verifyCallback;//! Callback function to dispatch eventsEventCallback _eventCallback;//! Marker for whether the assets manager is initedbool _inited;//! Marker for whether the update is canceledbool _canceled;//! Downloading task containerstd::unordered_map<std::string, std::shared_ptr<const network::DownloadTask>> _downloadingTask;/*是否启用资源下载类型*/bool _isUsingAssetsType;/*资源类型 ""为大厅 其它为子游戏包名*/std::string _assetsType;/* 热更新地址*/std::string _hotUpdateUrl;
};NS_CC_EXT_END#endif /* defined(__AssetsManagerEx__) */

AssetsManagerEx.cpp 源码

/****************************************************************************Copyright (c) 2014 cocos2d-x.orgCopyright (c) 2015-2016 Chukong Technologies Inc.Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.http://www.cocos2d-x.orgPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included inall copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS INTHE SOFTWARE.****************************************************************************/
#include "AssetsManagerEx.h"
#include "base/ccUTF8.h"
#include "CCAsyncTaskPool.h"#include <stdio.h>
#include <errno.h>
#include "MD5.h"#ifdef MINIZIP_FROM_SYSTEM
#include <minizip/unzip.h>
#else // from our embedded sources
#include "unzip/unzip.h"
#endifNS_CC_EXT_BEGIN#define VERSION_FILENAME        "version.manifest"
#define TEMP_MANIFEST_FILENAME  "project.manifest.temp"
#define TEMP_PACKAGE_SUFFIX     "_temp"
#define MANIFEST_FILENAME       "project.manifest"#define BUFFER_SIZE    8192
#define MAX_FILENAME   512#define DEFAULT_CONNECTION_TIMEOUT 45#define SAVE_POINT_INTERVAL 0.1const std::string AssetsManagerEx::VERSION_ID = "@version";
const std::string AssetsManagerEx::MANIFEST_ID = "@manifest";// Implementation of AssetsManagerExAssetsManagerEx::AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath)
: _updateState(State::UNINITED)
, _assets(nullptr)
, _storagePath("")
, _tempVersionPath("")
, _cacheManifestPath("")
, _tempManifestPath("")
, _localManifest(nullptr)
, _tempManifest(nullptr)
, _remoteManifest(nullptr)
, _updateEntry(UpdateEntry::NONE)
, _percent(0)
, _percentByFile(0)
, _totalSize(0)
, _sizeCollected(0)
, _totalDownloaded(0)
, _totalToDownload(0)
, _totalWaitToDownload(0)
, _nextSavePoint(0.0)
, _downloadResumed(false)
, _maxConcurrentTask(32)
, _currConcurrentTask(0)
, _verifyCallback(nullptr)
, _inited(false)
, _canceled(false)
{init(manifestUrl, storagePath);
}AssetsManagerEx::AssetsManagerEx(const std::string& manifestUrl, const std::string& storagePath, const VersionCompareHandle& handle)
: _updateState(State::UNINITED)
, _assets(nullptr)
, _storagePath("")
, _tempVersionPath("")
, _cacheManifestPath("")
, _tempManifestPath("")
, _localManifest(nullptr)
, _tempManifest(nullptr)
, _remoteManifest(nullptr)
, _updateEntry(UpdateEntry::NONE)
, _percent(0)
, _percentByFile(0)
, _totalSize(0)
, _sizeCollected(0)
, _totalDownloaded(0)
, _totalToDownload(0)
, _totalWaitToDownload(0)
, _nextSavePoint(0.0)
, _downloadResumed(false)
, _maxConcurrentTask(32)
, _currConcurrentTask(0)
, _versionCompareHandle(handle)
, _verifyCallback(nullptr)
, _eventCallback(nullptr)
, _inited(false)
, _isUsingAssetsType(false)
, _assetsType("")
, _hotUpdateUrl("")
{init(manifestUrl, storagePath);
}void AssetsManagerEx::init(const std::string& manifestUrl, const std::string& storagePath)
{// Init variablesstd::string pointer = StringUtils::format("%p", this);_eventName = "__cc_assets_manager_" + pointer;_fileUtils = FileUtils::getInstance();network::DownloaderHints hints ={static_cast<uint32_t>(_maxConcurrentTask),DEFAULT_CONNECTION_TIMEOUT,".tmp"};_downloader = std::shared_ptr<network::Downloader>(new network::Downloader(hints));_downloader->onTaskError = std::bind(&AssetsManagerEx::onError, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);_downloader->onTaskProgress = [this](const network::DownloadTask& task,int64_t /*bytesReceived*/,int64_t totalBytesReceived,int64_t totalBytesExpected){this->onProgress(totalBytesExpected, totalBytesReceived, task.requestURL, task.identifier);};_downloader->onFileTaskSuccess = [this](const network::DownloadTask& task){this->onSuccess(task.requestURL, task.storagePath, task.identifier);};setStoragePath(storagePath);//这里做一下处理,当传入的是一个特定的格式时,修改当前3个缓存的路径std::string typeString = "type.";auto pos = manifestUrl.find(typeString);if ( pos != std::string::npos) {auto amType = manifestUrl.substr(typeString.size());this->_isUsingAssetsType = true;if (amType == "hall") {//大厅this->_assetsType = "";_tempVersionPath = _tempStoragePath + VERSION_FILENAME;_cacheManifestPath = _storagePath + MANIFEST_FILENAME;_tempManifestPath = _tempStoragePath + TEMP_MANIFEST_FILENAME;}else {this->_assetsType = amType;_tempVersionPath = _tempStoragePath + amType + VERSION_FILENAME;_cacheManifestPath = _storagePath + amType + MANIFEST_FILENAME;_tempManifestPath = _tempStoragePath + amType + TEMP_MANIFEST_FILENAME;}}else {this->_isUsingAssetsType = false;this->_assetsType = "";_tempVersionPath = _tempStoragePath + VERSION_FILENAME;_cacheManifestPath = _storagePath + MANIFEST_FILENAME;_tempManifestPath = _tempStoragePath + TEMP_MANIFEST_FILENAME;if (manifestUrl.size() > 0){loadLocalManifest(manifestUrl);}}}AssetsManagerEx::~AssetsManagerEx()
{_downloader->onTaskError = (nullptr);_downloader->onFileTaskSuccess = (nullptr);_downloader->onTaskProgress = (nullptr);CC_SAFE_RELEASE(_localManifest);// _tempManifest could share a ptr with _remoteManifest or _localManifestif (_tempManifest != _localManifest && _tempManifest != _remoteManifest)CC_SAFE_RELEASE(_tempManifest);CC_SAFE_RELEASE(_remoteManifest);
}AssetsManagerEx* AssetsManagerEx::create(const std::string& manifestUrl, const std::string& storagePath)
{AssetsManagerEx* ret = new (std::nothrow) AssetsManagerEx(manifestUrl, storagePath);if (ret){ret->autorelease();}else{CC_SAFE_DELETE(ret);}return ret;
}void AssetsManagerEx::initManifests()
{_inited = true;_canceled = false;// Init and load temporary manifest_tempManifest = new (std::nothrow) Manifest();if (_tempManifest){_tempManifest->setHotUpdateUrl(_hotUpdateUrl);_tempManifest->parseFile(_tempManifestPath);// Previous update is interruptedif (_fileUtils->isFileExist(_tempManifestPath)){// Manifest parse failed, remove all temp filesif (!_tempManifest->isLoaded()){_fileUtils->removeDirectory(_tempStoragePath);CC_SAFE_RELEASE(_tempManifest);_tempManifest = nullptr;}}}else{_inited = false;}// Init remote manifest for future usage_remoteManifest = new (std::nothrow) Manifest();if (!_remoteManifest){_inited = false;}if (!_inited){CC_SAFE_RELEASE(_localManifest);CC_SAFE_RELEASE(_tempManifest);CC_SAFE_RELEASE(_remoteManifest);_localManifest = nullptr;_tempManifest = nullptr;_remoteManifest = nullptr;}
}void AssetsManagerEx::prepareLocalManifest()
{// An alias to assets_assets = &(_localManifest->getAssets());// Add search paths_localManifest->prependSearchPaths();
}bool AssetsManagerEx::loadLocalManifest(Manifest* localManifest, const std::string& storagePath)
{if (_updateState > State::UNINITED){return false;}if (!localManifest || !localManifest->isLoaded()){return false;}_inited = true;_canceled = false;// Reset storage pathif (storagePath.size() > 0){setStoragePath(storagePath);_tempVersionPath = _tempStoragePath + VERSION_FILENAME;_cacheManifestPath = _storagePath + MANIFEST_FILENAME;_tempManifestPath = _tempStoragePath + TEMP_MANIFEST_FILENAME;}// Release existing local manifestif (_localManifest){CC_SAFE_RELEASE(_localManifest);}_localManifest = localManifest;_localManifest->retain();// Find the cached manifest fileManifest *cachedManifest = nullptr;if (_fileUtils->isFileExist(_cacheManifestPath)){cachedManifest = new (std::nothrow) Manifest();if (cachedManifest){cachedManifest->setHotUpdateUrl(_hotUpdateUrl);cachedManifest->parseFile(_cacheManifestPath);if (!cachedManifest->isLoaded()){_fileUtils->removeFile(_cacheManifestPath);CC_SAFE_RELEASE(cachedManifest);cachedManifest = nullptr;}}}// Compare with cached manifest to determine which one to useif (cachedManifest){bool localNewer = _localManifest->versionGreater(cachedManifest, _versionCompareHandle);if (localNewer){// Recreate storage, to empty the content_fileUtils->removeDirectory(_storagePath);_fileUtils->createDirectory(_storagePath);CC_SAFE_RELEASE(cachedManifest);}else{CC_SAFE_RELEASE(_localManifest);_localManifest = cachedManifest;}}prepareLocalManifest();// Init temp manifest and remote manifestinitManifests();if (!_inited){return false;}else{_updateState = State::UNCHECKED;return true;}
}bool AssetsManagerEx::loadLocalManifest(const std::string& manifestUrl)
{if (manifestUrl.size() == 0){return false;}if (_updateState > State::UNINITED){return false;}_manifestUrl = manifestUrl;// Init and load local manifest_localManifest = new (std::nothrow) Manifest();if (!_localManifest){return false;}Manifest *cachedManifest = nullptr;// Find the cached manifest fileif (_fileUtils->isFileExist(_cacheManifestPath)){cachedManifest = new (std::nothrow) Manifest();if (cachedManifest){cachedManifest->setHotUpdateUrl(_hotUpdateUrl);cachedManifest->parseFile(_cacheManifestPath);if (!cachedManifest->isLoaded()){_fileUtils->removeFile(_cacheManifestPath);CC_SAFE_RELEASE(cachedManifest);cachedManifest = nullptr;}}}// Ensure no search path of cached manifest is used to load this manifeststd::vector<std::string> searchPaths = _fileUtils->getSearchPaths();if (cachedManifest){std::vector<std::string> cacheSearchPaths = cachedManifest->getSearchPaths();std::vector<std::string> trimmedPaths = searchPaths;for (auto path : cacheSearchPaths){const auto pos = std::find(trimmedPaths.begin(), trimmedPaths.end(), path);if (pos != trimmedPaths.end()){trimmedPaths.erase(pos);}}_fileUtils->setSearchPaths(trimmedPaths);}_localManifest->setHotUpdateUrl(_hotUpdateUrl);// Load local manifest in app package_localManifest->parseFile(_manifestUrl);if (cachedManifest){// Restore search paths_fileUtils->setSearchPaths(searchPaths);}if (_localManifest->isLoaded()){// Compare with cached manifest to determine which one to useif (cachedManifest){bool localNewer = _localManifest->versionGreater(cachedManifest, _versionCompareHandle);if (localNewer){// Recreate storage, to empty the content_fileUtils->removeDirectory(_storagePath);_fileUtils->createDirectory(_storagePath);CC_SAFE_RELEASE(cachedManifest);}else{CC_SAFE_RELEASE(_localManifest);_localManifest = cachedManifest;}}prepareLocalManifest();}// Fail to load local manifestif (!_localManifest->isLoaded()){CCLOG("AssetsManagerEx : No local manifest file found error.\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);return false;}initManifests();_updateState = State::UNCHECKED;return true;
}bool AssetsManagerEx::loadRemoteManifest(Manifest* remoteManifest)
{if (!_inited || _updateState > State::UNCHECKED){return false;}if (!remoteManifest || !remoteManifest->isLoaded()){return false;}// Release existing remote manifestif (_remoteManifest){CC_SAFE_RELEASE(_remoteManifest);}_remoteManifest = remoteManifest;_remoteManifest->retain();// Compare manifest version and set stateif (_localManifest->versionGreaterOrEquals(_remoteManifest, _versionCompareHandle)){_updateState = State::UP_TO_DATE;_fileUtils->removeDirectory(_tempStoragePath);dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);}else{_updateState = State::NEED_UPDATE;dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);}return true;
}std::string AssetsManagerEx::basename(const std::string& path) const
{size_t found = path.find_last_of("/\\");if (std::string::npos != found){return path.substr(0, found);}else{return path;}
}std::string AssetsManagerEx::get(const std::string& key) const
{auto it = _assets->find(key);if (it != _assets->cend()) {return _storagePath + it->second.path;}else return "";
}const Manifest* AssetsManagerEx::getLocalManifest() const
{return _localManifest;
}const Manifest* AssetsManagerEx::getRemoteManifest() const
{return _remoteManifest;
}const std::string& AssetsManagerEx::getStoragePath() const
{return _storagePath;
}void AssetsManagerEx::setStoragePath(const std::string& storagePath)
{_storagePath = storagePath;adjustPath(_storagePath);_fileUtils->createDirectory(_storagePath);_tempStoragePath = _storagePath;_tempStoragePath.insert(_storagePath.size() - 1, TEMP_PACKAGE_SUFFIX);_fileUtils->createDirectory(_tempStoragePath);
}void AssetsManagerEx::adjustPath(std::string &path)
{if (path.size() > 0 && path[path.size() - 1] != '/'){path.append("/");}
}bool AssetsManagerEx::decompress(const std::string &zip)
{// Find root path for zip filesize_t pos = zip.find_last_of("/\\");if (pos == std::string::npos){CCLOG("AssetsManagerEx : no root path specified for zip file %s\n", zip.c_str());return false;}const std::string rootPath = zip.substr(0, pos+1);// Open the zip fileunzFile zipfile = unzOpen(FileUtils::getInstance()->getSuitableFOpen(zip).c_str());if (! zipfile){CCLOG("AssetsManagerEx : can not open downloaded zip file %s\n", zip.c_str());return false;}// Get info about the zip fileunz_global_info global_info;if (unzGetGlobalInfo(zipfile, &global_info) != UNZ_OK){CCLOG("AssetsManagerEx : can not read file global info of %s\n", zip.c_str());unzClose(zipfile);return false;}// Buffer to hold data read from the zip filechar readBuffer[BUFFER_SIZE];// Loop to extract all files.uLong i;for (i = 0; i < global_info.number_entry; ++i){// Get info about current file.unz_file_info fileInfo;char fileName[MAX_FILENAME];if (unzGetCurrentFileInfo(zipfile,&fileInfo,fileName,MAX_FILENAME,NULL,0,NULL,0) != UNZ_OK){CCLOG("AssetsManagerEx : can not read compressed file info\n");unzClose(zipfile);return false;}const std::string fullPath = rootPath + fileName;// Check if this entry is a directory or a file.const size_t filenameLength = strlen(fileName);if (fileName[filenameLength-1] == '/'){//There are not directory entry in some case.//So we need to create directory when decompressing file entryif ( !_fileUtils->createDirectory(basename(fullPath)) ){// Failed to create directoryCCLOG("AssetsManagerEx : can not create directory %s\n", fullPath.c_str());unzClose(zipfile);return false;}}else{// Create all directories in advance to avoid issuestd::string dir = basename(fullPath);if (!_fileUtils->isDirectoryExist(dir)) {if (!_fileUtils->createDirectory(dir)) {// Failed to create directoryCCLOG("AssetsManagerEx : can not create directory %s\n", fullPath.c_str());unzClose(zipfile);return false;}}// Entry is a file, so extract it.// Open current file.if (unzOpenCurrentFile(zipfile) != UNZ_OK){CCLOG("AssetsManagerEx : can not extract file %s\n", fileName);unzClose(zipfile);return false;}// Create a file to store current file.FILE *out = fopen(FileUtils::getInstance()->getSuitableFOpen(fullPath).c_str(), "wb");if (!out){CCLOG("AssetsManagerEx : can not create decompress destination file %s (errno: %d)\n", fullPath.c_str(), errno);unzCloseCurrentFile(zipfile);unzClose(zipfile);return false;}// Write current file content to destinate file.int error = UNZ_OK;do{error = unzReadCurrentFile(zipfile, readBuffer, BUFFER_SIZE);if (error < 0){CCLOG("AssetsManagerEx : can not read zip file %s, error code is %d\n", fileName, error);fclose(out);unzCloseCurrentFile(zipfile);unzClose(zipfile);return false;}if (error > 0){fwrite(readBuffer, error, 1, out);}} while(error > 0);fclose(out);}unzCloseCurrentFile(zipfile);// Goto next entry listed in the zip file.if ((i+1) < global_info.number_entry){if (unzGoToNextFile(zipfile) != UNZ_OK){CCLOG("AssetsManagerEx : can not read next file for decompressing\n");unzClose(zipfile);return false;}}}unzClose(zipfile);return true;
}void AssetsManagerEx::decompressDownloadedZip(const std::string &customId, const std::string &storagePath)
{struct AsyncData{std::string customId;std::string zipFile;bool succeed;};AsyncData* asyncData = new AsyncData;asyncData->customId = customId;asyncData->zipFile = storagePath;asyncData->succeed = false;std::function<void(void*)> decompressFinished = [this](void* param) {auto dataInner = reinterpret_cast<AsyncData*>(param);if (dataInner->succeed){fileSuccess(dataInner->customId, dataInner->zipFile);}else{std::string errorMsg = "Unable to decompress file " + dataInner->zipFile;// Ensure zip file deletion (if decompress failure cause task thread exit anormally)_fileUtils->removeFile(dataInner->zipFile);dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DECOMPRESS, "", errorMsg);fileError(dataInner->customId, errorMsg);}delete dataInner;};AsyncTaskPool::getInstance()->enqueue(AsyncTaskPool::TaskType::TASK_OTHER, decompressFinished, (void*)asyncData, [this, asyncData]() {// Decompress all compressed filesif (decompress(asyncData->zipFile)){asyncData->succeed = true;}_fileUtils->removeFile(asyncData->zipFile);});
}void AssetsManagerEx::dispatchUpdateEvent(EventAssetsManagerEx::EventCode code, const std::string &assetId/* = ""*/, const std::string &message/* = ""*/, int curle_code/* = CURLE_OK*/, int curlm_code/* = CURLM_OK*/)
{switch (code){case EventAssetsManagerEx::EventCode::ERROR_UPDATING:case EventAssetsManagerEx::EventCode::ERROR_PARSE_MANIFEST:case EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST:case EventAssetsManagerEx::EventCode::ERROR_DECOMPRESS:case EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST:case EventAssetsManagerEx::EventCode::UPDATE_FAILED:case EventAssetsManagerEx::EventCode::UPDATE_FINISHED:case EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE:_updateEntry = UpdateEntry::NONE;break;case EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION:break;case EventAssetsManagerEx::EventCode::ASSET_UPDATED:break;case EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND:if (_updateEntry == UpdateEntry::CHECK_UPDATE){_updateEntry = UpdateEntry::NONE;}break;default:break;}if (_eventCallback != nullptr) {EventAssetsManagerEx* event = new (std::nothrow) EventAssetsManagerEx(_eventName, this, code, assetId, message, curle_code, curlm_code);_eventCallback(event);event->release();}
}AssetsManagerEx::State AssetsManagerEx::getState() const
{return _updateState;
}void AssetsManagerEx::downloadVersion()
{if (_updateState > State::PREDOWNLOAD_VERSION)return;std::string versionUrl = _localManifest->getVersionFileUrl();if (versionUrl.size() > 0){_updateState = State::DOWNLOADING_VERSION;// Download version file asynchronously_downloader->createDownloadFileTask(versionUrl, _tempVersionPath, VERSION_ID);}// No version file foundelse{CCLOG("AssetsManagerEx : No version file found, step skipped\n");_updateState = State::PREDOWNLOAD_MANIFEST;downloadManifest();}
}void AssetsManagerEx::parseVersion()
{if (_updateState != State::VERSION_LOADED)return;_remoteManifest->setHotUpdateUrl(_hotUpdateUrl);_remoteManifest->parseVersion(_tempVersionPath);if (!_remoteManifest->isVersionLoaded()){CCLOG("AssetsManagerEx : Fail to parse version file, step skipped\n");_updateState = State::PREDOWNLOAD_MANIFEST;downloadManifest();}else{if (_localManifest->versionGreaterOrEquals(_remoteManifest, _versionCompareHandle)){_updateState = State::UP_TO_DATE;_fileUtils->removeDirectory(_tempStoragePath);dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);}else{_updateState = State::PREDOWNLOAD_MANIFEST;downloadManifest();}}
}void AssetsManagerEx::downloadManifest()
{if (_updateState != State::PREDOWNLOAD_MANIFEST)return;std::string manifestUrl = _localManifest->getManifestFileUrl();if (manifestUrl.size() > 0){_updateState = State::DOWNLOADING_MANIFEST;// Download version file asynchronously_downloader->createDownloadFileTask(manifestUrl, _tempManifestPath, MANIFEST_ID);}// No manifest file foundelse{CCLOG("AssetsManagerEx : No manifest file found, check update failed\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST);_updateState = State::UNCHECKED;}
}void AssetsManagerEx::parseManifest()
{if (_updateState != State::MANIFEST_LOADED)return;_remoteManifest->setHotUpdateUrl(_hotUpdateUrl);_remoteManifest->parseFile(_tempManifestPath);if (!_remoteManifest->isLoaded()){CCLOG("AssetsManagerEx : Error parsing manifest file, %s", _tempManifestPath.c_str());dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_PARSE_MANIFEST);_updateState = State::UNCHECKED;}else{if (_localManifest->versionGreaterOrEquals(_remoteManifest, _versionCompareHandle)){_updateState = State::UP_TO_DATE;_fileUtils->removeDirectory(_tempStoragePath);dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);}else{_updateState = State::NEED_UPDATE;if (_updateEntry == UpdateEntry::DO_UPDATE){startUpdate();}else if (_updateEntry == UpdateEntry::CHECK_UPDATE){prepareUpdate();}dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);}}
}void AssetsManagerEx::prepareUpdate()
{if (_updateState != State::NEED_UPDATE)return;// Clean up before update_failedUnits.clear();_downloadUnits.clear();_totalWaitToDownload = _totalToDownload = 0;_nextSavePoint = 0;_percent = _percentByFile = _sizeCollected = _totalDownloaded = _totalSize = 0;_downloadResumed = false;_downloadedSize.clear();_totalEnabled = false;// Temporary manifest exists, previously updating and equals to the remote version, resuming previous downloadif (_tempManifest && _tempManifest->isLoaded() && _tempManifest->isUpdating() && _tempManifest->versionEquals(_remoteManifest)){_tempManifest->saveToFile(_tempManifestPath);_tempManifest->genResumeAssetsList(&_downloadUnits);_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();_downloadResumed = true;// Collect total sizefor(auto iter : _downloadUnits){const DownloadUnit& unit = iter.second;if (unit.size > 0){_totalSize += unit.size;}}}else{// Temporary manifest exists, but can't be parsed or version doesn't equals remote manifest (out of date)if (_tempManifest){// Remove all temp files_fileUtils->removeDirectory(_tempStoragePath);CC_SAFE_RELEASE(_tempManifest);// Recreate temp storage path and save remote manifest_fileUtils->createDirectory(_tempStoragePath);_remoteManifest->saveToFile(_tempManifestPath);}// Temporary manifest will be used to register the download states of each asset,// in this case, it equals remote manifest._tempManifest = _remoteManifest;// Check difference between local manifest and remote manifeststd::unordered_map<std::string, Manifest::AssetDiff> diff_map = _localManifest->genDiff(_remoteManifest);if (diff_map.size() == 0){updateSucceed();return;}else{// Generate download units for all assets that need to be updated or addedstd::string packageUrl = _remoteManifest->getPackageUrl();// Preprocessing local files in previous version and creating download foldersfor (auto it = diff_map.begin(); it != diff_map.end(); ++it){Manifest::AssetDiff diff = it->second;if (diff.type != Manifest::DiffType::DELETED){std::string path = diff.asset.path;DownloadUnit unit;unit.customId = it->first;unit.srcUrl = packageUrl + path + "?md5=" + diff.asset.md5;unit.storagePath = _tempStoragePath + path;unit.size = diff.asset.size;_downloadUnits.emplace(unit.customId, unit);_tempManifest->setAssetDownloadState(it->first, Manifest::DownloadState::UNSTARTED);_totalSize += unit.size;}}// Start updating the temp manifest_tempManifest->setUpdating(true);// Save current download manifest information for resuming_tempManifest->saveToFile(_tempManifestPath);_totalWaitToDownload = _totalToDownload = (int)_downloadUnits.size();}}_updateState = State::READY_TO_UPDATE;
}void AssetsManagerEx::startUpdate()
{if (_updateState == State::NEED_UPDATE){prepareUpdate();}if (_updateState == State::READY_TO_UPDATE){_totalSize = 0;_updateState = State::UPDATING;std::string msg;if (_downloadResumed){msg = StringUtils::format("Resuming from previous unfinished update, %d files remains to be finished.", _totalToDownload);}else{msg = StringUtils::format("Start to update %d files from remote package.", _totalToDownload);}dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, "", msg);batchDownload();}
}void AssetsManagerEx::updateSucceed()
{// Set temp manifest's updatingif (_tempManifest != nullptr) {_tempManifest->setUpdating(false);}// Every thing is correctly downloaded, do the following// 1. rename temporary manifest to valid manifestif (this->_isUsingAssetsType) {if (_fileUtils->isFileExist(_tempManifestPath)) {_fileUtils->renameFile(_tempStoragePath, this->_assetsType + TEMP_MANIFEST_FILENAME, this->_assetsType + MANIFEST_FILENAME);}}else {if (_fileUtils->isFileExist(_tempManifestPath)) {_fileUtils->renameFile(_tempStoragePath, TEMP_MANIFEST_FILENAME, MANIFEST_FILENAME);}}// 2. Get the delete filesstd::unordered_map<std::string, Manifest::AssetDiff> diff_map = _localManifest->genDiff(_remoteManifest);// 3. merge temporary storage path to storage path so that temporary version turns to cached versionif (_fileUtils->isDirectoryExist(_tempStoragePath)){// Merging all files in temp storage path to storage pathstd::vector<std::string> files;_fileUtils->listFilesRecursively(_tempStoragePath, &files);int baseOffset = (int)_tempStoragePath.length();std::string relativePath, dstPath;for (std::vector<std::string>::iterator it = files.begin(); it != files.end(); ++it){relativePath.assign((*it).substr(baseOffset));dstPath.assign(_storagePath + relativePath);// Create directoryif (relativePath.back() == '/'){_fileUtils->createDirectory(dstPath);}// Copy fileelse{if (_fileUtils->isFileExist(dstPath)){_fileUtils->removeFile(dstPath);}_fileUtils->renameFile(*it, dstPath);}// Remove from delete list for safe, although this is not the case in general.auto diff_itr = diff_map.find(relativePath);if (diff_itr != diff_map.end()) {diff_map.erase(diff_itr);}}// Preprocessing local files in previous version and creating download foldersfor (auto it = diff_map.begin(); it != diff_map.end(); ++it){Manifest::AssetDiff diff = it->second;if (diff.type == Manifest::DiffType::DELETED){// TODO: Do this when download finish, it don’t matter delete or not.std::string exsitedPath = _storagePath + diff.asset.path;_fileUtils->removeFile(exsitedPath);}}}// 4. swap the localManifestCC_SAFE_RELEASE(_localManifest);_localManifest = _remoteManifest;_localManifest->setManifestRoot(_storagePath);_remoteManifest = nullptr;// 5. make local manifest take effectprepareLocalManifest();// 6. Set update state_updateState = State::UP_TO_DATE;// 7. Notify finished eventdispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_FINISHED);// 8. Remove temp storage path_fileUtils->removeDirectory(_tempStoragePath);
}void AssetsManagerEx::checkUpdate()
{if (_updateEntry != UpdateEntry::NONE){CCLOGERROR("AssetsManagerEx::checkUpdate, updateEntry isn't NONE");return;}if (!_inited){CCLOG("AssetsManagerEx : Manifests uninited.\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);return;}if (!_localManifest->isLoaded()){CCLOG("AssetsManagerEx : No local manifest file found error.\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);return;}_updateEntry = UpdateEntry::CHECK_UPDATE;switch (_updateState) {case State::FAIL_TO_UPDATE:_updateState = State::UNCHECKED;case State::UNCHECKED:case State::PREDOWNLOAD_VERSION:{downloadVersion();}break;case State::UP_TO_DATE:{dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ALREADY_UP_TO_DATE);}break;case State::NEED_UPDATE:{dispatchUpdateEvent(EventAssetsManagerEx::EventCode::NEW_VERSION_FOUND);}break;default:break;}
}void AssetsManagerEx::update()
{if (_updateEntry != UpdateEntry::NONE){CCLOGERROR("AssetsManagerEx::update, updateEntry isn't NONE");return;}if (!_inited){CCLOG("AssetsManagerEx : Manifests uninited.\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);return;}if (!_localManifest->isLoaded()){CCLOG("AssetsManagerEx : No local manifest file found error.\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);return;}_updateEntry = UpdateEntry::DO_UPDATE;switch (_updateState) {case State::UNCHECKED:{_updateState = State::PREDOWNLOAD_VERSION;}case State::PREDOWNLOAD_VERSION:{downloadVersion();}break;case State::VERSION_LOADED:{parseVersion();}break;case State::PREDOWNLOAD_MANIFEST:{downloadManifest();}break;case State::MANIFEST_LOADED:{parseManifest();}break;case State::FAIL_TO_UPDATE:case State::READY_TO_UPDATE:case State::NEED_UPDATE:{// Manifest not loaded yetif (!_remoteManifest->isLoaded()){_updateState = State::PREDOWNLOAD_MANIFEST;downloadManifest();}else if (_updateEntry == UpdateEntry::DO_UPDATE){startUpdate();}}break;case State::UP_TO_DATE:case State::UPDATING:case State::UNZIPPING:_updateEntry = UpdateEntry::NONE;break;default:break;}
}void AssetsManagerEx::updateAssets(const DownloadUnits& assets)
{if (!_inited){CCLOG("AssetsManagerEx : Manifests uninited.\n");dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_NO_LOCAL_MANIFEST);return;}if (_updateState != State::UPDATING && _localManifest->isLoaded() && _remoteManifest->isLoaded()){_updateState = State::UPDATING;_downloadUnits.clear();_downloadedSize.clear();_percent = _percentByFile = _sizeCollected = _totalDownloaded = _totalSize = 0;_totalWaitToDownload = _totalToDownload = (int)assets.size();_nextSavePoint = 0;_totalEnabled = false;if (_totalToDownload > 0){_downloadUnits = assets;this->batchDownload();}else if (_totalToDownload == 0){onDownloadUnitsFinished();}}
}const DownloadUnits& AssetsManagerEx::getFailedAssets() const
{return _failedUnits;
}void AssetsManagerEx::downloadFailedAssets()
{CCLOG("AssetsManagerEx : Start update %lu failed assets.\n", static_cast<unsigned long>(_failedUnits.size()));updateAssets(_failedUnits);
}void AssetsManagerEx::fileError(const std::string& identifier, const std::string& errorStr, int errorCode, int errorCodeInternal)
{auto unitIt = _downloadUnits.find(identifier);// Found unit and add it to failed unitsif (unitIt != _downloadUnits.end()){_totalWaitToDownload--;DownloadUnit unit = unitIt->second;_failedUnits.emplace(unit.customId, unit);}dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_UPDATING, identifier, errorStr, errorCode, errorCodeInternal);_tempManifest->setAssetDownloadState(identifier, Manifest::DownloadState::UNSTARTED);_currConcurrentTask = std::max(0, _currConcurrentTask-1);queueDowload();
}void AssetsManagerEx::fileSuccess(const std::string &customId, const std::string &storagePath)
{// Set download state to SUCCESSED_tempManifest->setAssetDownloadState(customId, Manifest::DownloadState::SUCCESSED);auto unitIt = _failedUnits.find(customId);// Found unit and delete itif (unitIt != _failedUnits.end()){// Remove from failed units list_failedUnits.erase(unitIt);}unitIt = _downloadUnits.find(customId);if (unitIt != _downloadUnits.end()){// Reduce count only when unit found in _downloadUnits_totalWaitToDownload--;_percentByFile = 100 * (float)(_totalToDownload - _totalWaitToDownload) / _totalToDownload;// Notify progression eventdispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, "");}// Notify asset updated eventdispatchUpdateEvent(EventAssetsManagerEx::EventCode::ASSET_UPDATED, customId);_currConcurrentTask = std::max(0, _currConcurrentTask-1);queueDowload();
}void AssetsManagerEx::onError(const network::DownloadTask& task,int errorCode,int errorCodeInternal,const std::string& errorStr)
{// Skip version error occurredif (task.identifier == VERSION_ID){CCLOG("AssetsManagerEx : Fail to download version file, step skipped\n");_updateState = State::PREDOWNLOAD_MANIFEST;downloadManifest();}else if (task.identifier == MANIFEST_ID){dispatchUpdateEvent(EventAssetsManagerEx::EventCode::ERROR_DOWNLOAD_MANIFEST, task.identifier, errorStr, errorCode, errorCodeInternal);_updateState = State::FAIL_TO_UPDATE;}else{if (_downloadingTask.find(task.identifier) != _downloadingTask.end()) {_downloadingTask.erase(task.identifier);}fileError(task.identifier, errorStr, errorCode, errorCodeInternal);}
}void AssetsManagerEx::onProgress(double total, double downloaded, const std::string& /*url*/, const std::string &customId)
{if (customId == VERSION_ID || customId == MANIFEST_ID){_percent = 100 * downloaded / total;// Notify progression eventdispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, customId);return;}else{// Calcul total downloadedbool found = false;_totalDownloaded = 0;for (auto it = _downloadedSize.begin(); it != _downloadedSize.end(); ++it){if (it->first == customId){it->second = downloaded;found = true;}_totalDownloaded += it->second;}// Collect information if not registedif (!found){// Set download state to DOWNLOADING, this will run only once in the download process_tempManifest->setAssetDownloadState(customId, Manifest::DownloadState::DOWNLOADING);// Register the download size information_downloadedSize.emplace(customId, downloaded);// Check download unit size existance, if not exist collect size in total sizeif (_downloadUnits[customId].size == 0){_totalSize += total;_sizeCollected++;// All collected, enable total sizeif (_sizeCollected == _totalToDownload){_totalEnabled = true;}}}if (_totalEnabled && _updateState == State::UPDATING){float currentPercent = 100 * _totalDownloaded / _totalSize;// Notify at integer level changeif ((int)currentPercent != (int)_percent) {_percent = currentPercent;// Notify progression eventdispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_PROGRESSION, customId);}}}
}void AssetsManagerEx::onSuccess(const std::string &/*srcUrl*/, const std::string &storagePath, const std::string &customId)
{if (customId == VERSION_ID){_updateState = State::VERSION_LOADED;parseVersion();}else if (customId == MANIFEST_ID){_updateState = State::MANIFEST_LOADED;parseManifest();}else{if (_downloadingTask.find(customId) != _downloadingTask.end()) {_downloadingTask.erase(customId);}bool ok = true;auto &assets = _remoteManifest->getAssets();auto assetIt = assets.find(customId);if (assetIt != assets.end()){Manifest::Asset asset = assetIt->second;if (_verifyCallback != nullptr){ok = _verifyCallback(storagePath, asset);} else{ok =onVerifyDefault( storagePath,asset);}}if (ok){bool compressed = assetIt != assets.end() ? assetIt->second.compressed : false;if (compressed){decompressDownloadedZip(customId, storagePath);}else{fileSuccess(customId, storagePath);}}else{fileError(customId, "Asset file verification failed after downloaded");}}
}bool AssetsManagerEx::onVerifyDefault(const std::string storagePath,Manifest::Asset asset)
{//cocos2d::log("onVerifyDefault 0");Data data = cocos2d::FileUtils::getInstance()->getDataFromFile(storagePath);if (data.isNull() ||(data.getSize()== 0 )){CCLOG("onVerifyDefault 1");return false;}std::string result = md5(data.getBytes(), data.getSize());//cocos2d::log("onVerifyDefault:%s - assetmd5:%s-resultmd5:%s",storagePath.c_str(),asset.md5.c_str(),result.c_str());if (memcmp(result.c_str(), asset.md5.c_str(), result.length()) == 0){CCLOG("onVerifyDefault 2");return  true;}// cocos2d::log("onVerifyDefault 3");return false;
}void AssetsManagerEx::destroyDownloadedVersion()
{_fileUtils->removeDirectory(_storagePath);_fileUtils->removeDirectory(_tempStoragePath);
}void AssetsManagerEx::batchDownload()
{_queue.clear();for(auto iter : _downloadUnits){const DownloadUnit& unit = iter.second;if (unit.size > 0){_totalSize += unit.size;_sizeCollected++;}_queue.push_back(iter.first);}// All collected, enable total sizeif (_sizeCollected == _totalToDownload){_totalEnabled = true;}queueDowload();
}void AssetsManagerEx::queueDowload()
{if (_totalWaitToDownload == 0 || (_canceled && _currConcurrentTask == 0)){this->onDownloadUnitsFinished();return;}while (_currConcurrentTask < _maxConcurrentTask && _queue.size() > 0 && !_canceled){std::string key = _queue.back();_queue.pop_back();_currConcurrentTask++;DownloadUnit& unit = _downloadUnits[key];_fileUtils->createDirectory(basename(unit.storagePath));auto downloadTask = _downloader->createDownloadFileTask(unit.srcUrl, unit.storagePath, unit.customId);_downloadingTask.emplace(unit.customId, downloadTask);_tempManifest->setAssetDownloadState(key, Manifest::DownloadState::DOWNLOADING);}if (_percentByFile / 100 > _nextSavePoint){// Save current download manifest information for resuming_tempManifest->saveToFile(_tempManifestPath);_nextSavePoint += SAVE_POINT_INTERVAL;}
}void AssetsManagerEx::onDownloadUnitsFinished()
{// Always save current download manifest information for resuming_tempManifest->saveToFile(_tempManifestPath);// Finished with error checkif (_failedUnits.size() > 0){_updateState = State::FAIL_TO_UPDATE;dispatchUpdateEvent(EventAssetsManagerEx::EventCode::UPDATE_FAILED);}else if (_updateState == State::UPDATING){updateSucceed();}
}void AssetsManagerEx::cancelUpdate()
{if (_canceled){return;}_canceled = true;std::vector<std::shared_ptr<const network::DownloadTask>> tasks;for (const auto& it : _downloadingTask){tasks.push_back(it.second);}for (const auto& it : tasks){_downloader->abort(*it);}_downloadingTask.clear();
}NS_CC_EXT_END

Cocos Creator 解决热更新资源md5比较引发卡顿问题相关推荐

  1. Cocos Creator基于热更新的分包方案

    cocos 的热更新是基于对比本来文件列表和远程文件列表的md5实现的,如果有多个远程资源库,就可以拿来作为分包方案.大概流程是这样的: 一 确定分包策略 首先是,策划要根据一定的策略,将动态加载的资 ...

  2. Cocos Creator 热更新文件MD5计算和需要注意的问题

    Creator的热更新使用jsb.热更新基本按照 http://docs.cocos.com/creator/manual/zh/advanced-topics/hot-update.html?h=% ...

  3. 这么全的 Cocos Creator 3.x 学习资源,竟然是免费的!

    目前 Cocos Creator 3.0 越来越成熟,使用 Cocos Creator 做游戏的小伙伴越来越多.我们在这里整理了现阶段最全面.最优质的 Cocos Creator 3.x 的学习资料, ...

  4. Cocos Creator 3D 重磅更新,全面支持字节跳动!

    Hi,各位开发者,Cocos Creator 3D v1.1.2 版本是 v1.1.x 规划内最后一个稳定版本,我们在 v1.1.1 的基础上做了一系列优化和修复,进一步提升 3D 引擎的稳定性. 除 ...

  5. webstorm 运行android,Windows React Native环境搭建:webstorm+android studio 及解决热更新

    工具准备: SDK Android SDK Build-tools:23.0.1 SDK Platform:Android N .6.0 .5.1.1.5.0.1.4.4.2.4.1.2 Androi ...

  6. StarForce热更新资源学习记录

    学习过程,记录一下 文章目录 目录 文章目录 GF中的热更新步骤 具体操作 检查资源 1.打包设置 2.检查包是否可以运行 3.构建更新资源 4.打包新资源 5.上传资源到服务器 6.制作versio ...

  7. Cocos Creator实战-使用粒子资源实现点击屏幕效果

    文章目录 效果图 涉及到的知识点 制作粒子特效 事件监听 获取触摸点的坐标 制作粒子特效预制资源 动态加载预制资源 动态显示特效 相关参考资料 效果图 涉及到的知识点 粒子特效制作 触摸事件监听以及坐 ...

  8. Cocos Creator 3.x 热更新

    前言:游戏做热更新 是基本需求: 好在 cocos-creator 已经为我们做好了方案,相对于 U3D 的热更新方案来说,使用起来很简便!,不用关注很多细节 本文使用的是 cocos-creator ...

  9. Cocos Creator 2.3.3 更新说明,效率即是一切!

    效率即是一切,Cocos Creator 2.3.3 正式版来啦!本次更新带来了更多新的特性,优化了性能以及提升了稳定性,希望能为广大开发者们保驾护航.建议所有开发者升级哦! 以下是 Cocos Cr ...

最新文章

  1. 美国 2006 年机器学习和知识发现年会数据挖掘使用率较高算法排名
  2. 多线程与高并发(五):强软弱虚四种引用以及ThreadLocal的原理与源码
  3. 我看西电通院月考——学生应该做点什么?
  4. Hibernate学习汇总
  5. Unity3d暴风魔镜发布ios问题记录
  6. 病毒防疫管理系统基于VS2017 .netcore2.2
  7. java判断session中是否存在_java中session用法 判断用户是否登录
  8. 大功率锂电池组BMS(电池管理系统)保护板电路介绍(ACS758/CH704应用案例)
  9. CentOS安装YAPI
  10. 安卓获取浏览器上网记录_Android 获取自带浏览器上网记录
  11. 最新毕业设计参考文献大全
  12. 服务器不能创建对象教务系统,[转]解决强智教务系统非IE下无法创建对象错误...
  13. Python创建决策树—解决隐形眼镜选择问题
  14. 关于如何用vscode使用Competitive Programming Helper (cph)插件以及网页插件competitive-companion实现高效刷题
  15. 网络综合测试仪 的功能和参数
  16. 基于javaweb+SSM的校园外卖点餐系统(java+SSM+JSP+maven+mysql)
  17. Android程序登录界面设计
  18. 面对海量IoT设备,如何打造高性能物联网平台接入层?
  19. 全球各国黑客网络犯罪形式概览
  20. AndroidX深入浅出Jetpack全面介绍

热门文章

  1. 【Qt笔记】[帮助文档]——类QString:取子串函数mid()、left()、right() ——QT怎么取字符串子串切片
  2. Oracle dmp文件转换成为excle/.xlsx/.csv/pdf/text...?
  3. 企业发展四个阶段的股权激励方式
  4. [Android]关于aidl
  5. 小米9运行linux,小米9价格惹争议,看官方怎么说
  6. Python实战之网页刷访问量方法
  7. 【2019年总结】-- 特别的一年
  8. 聚焦云计算、大数据、人工智能等开源技术,这场开源开发者的盛会不容错过!
  9. 如何将照片文字转换成word
  10. 绿标域名与普通域名的区别