概述
资源的加载主要包含游戏包内的 src/project.dev.js 、res目录下的文件以及一些网络的资源。
engine 部分资源文件的管理主要在 cocos2d/core/assets/CCAsset.js 和 cocos2d/core/platform/CCAssetLibrary.js。
engine 部分资源文件的加载和释放代码在 cocos2d/core/load-pipeline 目录下面,主要有 CCloader.js、pipeline.js、loader.js、loading-items.js、downloader.js、text-download.js、asset-loader.js
如果是原生平台,runtiem 部分资源文件的加载相关代码在 HttpClient-android.cpp、jsb_xmlhttprequest.cpp、Cocos2dxHttpURLConnection.java,主要是文件下载的实现部分。
如果是原生平台,还包括 jsb-adapter/engine/jsb-loader.js,这部分包括对原生平台对 Downloader 和 Loader 的重新实现。
AssetLibrary 初始化在 main.js 中进行,配置资源文件的路径:
1 | // init assets |
框架简介
- CCLoader:继承于 Pipeline,CCLoader提供了友好的资源管理接口(加载、获取、释放)以及一些辅助接口(如自动释放、对Pipeline的修改)。
- Pipeline:包含了多个Pipe和多个LoadingItems,这里实现了一个Pipe到Pipe衔接流转的过程,以及Pipe和LoadingItems的管理接口。pipeline 自身实现的是资源缓存和让资源依次流过管道,即每个资源依次经过每个 pipe 的处理。
- Pipe:这里的 pipe 是指实现了加载资源的单位(如 loader,assetLoader,downloader),pipeline 对资源的处理最终是调用 pipe.handle 实现的。每一种Pipe都会对资源进行特定的加工。
- LoadingItems:LoadingItems 是一个加载对象队列,可以用来输送加载对象到加载管线中。它继承于CallbackInvoker,管理着LoadingItem,一个 LoadingItem 就是资源从开始加载到加载完成的上下文。这里说的上下文,指的是与加载该资源相关的变量的集合,比如当前加载的状态、url、依赖哪些资源、以及加载完成后的对象等等。LoadingItems 配合 pipeline,实现了加载状态维护、依赖资源入队等内容。
资源加载
资源加载采用了 PipeLine 模式。
一次资源的加载流程底层的调用函数如下:
- cc.loader.loadRes()
- CCLoader.load()
- loading-items.append
- pipeline.flowIn
- pipeline.flow
- pipe.handle(pipe 对应于 asset-loader、downloader、loader)
- 加载完成,进行回调
加载流程中其实还有一种情况需要注意:loader 其实会根据资源类型将加载任务分发给不同的类,如 uuid 类型会交给 uuid-loader。uuid-loader 加载时会加载该资源的依赖资源,其过程为:
- uuid-loader.loadUuid
- uuid-loader.loadDepends
- pipeline.flowInDeps
- loading-items.append
- 后续流程和正常流程一致
加载流程
CCLoader
CCLoader 继承自 pipeline:
1 | // engine CCLoader.js |
创建 CCLoader 时,会把三个 Pipe 提供给 PipeLine:
1 | Pipeline.call(this, [ |
CCLoader 是用户可以直接调用来加载、释放资源的类,它对 pipeline、loading-items、loader 进行了整合封装,并提供了 load
、loadRes
、loadResDir
、release
、releaseRes
等方法供用户使用。资源加载相关的便是 load
、loadRes
等方法,而所有方法最终是调用 load
方法来进行资源加载的。load
和其它接口的最大区别在于,load
可以用于加载绝对路径的资源(比如一个sd卡的绝对路径、或者网络上的一个url),而 loadRes
等只能加载 res 目录下的资源。
先来看一下 loadRes
方法:
1 | // engine CCLoader.js |
loadRes 工作流程如下:
- 调用_getResUuid查询uuid,该方法会调用AssetTable的getUuid方法查询资源的uuid。从网络上加载的资源以及SD卡中我们存储的资源,Creator并没有为它们生成uuid。所以这些不是在Creator项目中生成的资源不能使用loadRes来加载。
- 调用this.load方法加载资源。
- 在回调方法中设置加载完成后的处理,在加载完成后,该资源以及其引用的资源都会被标记为禁止自动释放(在场景切换的时候,Creator会自动释放下个场景不使用的资源)。
load 方法如下:
1 | // engine CCLoader.js |
load 方法中,所做的就是创建一个 LoadingItem,然后对其进行初始化,之后调用其 append 方法,把一个加载项 LoadingItems 队列。这里 LoadingItem 简单来看就是对待加载资源的一层封装。
所有要加载的资源最后会被添加到_sharedResources中(不论该资源是否已加载,如果已加载会push它的item,未加载会push它的res对象,res对象是通过 getResWithUrl
方法从 AssetLibrary
中查询出来的)。
loading-tiems
loading-items 主要是完善了每个资源对象的属性,包括资源内容、url 地址等,同时维护每个资源的加载状态、依赖关系等。
1 | // engine loading-tiems.js |
append 所做的是:先对资源列表 urlList 里每一个元素做字段完善(create)、依赖处理(registerQueueDep),然后调用 pipeline.flowIn 将所有未加载完成的资源放入 pipeline 之中。
pipeline
pipeline 包含多个 pipe,pipe 存储在 _pipes 数组中,而且数组中的 nextPipe 赋值给了 lastPipe.next,以此将 pipe 链接成单向链表结构。这里的 pipe 是指实现了加载资源的单位(如 loader),pipeline 对资源的处理最终是调用 pipe.handle 实现的,pipeline 自身实现的是资源缓存和让资源依次流过管道,即每个资源依次经过每个 pipe 的处理。在 CCLoader 初始化 pipeline 时,为它添加了 AssetLoader、Downloader、Loader 三个 pipe,每个资源都会依次经过这三个 pipe 的处理。pipeline 中的 _cache 数组是对每个资源的缓存。
1 | // asset-loader pipeline.js |
lowIn 方法所做其实就是:先将每个资源 item 缓存到 _cache 中,然后对每个 item 调用 flow 方法。这里要注意的是 flow 的 pipe 参数为 _pipes[0],即先将资源交给 pipeline 的第一个 pipe。以下是 flow 方法:
1 | function flow (pipe, item) { |
flow 方法中对资源的处理分为三部分:
- 如果资源状态为 ERROR,则直接返回
- 如果资源状态为 COMPLETE 已完成,如果后续还有 pipe 就将其交给下一个 pipe 处理
- 如果资源状态为其他状态,则调用 pipe.handle 方法对其进行加载。加载完成后,先将加载结果交给 item,即item.content = result,然后会对其加载状态 states 进行修改,最后如果后续还有 pipe 就将其交给后续 pipe 处理。
Pipe
加下来介绍一下 pipe 对资源的处理,pipe-line 中包含了三个 pipe:assetLoader、downloader 和 loader。
asset-loader
asset-loader 是Pipeline的第一个Pipe,这个Pipe的职责是进行初始化,从cc.AssetLibrary中取出该资源的完整信息,获取该资源的类型,对rawAsset类型进行设置type,方便后面的pipe执行不同的处理,而非rawAsset则执行callback进入下一个Pipe处理。
1 | // engine asset-loader.js |
downloader
downloader 是对资源处理的第二道工序,负责对资源的下载,比如从磁盘读取,从网络读取等。
不同类型的资源在不同的平台下有不同的获取方式:
- 比如脚本在原生平台使用 require 方法获取,而在H5平台则使用动态添加的
<script>
HTML标签,指定src进行加载。 - 又比如json在原生平台使用 jsb.fileutils 进行加载,而在H5平台则使用XMLHttpRequest从网络下载。
关于 jsb.fileutils 的实现,参考前面的《游戏文件加载流程》一文。
原生平台在 jsb-adapter/engine/jsb-loader.js 对 Downloader 的方法进行了重新实现。
1 | // engine downloader.js |
在 handle 方法中根据资源类型选择不同的方法加载。Downloader的 this.extMap 记录了各种资源类型的下载方式,在 Web 平台所有的类型最终都对应 downloader.js 提供的这6个下载方法:
1 | // engine downloader.js |
在初始化 Downloader 时加到 extMap:
1 | this.extMap = js.mixin(extMap, defaultMap); |
在原生平台:
1 | // jsb-adapter jsb-loader.js |
downloadScript
如果是微信或者原生平台,只是对脚本进行require(CommonJS模块化规范),这里主要是web平台的处理,原生平台的处理在后面统一介绍,web平台是通过创建一个script的HTML标签,指定标签的src,添加事件监听,通过这种HTML的方式下载脚本,使其生效。
1 | // jsb-adapter jsb-loader.js |
1 | // engine downloader.js |
downloadImage
1 | // jsb-adapter jsb-loader.js |
对于 Web 平台下载图片的方法,注意对于跨域的处理。
由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域,以下为跨域的详细描述表格。在web端,使用webgl模式无法直接使用跨域图片,需要服务器配合设置Access-Control-Allow-Origin(Canvas模式允许使用跨域图片)。
1 | // engine downloader.js |
downloadText
1 | // jsb-adapter jsb-loader.js |
1 | // engine text-downloader.js |
资源下载
对于原生平台不论是图片还是文本的加载最终会调用 runtime 中的 HttpClient-android 的相关 api 来进行下载。
在 jsb_xmlhttprequest.cpp 中实现 js 中 XMLHttpRequest
的方法,资源的下载就靠它们来完成。
1 | // jsb_xmlhttprequest.cpp |
loader
loader 一般是对资源最后的一道处理工序,比如对下载完之后的图片进行处理等。
这部分在不同平台的处理也是不一样的。
原生平台在 jsb-adapter/engine/jsb-loader.js 对 Downloader 的方法进行了重新实现。
Web 平台的实现在 load-pipeline/loader.js。
Web 平台:
1 | // engine loader.js |
1 | // engine loader.js |
原生Android平台
1 | // jsb-adapter jsb-loader.js |
资源释放
CCLoader 对于资源释放提供了 release、releaseRes、releaseAsset、releaseResDir、releaseAll 方法,但实际上所有方法最终是调用 release 方法来实现资源释放的,在 release 调用之前所做的就是获取资源对应的 uuid,然后调用 release 方法。
相关文章:
https://blog.csdn.net/u013647453/article/details/88564966
https://www.cnblogs.com/ybgame/p/10576884.html
https://www.cnblogs.com/ybgame/p/10576986.html