大家做热更新的时候都会用到 AssetsManager , 我们使用的 2.2.6 版本还存在一些问题, 在这里记录一下, 希望大家能够避开这些!
1. “can not open destination file xxx”
AssetsManager 在解压时有一定概率会出现这个错误, 让我们先定位错误发生点:
1 | FILE *out = fopen(fullPath.c_str(), "wb"); |
嗯, 其实就是打开一个文件去写的时候发生了错误, 这个 fullPath
就是解压的目标路径, 但是这个目标路径的文件夹是有可能不存在的, 比如 download/res/ui/test.png
, 如果 download/res/ui` 目录不存在, 就会解压失败.
这个问题也有别人遇到了, 还有具体的原因分析, 大家可以看这里: AssetsManager在下载某些特定内容的zip文件后解压缩会失败.
解决方案也比较简单, 已经在 3.x 有实现了, 就是在解压文件前, 遍历创建下每个层级的目录 就可以了, 代码如下:
1 | //There are not directory entry in some case. |
2. 下载进度问题
AssetsManager 的下载进度会在控制台上打印出来, 但是这个进度却与我们注册监听的进度不太一致, 经常都下载完成了, 进度却只走了 10% 左右, 而解压确实是没有占用进度的, 令人十分困惑.
经过仔细阅读源码和分析, 发现了问题所在, AssetsManager 实际上是多线程的, 使用消息队列在线程间通信. 下载线程有消息了, 会压入队列中, 主线程注册了update, 不断的从队列中拿取消息, 处理, 删除. 这套设定其实还是蛮不错的, 但是却没有考虑到主线程的处理能力不足的情况. 下面是下载进度的逻辑:
1 | int assetsManagerProgressFunc(void *ptr, double totalToDownload, double nowDownloaded, double totalToUpLoad, double nowUpLoaded) |
举个例子, 下载线程每秒压入5个消息, 但是主线程的update回调是1秒一次, 得等5s才能处理完成. 这样就会导致下载早已完成, 但主线程却还有一大坨的消息没有处理完成, 仍旧在不紧不慢的处理着. 解决问题的方法也比较简单, 可以从两个角度入手, 提升主线程的处理能力和减少下载线程的消息制造. 由于主线程已经是注册的帧事件 update 了, 没有提升的空间, 所以只能从第二个角度入手了.
分析了下需求, 对于下载进度的索取, 其实没有必要过于精确, 精确到1%就可以了. 这样的话一共只会产生100个事件, 大大减少了事件的数量. 这个问题, 3.x 也做了处理, 和我的想法一致. 摘录实现如下:
1 | int assetsManagerProgressFunc(void *ptr, double totalToDownload, double nowDownloaded, double totalToUpLoad, double nowUpLoaded) |
在查看源码的时候, 也发现了一些非常有意思的事情. AssetsManager 有做一个这样的设定, 下载全部完成后, 会将 downloaded-version-code
字段写入到 UserDefault
中, 解压完成后删除这个字段, 为什么呢?
为了应对解压过程中出现了一些意外, 比如关闭了游戏进程, 这样重启游戏的时候就不用再下载更新包了. 这本是一个不错的设定, 但是它使用了事件队列来做这个事情, 上面说过, 下载完成后, 其实积压了一大坨的事件, 所以这个事件根本不会立刻被执行到. 但是解压的操作却不会受次影响, 会立刻执行到. 这会导致什么问题呢? 导致先删除downloaded-version-code
字段, 后设置, 与预期的执行顺序完全想反. 虽然最后可能不会影响到什么, 但是却是一段非常危险的代码.
Update: 2015年06月25日
还是接着上面的那个问题, AssetsManager 将 downloaded-version-code
记录到了 UserDefault
中, 但是只有解压成功了才会删除, 那么解压失败了呢? 解压失败失败的原因有好多, 如果是下载的更新包有问题的话, 重启后仍然不会重新下载, 直接开始解压, 就会陷入到一个循环中, 一直出错. 解决的办法是什么呢?
舍弃掉这个优化.
将 AssetsManager 中的这几行注释掉, 就不会记录 downloaded-version-code
, 出错后重启就会重新下载:
1 | CCUserDefault::sharedUserDefault()->setStringForKey(KEY_OF_DOWNLOADED_VERSION, |
我们的游戏就遇到了这样的情况, 玩家热更解压失败后就一直处于解压失败的状态, 只能删除游戏重新安装了!
哈哈,正如你所想的,3.x 也做了这样的处理:
1 | if (! uncompress()) |
解压失败后就直接清除了 downloaded-version-code
的记录.
从上述可以看到, 这些问题都已在 3.x 中解决, 所以能升级引擎的还是赶紧升级. 同时也会明白cocos的坑还是蛮多的, 大家一定要做好测试呀!
–EOF–