Download流程的处理由Downloader这个pipe负责(downloader.js),Downloader提供了各种资源的“下载”方式——即如何获取文件内容,有从网络获取、从磁盘获取,不同类型的资源在不同的平台下有不同的获取方式。

  • 比如脚本在原生平台使用require方法获取,而在H5平台则使用动态添加的 <script> HTML标签,指定src进行加载。

  • 又比如json在原生平台使用jsb.fileutils进行加载,而在H5平台则使用XMLHttpRequest从网络下载。

  • Downloader处理

Downloader的handle接收一个item和callback,根据item的type在this.extMap中获取对应的downloadFunc,交由downloadFunc下载,根据下载结果调用callback。同时有一个并发限制,默认最多同时下载64个资源,超过的会进入队列,等待前面的资源加载完成后再依次进行加载。如果item的ignoreMaxConcurrency为true则无视该并发限制。downloadFunc接受一个item和一个callback,如果是同步下载,需要返回downloadFunc的返回值,而异步下载则返回undefined或不返回。

  1. Downloader.prototype.handle = function (item, callback) {
  2. var self = this;
  3. var downloadFunc = this.extMap[item.type] || this.extMap['default'];
  4. var syncRet = undefined;
  5. if (this._curConcurrent < cc.macro.DOWNLOAD_MAX_CONCURRENT) {
  6. this._curConcurrent++;
  7. syncRet = downloadFunc.call(this, item, function (err, result) {
  8. self._curConcurrent = Math.max(0, self._curConcurrent - 1);
  9. self._handleLoadQueue();
  10. callback && callback(err, result);
  11. });
  12. // 当downloadFunc是同步执行的,会返回非undefined的syncRet
  13. if (syncRet !== undefined) {
  14. this._curConcurrent = Math.max(0, this._curConcurrent - 1);
  15. this._handleLoadQueue();
  16. return syncRet;
  17. }
  18. }
  19. else if (item.ignoreMaxConcurrency) {
  20. syncRet = downloadFunc.call(this, item, callback);
  21. if (syncRet !== undefined) {
  22. return syncRet;
  23. }
  24. }
  25. else {
  26. this._loadQueue.push({
  27. item: item,
  28. callback: callback
  29. });
  30. }
  31. };

Downloader的this.extMap记录了各种资源类型的下载方式,所有的类型最终都对应这6个下载方法,downloadScript、downloadImage(downloadWebp)、downloadAudio、downloadText、downloadFont、downloadUuid,它们对应实现了各种类型资源的下载,通过Downloader.addHandlers可以添加或修改任意资源的下载方式。

  • downloadScript

如果是微信或者原生平台,只是对脚本进行require(CommonJS模块化规范),这里主要是web平台的处理,原生平台的处理在后面统一介绍,web平台是通过创建一个script的HTML标签,指定标签的src,添加事件监听,通过这种HTML的方式下载脚本,使其生效。

  1. function downloadScript (item, callback, isAsync) {
  2. if (sys.platform === sys.WECHAT_GAME) {
  3. require(item.url);
  4. callback(null, item.url);
  5. return;
  6. }
  7. // 创建一个script标签元素,并指定其src为我们的源码路径
  8. var url = item.url,
  9. d = document,
  10. s = document.createElement('script');
  11. s.async = isAsync;
  12. s.src = urlAppendTimestamp(url);
  13. function loadHandler () {
  14. s.parentNode.removeChild(s);
  15. s.removeEventListener('load', loadHandler, false);
  16. s.removeEventListener('error', errorHandler, false);
  17. callback(null, url);
  18. }
  19. function errorHandler() {
  20. s.parentNode.removeChild(s);
  21. s.removeEventListener('load', loadHandler, false);
  22. s.removeEventListener('error', errorHandler, false);
  23. callback(new Error('Load ' + url + ' failed!'), url);
  24. }
  25. // 添加加载完成和错误回调
  26. s.addEventListener('load', loadHandler, false);
  27. s.addEventListener('error', errorHandler, false);
  28. d.body.appendChild(s);
  29. }

当cc.game.config['noCache']为true时,urlAppendTimestamp会在url的尾部添加当前的时间戳,这会导致每次加载资源时由于url不同,不会直接使用浏览器的缓存,而是重新获取最新的资源,接下来的各种下载函数中也有urlAppendTimestamp。

  • downloadImage

downloadWebp和downloadImage都是用于下载图片资源,downloadWebp只是判断了cc.sys.capabilities.webp是否为true,如果为false表示当前的环境不支持webp,如果支持则直接调用downloadImage进行下载。downloadImage中引入了2个概念,imagePool和crossOrigin,imagePool是一个JS.Pool,它的get方法会返回一个Image对象。如果是非https下的跨域请求,下载失败时会使用不跨域的方式再请求一次。

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域,以下为跨域的详细描述表格。在web端,使用webgl模式无法直接使用跨域图片,需要服务器配合设置Access-Control-Allow-Origin(Canvas模式允许使用跨域图片)。

当我们访问跨域资源的时候,能否正确加载图片取决于图片服务器是否开启了跨域支持(Access-Control-Allow-Origin: *),比如 http://tools.itharbors.com/res/logo.png 这个资源的服务器开启了跨域支持,所以可以正确加载,不需要调整客户端加载的代码。

那么downloadImage为什么要在设置crossOrigin加载失败之后,将crossOrigin设置为null再加载一次呢?因为关闭crossOrigin之后虽然可以加载,但无法准确地捕获错误。在测试中,如果服务器没有开启跨域支持,通过将crossOrigin设置为null确实可以下载到图片,然而在webgl初始化该图片时会报错。

  1. function downloadImage (item, callback, isCrossOrigin, img) {
  2. if (isCrossOrigin === undefined) {
  3. isCrossOrigin = true;
  4. }
  5. var url = urlAppendTimestamp(item.url);
  6. img = img || misc.imagePool.get();
  7. if (isCrossOrigin && window.location.protocol !== 'file:') {
  8. img.crossOrigin = 'anonymous';
  9. } else {
  10. img.crossOrigin = null;
  11. }
  12. if (img.complete && img.naturalWidth > 0 && img.src === url) {
  13. return img;
  14. } else {
  15. function loadCallback () {
  16. img.removeEventListener('load', loadCallback);
  17. img.removeEventListener('error', errorCallback);
  18. callback(null, img);
  19. }
  20. function errorCallback () {
  21. img.removeEventListener('load', loadCallback);
  22. img.removeEventListener('error', errorCallback);
  23. // Retry without crossOrigin mark if crossOrigin loading fails
  24. // 如果加载失败,重试的时候img.crossOrigin被置为null
  25. // Do not retry if protocol is https, even if the image is loaded, cross origin image isn't renderable.
  26. // 如果是https就不重试了,因为就算加载了到了图片也无法渲染
  27. if (window.location.protocol !== 'https:' && img.crossOrigin && img.crossOrigin.toLowerCase() === 'anonymous') {
  28. downloadImage(item, callback, false, img);
  29. } else {
  30. callback(new Error('Load image (' + url + ') failed'));
  31. }
  32. }
  33. // 设置src开始加载图片
  34. img.addEventListener('load', loadCallback);
  35. img.addEventListener('error', errorCallback);
  36. img.src = url;
  37. }
  38. }
  • downloadFont

downloadFont的本质也是通过添加HTML标签,通过div、style标签来实现字体的加载。通过item的name、srcs或name、url、type进行加载。

  1. function _loadFont (name, srcs, type){
  2. // 创建一个类型为text/css的style标签
  3. var doc = document,
  4. fontStyle = document.createElement('style');
  5. fontStyle.type = 'text/css';
  6. doc.body.appendChild(fontStyle);
  7. // 构建并设置fontStyle的textContent属性
  8. var fontStr = '';
  9. if (isNaN(name - 0)) {
  10. fontStr += '@font-face { font-family:' + name + '; src:';
  11. }
  12. else {
  13. fontStr += '@font-face { font-family:\'' + name + '\'; src:';
  14. }
  15. if (srcs instanceof Array) {
  16. for (var i = 0, li = srcs.length; i < li; i++) {
  17. var src = srcs[i];
  18. type = Path.extname(src).toLowerCase();
  19. fontStr += 'url(\'' + srcs[i] + '\') format(\'' + FONT_TYPE[type] + '\')';
  20. fontStr += (i === li - 1) ? ';' : ',';
  21. }
  22. } else {
  23. type = type.toLowerCase();
  24. fontStr += 'url(\'' + srcs + '\') format(\'' + FONT_TYPE[type] + '\');';
  25. }
  26. fontStyle.textContent += fontStr + '}';
  27. // 添加一个试用该字体的div
  28. //<div style="font-family: PressStart;">.</div>
  29. var preloadDiv = document.createElement('div');
  30. var _divStyle = preloadDiv.style;
  31. _divStyle.fontFamily = name;
  32. preloadDiv.innerHTML = '.';
  33. _divStyle.position = 'absolute';
  34. _divStyle.left = '-100px';
  35. _divStyle.top = '-100px';
  36. doc.body.appendChild(preloadDiv);
  37. }
  38. function downloadFont (item, callback) {
  39. var url = item.url,
  40. type = item.type,
  41. name = item.name,
  42. srcs = item.srcs;
  43. if (name && srcs) {
  44. if (srcs.indexOf(url) === -1) {
  45. srcs.push(url);
  46. }
  47. _loadFont(name, srcs);
  48. } else {
  49. type = Path.extname(url);
  50. name = Path.basename(url, type);
  51. _loadFont(name, url, type);
  52. }
  53. if (document.fonts) {
  54. document.fonts.load('1em ' + name).then(function () {
  55. callback(null, null);
  56. }, function(err){
  57. callback(err);
  58. });
  59. } else {
  60. return null;
  61. }
  62. }
  • downloadAudio

downloadAudio位于audio-downloader.js中,它会根据item的useDom选项决定使用哪种声音下载方式:

  1. function downloadAudio (item, callback) {
  2. // 浏览器不支持音效
  3. if (formatSupport.length === 0) {
  4. return new Error('Audio Downloader: audio not supported on this browser!');
  5. }
  6. item.content = item.url;
  7. // 如果指定了useDom或者不支持WebAudio,会自动帮我们切换成DomAudio
  8. if (!__audioSupport.WEB_AUDIO || (item.urlParam && item.urlParam['useDom'])) {
  9. loadDomAudio(item, callback);
  10. } else {
  11. loadWebAudio(item, callback);
  12. }
  13. }

loadWebAudio会使用cc.loader.getXMLHttpRequest下载资源,在onLoad回调中使用sys.__audioSupport.context["decodeAudioData"]()进行解码。

而loadDomAudio则是通过aduio这个HTML标签进行加载和监听。

  • downloadText

文本的下载分2中方式,如果是原生平台,会使用jsb.fileUtils.getStringFromFile从磁盘中直接获取,如果是其他普通,会使用cc.loader.getXMLHttpRequest下载。

在Creator2.x之后,这段判断被移到了engine目录的jsb目录下,Creator直接在构建时使用合适的代码,而不是在函数执行中去判断当前是哪种平台。

  1. if (CC_JSB) {
  2. module.exports = function (item, callback) {
  3. var url = item.url;
  4. var result = jsb.fileUtils.getStringFromFile(url);
  5. if (typeof result === 'string' && result) {
  6. return result;
  7. } else {
  8. return new Error('Download text failed: ' + url);
  9. }
  10. };
  11. } else {
  12. var urlAppendTimestamp = require('./utils').urlAppendTimestamp;
  13. module.exports = function (item, callback) {
  14. var url = item.url;
  15. url = urlAppendTimestamp(url);
  16. var xhr = cc.loader.getXMLHttpRequest(),
  17. errInfo = 'Load ' + url + ' failed!',
  18. navigator = window.navigator;
  19. xhr.open('GET', url, true);
  20. if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
  21. // IE-specific logic here
  22. xhr.setRequestHeader('Accept-Charset', 'utf-8');
  23. xhr.onreadystatechange = function () {
  24. if(xhr.readyState === 4) {
  25. if (xhr.status === 200 || xhr.status === 0) {
  26. callback(null, xhr.responseText);
  27. } else {
  28. callback({status:xhr.status, errorMessage:errInfo});
  29. }
  30. }
  31. };
  32. } else {
  33. if (xhr.overrideMimeType) xhr.overrideMimeType('text\/plain; charset=utf-8');
  34. xhr.onload = function () {
  35. if(xhr.readyState === 4) {
  36. if (xhr.status === 200 || xhr.status === 0) {
  37. callback(null, xhr.responseText);
  38. } else {
  39. callback({status:xhr.status, errorMessage:errInfo});
  40. }
  41. }
  42. };
  43. xhr.onerror = function(){
  44. callback({status:xhr.status, errorMessage:errInfo});
  45. };
  46. }
  47. xhr.send(null);
  48. };
  49. }
  • downloadUuid

Creator中的资源都会有它的uuid,都会调用该方法进行下载。而uuid资源可能以2种形式存在,第一种是单独的json文件,比如一个prefab或spriteFrame资源,都有自己的json文件。而另一种则是打包资源,所谓的Pack就是将多个json文件合并为一个json文件,把各个json文件中的json对象组合到一个json数组中,从而达到减少IO的作用。downloadUuid方法会使用PackDownloader进行下载,如果下载失败则使用json的下载方式,也就是downloadText。

  1. function downloadUuid (item, callback) {
  2. var result = PackDownloader.load(item, callback);
  3. if (result === undefined) {
  4. return this.extMap['json'](item, callback);
  5. } else if (!!result) {
  6. return result;
  7. }
  8. }

PackDownloader的load方法实现如下,根据uuidToPack中的uuid取出packUuid,如果packUuid不存在,则说明这个uuid没有被打包,直接使用json的方式加载即可。接下来再根据globalUnpackers[packUuid]取出unpacker,调用unpacker.retrieve(uuid)解析出json并返回。

  1. load: function (item, callback) {
  2. var uuid = item.uuid;
  3. var packUuid = uuidToPack[uuid];
  4. if (!packUuid) {
  5. // 返回undefined以让调用者知道它未被识别。
  6. // 不返回false,因为改变返回值类型可能会导致jit失败,尽管返回undefined可能有相同的问题
  7. return;
  8. }
  9. // 一个uuid有可能被重复打包到多个json文件中,《从编辑器到运行时》一章会介绍这种情况如何产生
  10. if (Array.isArray(packUuid)) {
  11. // 这里会遍历多个Pack,从中选择状态最接近加载完成的Pack(谁先加载完用谁)。
  12. packUuid = this._selectLoadedPack(packUuid);
  13. }
  14. // 取出unpacker,如果加载完成了,从unpacker中取出对应uuid的json对象返回。
  15. var unpacker = globalUnpackers[packUuid];
  16. if (unpacker && unpacker.state === PackState.Loaded) {
  17. var json = unpacker.retrieve(uuid);
  18. if (json) {
  19. return json;
  20. } else {
  21. return error(uuid, packUuid);
  22. }
  23. } else { // 其他情况为未加载完成
  24. // unpacker为空则创建一个
  25. if (!unpacker) {
  26. if (!CC_TEST) {
  27. console.log('Create unpacker %s for %s', packUuid, uuid);
  28. }
  29. unpacker = globalUnpackers[packUuid] = new JsonUnpacker();
  30. unpacker.state = PackState.Downloading;
  31. }
  32. // 如果正在加载中或未加载,会走_loadNewPack也就是cc.loader.load,但cc.loader中规避了重复加载。
  33. this._loadNewPack(uuid, packUuid, callback);
  34. }
  35. // 返回null,让调用者知道它正在异步加载
  36. return null;
  37. }

接下来我们进一步了解一下PackDownloader这个类做了什么?Pack又是什么?globalUnpackers和packIndices又是什么?

  • PackDownloader

    • initPacks接受packs变量进行初始化,packIndices变量引用了packs,遍历packs来初始化uuidToPack,建立了uuid到pack的映射。
    • _loadNewPack根据packUuid调用cc.AssetLibrary.getLibUrlNoExt(packUuid) + '.json';获取packUrl,并调用cc.loader.load加载json文件,加载完成后调用 _doLoadNewPack以及callback。
    • _doLoadNewPack根据packUuid从globalUnpackers中取出unpacker,并返回unpacker.retrieve(uuid)

PackDownloader做的事情主要是对Json文件的解析、管理和获取。在某些情况下多个json文件会被打包成一个json文件,如AnimationClip文件,在编辑器制作的时候每个动画都是一个Clip文件(json文件),而在打包之后这些Clip会被合并成一个新的json文件(这样做的目的是节省IO),这就是Pack。

当我们发布项目时Creator自动帮我们进行合并,多个json对象组成一个数组对象,packIndices记录了每个packUuid对应的一组uuid(也就是一个pack文件中合并了哪些文件),每个文件的uuid对应这个json数组对象的下标。packIndices[packUuid]的下标1是该packUuid对应合并后的json数组下标1这个json对象的uuid。

每个Clip都有一个uuid,通过uuidToPack的索引获取这个Clip对应的packUuid,也就是合并Json的uuid,这个uuid会对应一个JsonUnpacker,JsonUnpacker会将合并后的json进行解析并缓存,同时保持一个映射,在这里就是每个Clip的uuid对应的json对象。

  1. // 初始化Packs,这里传入的packs是一个二维数组,首先它是一个uuids的数组,一组uuid被视为一个pack,packs就是一组pack
  2. // 每个uuids都是一个数组,记录了这个pack中合并的所有uuid。
  3. initPacks: function (packs) {
  4. packIndices = packs;
  5. for (var packUuid in packs) {
  6. var uuids = packs[packUuid];
  7. for (var i = 0; i < uuids.length; i++) {
  8. var uuid = uuids[i];
  9. // the smallest pack must be at the beginning of the array to download more first
  10. // 最小的pack必须放在数组的前面,以便下载更多的包。
  11. var pushFront = uuids.length === 1;
  12. // map - uuidToPack, key - uuid, value - packUuid (如果已存在该key,value会添加到数组中)
  13. pushToMap(uuidToPack, uuid, packUuid, pushFront);
  14. }
  15. }
  16. },
  17. // 加载一个新的Pack时会调用该方法,根据packUuid去获取url,并立即下载(ignoreMaxConcurrency为true)
  18. _loadNewPack: function (uuid, packUuid, callback) {
  19. var self = this;
  20. var packUrl = cc.AssetLibrary.getLibUrlNoExt(packUuid) + '.json';
  21. cc.loader.load({ url: packUrl, ignoreMaxConcurrency: true }, function (err, packJson) {
  22. if (err) {
  23. cc.errorID(4916, uuid);
  24. return callback(err);
  25. }
  26. var res = self._doLoadNewPack(uuid, packUuid, packJson);
  27. if (res) {
  28. callback(null, res);
  29. } else {
  30. callback(error(uuid, packUuid));
  31. }
  32. });
  33. },
  34. // 当一个Pack加载完之后,会回调该方法
  35. _doLoadNewPack: function (uuid, packUuid, packJson) {
  36. var unpacker = globalUnpackers[packUuid];
  37. // double check cache after load
  38. // 只要unpacker的状态不是PackState.Loaded,进行解析并切换状态
  39. if (unpacker.state !== PackState.Loaded) {
  40. unpacker.read(packIndices[packUuid], packJson);
  41. unpacker.state = PackState.Loaded;
  42. }
  43. return unpacker.retrieve(uuid);
  44. },
  45. // 遍历多个packUuid,只要找到第一个状态为PackState.Loaded的unpacker
  46. // 找不到则找一个最接近PackState.Loaded的unpacker
  47. _selectLoadedPack: function (packUuids) {
  48. var existsPackState = PackState.Invalid;
  49. var existsPackUuid = '';
  50. for (var i = 0; i < packUuids.length; i++) {
  51. var packUuid = packUuids[i];
  52. var unpacker = globalUnpackers[packUuid];
  53. if (unpacker) {
  54. var state = unpacker.state;
  55. if (state === PackState.Loaded) {
  56. return packUuid;
  57. } else if (state > existsPackState) {
  58. existsPackState = state;
  59. existsPackUuid = packUuid;
  60. }
  61. }
  62. }
  63. return existsPackState !== PackState.Invalid ? existsPackUuid : packUuids[0];
  64. },
  • globalUnpackers

    • globalUnpackers根据packUuid为索引,保存着JsonUnpacker对象。
    • JsonUnpacker记录了jsons和state,关键的read方法和retrieve方法的职责是解析json数据以及根据key从jsons中查询信息。
  1. JsonUnpacker.prototype.read = function (indices, data) {
  2. var jsons = typeof data === 'string' ? JSON.parse(data) : data;
  3. if (jsons.length !== indices.length) {
  4. cc.errorID(4915);
  5. }
  6. for (var i = 0; i < indices.length; i++) {
  7. var key = indices[i];
  8. var json = jsons[i];
  9. this.jsons[key] = json;
  10. }
  11. };
  12. JsonUnpacker.prototype.retrieve = function (key) {
  13. return this.jsons[key] || null;
  14. };

这里传入的data是一个数组json对象,indices是一个uuid数组,read的职责就是将indices[i]作为uuid,对应的jsons[i]作为json对象,记录到this.jsons这个容器中,那么后面的retrieve就可以用uuid来获取对应的json对象了。

  • 关于packIndices

    • 在AssetLibrary的init方法中,调用了PackDownloader.initPacks(options.packedAssets);
    • 项目发布时会生成一个巨大的settings.js文件,该文件内容如下图所示,其中的packedAssets就是我们的packIndices。
    • 例如图中的key 01204b0d7可以在发布后的res/import/01目录中找到01204b0d7.json,这个文件是一个有5个对象的json数组,他们的uuid分别为0、205、207、1、473。

01204b0d7.json文件对应的内容在格式化查看工具中打开如下所示,正好是一个拥有5个对象的json数组,第一个对象是Array、后面是4个Object对象。而上图对应的packedAssets下的01204b0d7对象数组为这个json数组的uuid,按下标一一对应。

  • 原生Downloader处理

在原生平台下会执行jsb-loader.js下的内容,对于字体、音效、脚本和图片使用新的下载方法。

  1. // 字体使用了empty
  2. function empty (item, callback) {
  3. return null;
  4. }
  5. // 下载脚本直接使用require即可
  6. function downloadScript (item, callback) {
  7. require(item.url);
  8. return null;
  9. }
  10. // 声音不需要下载,声音的加载流程包含了下载
  11. function downloadAudio (item, callback) {
  12. return item.url;
  13. }
  14. // 图片分3种情况,textureCache中缓存直接使用、远程图片使用jsb.loadRemoteImg、本地图片使用textureCache的addImageAsync方法加载。
  15. function loadImage (item, callback) {
  16. var url = item.url;
  17. var cachedTex = cc.textureCache.getTextureForKey(url);
  18. if (cachedTex) {
  19. return cachedTex;
  20. } else if (url.match(jsb.urlRegExp)) {
  21. jsb.loadRemoteImg(url, function(succeed, tex) {
  22. if (succeed) {
  23. tex.url = url;
  24. callback && callback(null, tex);
  25. } else {
  26. callback && callback(new Error('Load image failed: ' + url));
  27. }
  28. });
  29. } else {
  30. var addImageCallback = function (tex) {
  31. if (tex instanceof cc.Texture2D) {
  32. tex.url = url;
  33. callback && callback(null, tex);
  34. }
  35. else {
  36. callback && callback(new Error('Load image failed: ' + url));
  37. }
  38. };
  39. cc.textureCache._addImageAsync(url, addImageCallback);
  40. }
  41. }

在项目发布时,会根据发布平台生成最终的执行代码。构建原生平台时Creator1.x会指定engine/jsb目录下的脚本,而Creator2.x指定的是engine/bin目录下的jsb脚本。

Cocos Creator 资源加载流程剖析【二】——Download部分的更多相关文章

  1. Cocos Creator 资源加载流程剖析【一】——cc.loader与加载管线

    这系列文章会对Cocos Creator的资源加载和管理进行深入的剖析.主要包含以下内容: cc.loader与加载管线 Download部分 Load部分 额外流程(MD5 Pipe) 从编辑器到运 ...

  2. Cocos Creator 资源加载流程剖析【三】——Load部分

    Load流程是整个资源加载管线的最后一棒,由Loader这个pipe负责(loader.js).通过Download流程拿到内容之后,需要对内容做一些"加载"处理.使得这些内容可以 ...

  3. Cocos Creator 资源加载流程剖析【六】——场景切换流程

    这里讨论场景切换的完整流程,从我们调用了loadScene开始切换场景,到场景切换完成背后发生的事情.整个流程可以分为场景加载和场景切换两部分,另外还简单讨论了场景的预加载. 加载场景的流程 load ...

  4. Cocos Creator 资源加载流程剖析【五】——从编辑器到运行时

    我们在编辑器中看到的资源,在构建之后会进行一些转化,本章将揭开Creator对资源进行的处理. 资源处理的整体规则 首先我们将Creator的开发和运行划分为以下几个场景: 编辑器 当我们将资源放到编 ...

  5. Cocos Creator 资源加载流程剖析【四】——额外流程(MD5 PIPE)

    当我们将游戏构建发布到web平台时,勾选Md5 Cache选项可以开启MD5 Pipe,它的作用是给构建后的资源加上md5后缀,避免浏览器的缓存导致部分资源不是最新,因为使用了md5后缀后,当资源内容 ...

  6. Cocos Creator 资源加载(笔记)

    cc.loader 加载资源动态加载资源要注意两点,一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下.resources 需要在 assets 文件夹中手工 ...

  7. java面试记录二:spring加载流程、springmvc请求流程、spring事务失效、synchronized和volatile、JMM和JVM模型、二分查找的实现、垃圾收集器、控制台顺序打印ABC的三种线程实现

    注:部分答案引用网络文章 简答题 1.Spring项目启动后的加载流程 (1)使用spring框架的web项目,在tomcat下,是根据web.xml来启动的.web.xml中负责配置启动spring ...

  8. 插件化框架解读之Android 资源加载机制详解(二)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680Android提供了一种非常灵活的资源系统,可以根据不同的条件提供 ...

  9. Android之Android apk动态加载机制的研究(二):资源加载和activity生命周期管理

    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客) 前言 为了 ...

随机推荐

  1. TCP的延迟ACK机制

    TCP的延迟ACK机制 TCP的延迟ACK机制一说到TCP,人们就喜欢开始扯三步握手之类的,那只是其中的一个环节而已.实际上每一个数据包的正确发送都是一个类似握手的过程,可以简单的把它视为两步握手.一 ...

  2. CentOS安装JAVA

    http://hermosa-young.iteye.com/blog/1798026 每次都要搜索一下太麻烦了,留个底,方便以后查询 一般情况下,我们都要将linux自带的OPENJDK卸载掉,然后 ...

  3. 接口测试返回的json文件中字符串是乱序

    问题描述 接口测试中post方式匹配返回信息时显示不匹配, 但是statuscode明明是200, 而且用postman /restclient等工具测出来也是没问题的. 根本原因 封装了这么个方法来 ...

  4. Java Script 读书笔记 (三) 函数

    1. 函数作用域 在函数内部定义的变量,外部无法读取,称为"局部变量"(local variable). 变量v在函数内部定义,所以是一个局部变量,函数之外就无法读取. 函数内部定 ...

  5. vector作为函数返回值

    在实际的操作中,我们经常会碰到需要返回一序列字符串或者一列数字的时候,以前会用到数组来保存这列的字符串或者数字,现在我们可以用vector来保存这些数据.但是当数据量很大的时候使用vector效率就比 ...

  6. 删除外部dwg中指定的块定义

    本例实现删除外部图纸中指定的块定义,在外部图纸当前模型空间中是没有该块定义的块参照存在. 代码如下: void CBlockUtil::DeleteBlockDefFormOtherDwg(const ...

  7. BZOJ_3261_最大异或和_可持久化trie

    BZOJ_3261_最大异或和_可持久化trie Description 给定一个非负整数序列{a},初始长度为N. 有M个操作,有以下两种操作类型: 1.Ax:添加操作,表示在序列末尾添加一个数x, ...

  8. HTTP VISUAL HTTP请求可视化工具、HTTP快照工具(公测)

    先啰嗦几句,最近工作比较忙,再加上自己又开设了一个小站(简单点),没时间写博客,都快憋坏了,趁着周末有时间,抓紧来一篇~ HTTP VISUAL是一款HTTP可视化工具,它可以记录HTTP请求,包括请 ...

  9. appium+python 清空文本框EditText的值

    清空EditText的自动化脚本编写流程: 前提条件:进入到要删除文本框的页面 1.查找到要删除的文本框,可通过id.name等属性进行查找 2.点击 3.通过get_attribute(" ...

  10. [Swift]LeetCode1033. 移动石子直到连续 | Moving Stones Until Consecutive

    Three stones are on a number line at positions a, b, and c. Each turn, let's say the stones are curr ...