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

Load流程是整个资源加载管线的最后一棒,由Loader这个pipe负责(loader.js)。通过Download流程拿到内容之后,需要对内容做一些“加载”处理。使得这些内容可以在游戏中使用。这里并不是所有的资源都需要进行一个加载处理,目前只有图片、Json、Plist、Uuid(Prefab、场景)等资源才会执行加载的流程,其他的资源在Download流程之后就可以在游戏中使用了。

  • Loader处理

Loader的handle接收一个item和callback,根据item的type在this.extMap中获取对应的loadFunc。

1Loader.prototype.addHandlers = function (extMap) { 2 this.extMap = JS.mixin(this.extMap, extMap); 3}; 4 5Loader.prototype.handle = function (item, callback) { 6 var loadFunc = this.extMap[item.type] || this.extMap['default']; 7 return loadFunc.call(this, item, callback); 8};
  • 资源的加载方式

Loader的this.extMap记录了各种资源类型的下载方式,所有的类型最终都对应这5个加载方法,loadNothing、loadJSON、loadImage、loadPlist、loadUuid,它们对应实现了各种类型资源的加载,通过Loader.addHandlers可以添加或修改任意资源的加载方式。加载结束后将可用的内容返回。

1// 无需加载,即经过前面的下载已经可用了,例如font、script等资源 2function loadNothing (item, callback) { 3 return null; 4} 5 6// 使用JSON.parse进行解析并返回 7function loadJSON (item, callback) { 8 if (typeof item.content !== 'string') { 9 return new Error('JSON Loader: Input item doesn\'t contain string content'); 10 } 11 12 try { 13 var result = JSON.parse(item.content); 14 return result; 15 } 16 catch (e) { 17 return new Error('JSON Loader: Parse json [' + item.id + '] failed : ' + e); 18 } 19} 20 21// 创建Texture2D,并根据图片的内容初始化Texture2D,最后添加到cc.textureCache中 22function loadImage (item, callback) { 23 if (sys.platform !== sys.WECHAT_GAME && !(item.content instanceof Image)) { 24 return new Error('Image Loader: Input item doesn\'t contain Image content'); 25 } 26 var rawUrl = item.rawUrl; 27 var tex = cc.textureCache.getTextureForKey(rawUrl) || new Texture2D(); 28 tex.url = rawUrl; 29 tex.initWithElement(item.content); 30 tex.handleLoadedTexture(); 31 cc.textureCache.cacheImage(rawUrl, tex); 32 return tex; 33} 34 35// 使用cc.plistParser进行解析并返回 36function loadPlist (item, callback) { 37 if (typeof item.content !== 'string') { 38 return new Error('Plist Loader: Input item doesn\'t contain string content'); 39 } 40 var result = cc.plistParser.parse(item.content); 41 if (result) { 42 return result; 43 } 44 else { 45 return new Error('Plist Loader: Parse [' + item.id + '] failed'); 46 } 47}
  • loadUuid

loadUuid用于加载creator内部统一规划的资源,每个uuid都会对应一个json对象,可能是prefab、spriteFrame,等等。在loadUuid这个方法中,最关键的操作就是cc.deserialize反序列化把资源对象创建了出来,其次就是加载依赖资源。

uuid的解析首先需要一个json对象,如果item的content是string类型,则进行解析,如果是object类型,则直接使用item.content,如果既不是string也不是object则直接报错。

1function loadUuid (item, callback) { 2 if (CC_EDITOR) { 3 MissingClass = MissingClass || Editor.require('app://editor/page/scene-utils/missing-class-reporter').MissingClass; 4 } 5 6 // 获取json对象,如果是string则进行解析,如果是object则直接使用,报错则返回Error对象 7 var json; 8 if (typeof item.content === 'string') { 9 try { 10 json = JSON.parse(item.content); 11 } catch (e) { 12 return new Error('Uuid Loader: Parse asset [' + item.id + '] failed : ' + e.stack); 13 } 14 } else if (typeof item.content === 'object') { 15 json = item.content; 16 } else { 17 return new Error('JSON Loader: Input item doesn\'t contain string content'); 18 } 19 20 // 根据是否场景对象、编辑器环境来决定classFinder的实现。 21 var classFinder; 22 var isScene = isSceneObj(json); 23 if (isScene) { 24 if (CC_EDITOR) { 25 // 编辑器 + 场景的模式下,使用MissingClass.classFinder作为包裹函数 26 MissingClass.hasMissingClass = false; 27 classFinder = function (type, data, owner, propName) { 28 var res = MissingClass.classFinder(type, data, owner, propName); 29 if (res) { 30 return res; 31 } 32 return cc._MissingScript.getMissingWrapper(type, data); 33 }; 34 classFinder.onDereferenced = MissingClass.classFinder.onDereferenced; 35 } else { 36 // 非编辑器下,使用cc._MissingScript.safeFindClass,也是调用了JS._getClassById 37 // 区别是在解析失败后会调用cc.deserialize.reportMissingClass(id); 38 classFinder = cc._MissingScript.safeFindClass; 39 } 40 } else { 41 classFinder = function (id) { 42 // JS为引擎的platform\js.js,而_getClassById方法从_idToClass[classId]中返回Class 43 // _idToClass为id到类的一个注册map,key为id,value为class 44 // 使用CCClass定义继承自cc.Component的类会被自动注册到_idToClass中 45 // platform\CCClass.js中的var cls = define(name, base, mixins, options); 46 // 最终调用了JS.setClassName,Creator的类的实现细节是另外一个大话题 47 // 这里只需要了解,所有可拖拽到prefab上的类都会被注册到JS._idToClass中,这里的id就是类名 48 var cls = JS._getClassById(id); 49 if (cls) { 50 return cls; 51 } 52 cc.warnID(4903, id); 53 return Object; 54 }; 55 } 56 57 // 进行反序列化,反序列化出asset 58 var tdInfo = cc.deserialize.Details.pool.get(); 59 var asset; 60 try { 61 // deserialize的实现位于platform\deserialize.js 62 // 具体的实现非常复杂,大致可以理解为new出对应的类,并从json对象中反序列化该类的所有属性 63 // 所以返回的asset是这个json最顶层object对应的类,比如cc.SpriteFrame或者自定义的组件 64 // 该资源所依赖的所有资源会被反序列化到tdInfo中,在tdInfo.uuidList中。 65 asset = cc.deserialize(json, tdInfo, { 66 classFinder: classFinder, 67 target: item.existingAsset, 68 customEnv: item 69 }); 70 } catch (e) { 71 cc.deserialize.Details.pool.put(tdInfo); 72 var err = CC_JSB ? (e + '\n' + e.stack) : e.stack; 73 return new Error('Uuid Loader: Deserialize asset [' + item.id + '] failed : ' + err); 74 } 75 76 // 如果是在编辑器下的场景存在类丢失,进行报告(应该是报红) 77 asset._uuid = item.uuid; 78 if (CC_EDITOR && isScene && MissingClass.hasMissingClass) { 79 MissingClass.reportMissingClass(asset); 80 } 81 82 // 判断是否可延迟加载,并调用loadDepends 83 var deferredLoad = canDeferredLoad(asset, item, isScene); 84 loadDepends(this.pipeline, item, asset, tdInfo, deferredLoad, callback); 85}

canDeferredLoad方法会根据资源类型监测是否可以延迟加载,当item的deferredLoadRaw为true且该资源支持延迟加载(在代码中搜索preventDeferredLoadDependents可以发现除了TileMap、DragonBones、Spine等资源外,都不支持延迟加载),或是设置了延迟加载的场景才可以延迟加载。

1// can deferred load raw assets in runtime 2// 检查是否延迟加载Raw Assets 3function canDeferredLoad (asset, item, isScene) { 4 if (CC_EDITOR || CC_JSB) { 5 return false; 6 } 7 var res = item.deferredLoadRaw; 8 if (res) { 9 // check if asset support deferred 10 // 检查该资源是否支持延迟加载 11 if (asset instanceof cc.Asset && asset.constructor.preventDeferredLoadDependents) { 12 res = false; 13 } 14 } else if (isScene) { 15 // 如果是prefab或scene,取其asyncLoadAssets属性 16 if (asset instanceof cc.SceneAsset || asset instanceof cc.Prefab) { 17 res = asset.asyncLoadAssets; 18 } 19 } 20 return res; 21}

loadDepends方法会加载依赖,主要做了2个事情,延迟加载和依赖加载

延迟加载指的是资源A依赖了B、C、D,其中资源D延迟加载了,那么BC加载完成即算这个资源加载完成,并执行回调,D也会进行加载,但什么时候加载完这里并不关心。在实际应用中的表现就是加载一个场景,基础部分的内容加载完成了,进入了该场景之后再陆续看到其他内容加载完成。

根据deferredLoadRawAssetsInRuntime,对raw类型资源进行延迟加载,延迟加载的内容会进入dependKeys数组,而不延迟加载的内容进入depends数组。

depends数组是该资源所依赖的资源数组,loadDepends会调用pipeline.flowInDeps进行加载,如果该数组为空则不加载依赖,执行完成回调。dependKeys数组是item的属性,记录了该资源依赖的所有资源,在做资源释放的时候会用到。预加载的内容会直接进入dependKeys,而正常加载的资源在加载完成后才会被添加到dependKeys中。

最后调用pipeline.flowInDeps加载depends数组,flowInDeps的完成回调中,如果item加载完成且没有报错,调用loadCallback,如果未加载完成,插入到item的queue的 _callbackTable[dependSrc]中或添加queue的监听(这两个操作的意义都是在item加载完成后执行loadCallback),loadCallback将依赖对象的依赖属性进行赋值,并添加该资源的id到dependKeys中。

当反序列化出来的asset._preloadRawFiles有值时,会将callback进行包裹,在异步加载完RawFiles才执行最终的callback。实际并没有什么作用。

1function loadDepends (pipeline, item, asset, tdInfo, deferredLoadRawAssetsInRuntime, callback) { 2 // tdInfo.uuidList为这个prefab或场景所依赖的uuid类型的资源 3 var uuidList = tdInfo.uuidList; 4 var objList, propList, depends; 5 var i, dependUuid; 6 // cache dependencies for auto release 7 // dependKeys用于缓存该资源的依赖,在资源释放的时候会用到 8 var dependKeys = item.dependKeys = []; 9 10 /******************************* 过滤决定哪些资源要加载,哪些要延迟,得出depends数组 **********************************/ 11 // 如果支持延迟加载 12 if (deferredLoadRawAssetsInRuntime) { 13 objList = []; 14 propList = []; 15 depends = []; 16 // parse depends assets 17 for (i = 0; i < uuidList.length; i++) { 18 dependUuid = uuidList[i]; 19 var obj = tdInfo.uuidObjList[i]; 20 var prop = tdInfo.uuidPropList[i]; 21 var info = cc.AssetLibrary._getAssetInfoInRuntime(dependUuid); 22 if (info.raw) { 23 // skip preloading raw assets 24 // 对于raw类型的资源不进行加载,tdInfo.uuidObjList[i][prop] = url 25 var url = info.url; 26 obj[prop] = url; 27 dependKeys.push(url); 28 } else { 29 objList.push(obj); 30 propList.push(prop); 31 // declare depends assets 32 // 对于非raw类型的资源,进入depends进行加载,但带上deferredLoadRaw标记 33 // 意为该uuid引用的其他raw类型的资源进行延迟加载 34 depends.push({ 35 type: 'uuid', 36 uuid: dependUuid, 37 deferredLoadRaw: true, 38 }); 39 } 40 } 41 } else { 42 objList = tdInfo.uuidObjList; 43 propList = tdInfo.uuidPropList; 44 depends = new Array(uuidList.length); 45 // declare depends assets 46 // 不支持延迟加载则直接进入depends数组,这里没有deferredLoadRaw标记 47 for (i = 0; i < uuidList.length; i++) { 48 dependUuid = uuidList[i]; 49 depends[i] = { 50 type: 'uuid', 51 uuid: dependUuid 52 }; 53 } 54 } 55 56 /******************************* tdInfo.rawProp和asset._preloadRawFiles的处理 **********************************/ 57 // declare raw 58 // 有些json文件包含了一些raw属性,以$_$rawType结尾,这里会直接加载item.url,但目前还未碰到过这样类型的资源。 59 // 下面2个说法是错误的。 60 // 如果这个uuid资源本身就是一个raw资源,加载自己? 61 // 如果这个uuid资源存在raw属性,例如一个脚本拖拽了一个Texture2D类型的资源作为它的成员变量? 62 if (tdInfo.rawProp) { 63 objList.push(asset); 64 propList.push(tdInfo.rawProp); 65 depends.push(item.url); 66 } 67 // preload raw files 68 // 预加载它的raw文件,这里是asset的属性,但从引擎代码没有看到哪里对这个属性赋值过 69 // 不过prefab等文件倒是有一个_rawFiles的属性,但从代码上看也与这个方法无关,看上去倒像是预留的一个接口 70 // 提供给开发者做某种资源类型的完成回调包装。 71 if (asset._preloadRawFiles) { 72 var finalCallback = callback; 73 callback = function () { 74 asset._preloadRawFiles(function (err) { 75 finalCallback(err || null, asset); 76 }); 77 }; 78 } 79 // fast path 80 // 如果没有资源要加载就直接返回 81 if (depends.length === 0) { 82 cc.deserialize.Details.pool.put(tdInfo); 83 return callback(null, asset); 84 } 85 86 /******************************* 调用pipeline.flowInDeps进行依赖加载,资源加载完成后调用loadCallback **********************************/ 87 // Predefine content for dependencies usage 88 // 加载depends,加载完成后注册到item.dependKeys中,并赋值给this.obj[this.prop] 89 item.content = asset; 90 pipeline.flowInDeps(item, depends, function (errors, items) { 91 // 这个回调在所有的item都加载完成后执行,所以item都是有的,但有可能有报错 92 var item, missingAssetReporter; 93 for (var src in items.map) { 94 item = items.map[src]; 95 if (item.uuid && item.content) { 96 item.content._uuid = item.uuid; 97 } 98 } 99 for (var i = 0; i < depends.length; i++) { 100 var dependSrc = depends[i].uuid; 101 var dependUrl = depends[i].url; 102 var dependObj = objList[i]; 103 var dependProp = propList[i]; 104 item = items.map[dependUrl]; 105 if (item) { 106 var thisOfLoadCallback = { 107 obj: dependObj, 108 prop: dependProp 109 }; 110 111 // 资源加载完成的回调,关联依赖对象obj的prop为item的value 112 function loadCallback (item) { 113 var value = item.isRawAsset ? item.rawUrl : item.content; 114 this.obj[this.prop] = value; 115 if (item.uuid !== asset._uuid && dependKeys.indexOf(item.id) < 0) { 116 dependKeys.push(item.id); 117 } 118 } 119 120 // 如果资源已经加载完了,且没有报错,则执行loadCallback回调 121 if (item.complete || item.content) { 122 if (item.error) { 123 if (CC_EDITOR && item.error.errorCode === 'db.NOTFOUND') { 124 if (!missingAssetReporter) { 125 var MissingObjectReporter = Editor.require('app://editor/page/scene-utils/missing-object-reporter'); 126 missingAssetReporter = new MissingObjectReporter(asset); 127 } 128 missingAssetReporter.stashByOwner(dependObj, dependProp, Editor.serialize.asAsset(dependSrc)); 129 } else { 130 cc._throw(item.error); 131 } 132 } else { 133 loadCallback.call(thisOfLoadCallback, item); 134 } 135 } else { 136 // item was removed from cache, but ready in pipeline actually 137 // 该item从cache中移除了?但在pipeline中? 138 // 这里监听了该item的加载完成事件,在加载完成时调用loadCallback 139 var queue = LoadingItems.getQueue(item); 140 // Hack to get a better behavior 141 // 这个behavior非常的bad,_callbackTable是CallbacksHandler的成员变量 142 // 两个操作都是添加监听,但前者是直接拿到监听该事件的回调数组,强行插入 143 var list = queue._callbackTable[dependSrc]; 144 if (list) { 145 list.unshift(loadCallback, thisOfLoadCallback); 146 } else { 147 queue.addListener(dependSrc, loadCallback, thisOfLoadCallback); 148 } 149 } 150 } 151 } 152 if (CC_EDITOR && missingAssetReporter) { 153 missingAssetReporter.reportByOwner(); 154 } 155 cc.deserialize.Details.pool.put(tdInfo); 156 callback(null, asset); 157 }); 158}

CCLoader的flowInDeps,实现如下,传入资源的owner,依赖列表urlList,以及urlList的回调。当一个依赖又有依赖的时候,queue的append又会走到这个新资源的loadUuid,去加载那一层所依赖的资源。而flowInDeps开头的var item = this._cache[res.url] 也确保了资源不会被重复加载。

1proto.flowInDeps = function (owner, urlList, callback) { 2 // 准备_sharedList,已加载或正在加载的资源push item,未加载的push res 3 _sharedList.length = 0; 4 for (var i = 0; i < urlList.length; ++i) { 5 var res = getResWithUrl(urlList[i]); 6 if (!res.url && ! res.uuid) 7 continue; 8 var item = this._cache[res.url]; 9 if (item) { 10 _sharedList.push(item); 11 } else { 12 _sharedList.push(res); 13 } 14 } 15 16 // 创建一个新的队列,当有owner时,将子队列的进度同步到ownerQueue 17 var queue = LoadingItems.create(this, owner ? function (completedCount, totalCount, item) { 18 if (this._ownerQueue && this._ownerQueue.onProgress) { 19 this._ownerQueue._childOnProgress(item); 20 } 21 } : null, function (errors, items) { 22 callback(errors, items); 23 // Clear deps because it's already done 24 // Each item will only flowInDeps once, so it's still safe here 25 // 加载完成后清除owner.deps数组 26 owner && owner.deps && (owner.deps.length = 0); 27 items.destroy(); 28 }); 29 if (owner) { 30 var ownerQueue = LoadingItems.getQueue(owner); 31 // Set the root ownerQueue, if no ownerQueue defined in ownerQueue, it's the root 32 // 设置queue的ownerQueue 33 queue._ownerQueue = ownerQueue._ownerQueue || ownerQueue; 34 } 35 var accepted = queue.append(_sharedList, owner); 36 _sharedList.length = 0; 37 return accepted; 38};
  • 延迟加载

  • 延迟加载的作用

在creator编辑器中可以设置场景和prefab的延迟加载,设置了延迟加载之后,场景或prefab所引用的一些Raw类型资源如cc.Texture2D、cc.AudioClip等会延迟加载,同时,玩家进入场景后可能会看到一些资源陆续显示出来,并且激活新界面时也可能会看到界面中的元素陆续显示出来,因此这种加载方式更适合网页游戏。

具体的实现是在loadUuid中执行canDeferredLoad时,它的asset.asyncLoadAssets为一个Object。在后面的loadDepends方法中会执行deferredLoadRawAssetsInRuntime的判断。所有Raw类型的资源会被延迟加载,而非Raw类型的资源会被添加到depends数组中进行加载。最终加载完成时我们可以得到一个不完整的资源(因为它有一部分依赖被延迟加载了)。

  • 延迟加载的资源在什么时候加载?

从整个Pipeline的加载流程来看,并没有任何地方去加载这些被延迟的Raw类型资源,而在底层加载图片的地方进行断点,可以发现当场景或Prefab被激活时(添加到场景中),会有一个ensureLoadTexture方法被调用,在这里会执行这些延迟资源的加载流程。所以延迟加载的资源在节点被激活时会自动加载。下图是一个延迟加载图片的调用堆栈。

image

ensureLoadTexture的实现如下所示,AudioClip也类似,在调用play播放声音时会执行preload,检测到声音没有被加载时会执行cc.loader.load方法加载声音。

1 /** 2 * !#en If a loading scene (or prefab) is marked as `asyncLoadAssets`, all the textures of the SpriteFrame which 3 * associated by user's custom Components in the scene, will not preload automatically. 4 * These textures will be load when Sprite component is going to render the SpriteFrames. 5 * You can call this method if you want to load the texture early. 6 * !#zh 当加载中的场景或 Prefab 被标记为 `asyncLoadAssets` 时,用户在场景中由自定义组件关联到的所有 SpriteFrame 的贴图都不会被提前加载。 7 * 只有当 Sprite 组件要渲染这些 SpriteFrame 时,才会检查贴图是否加载。如果你希望加载过程提前,你可以手工调用这个方法。 8 */ 9 ensureLoadTexture: function () { 10 if (!this._texture) { 11 this._loadTexture(); 12 } 13 }, 14 15 _loadTexture: function () { 16 if (this._textureFilename) { 17 // 这里返回的tex可能是一个未加载完成的纹理,如纹理未加载完成,可监听其加载完成回调 18 var texture = cc.textureCache.addImage(this._textureFilename); 19 this._refreshTexture(texture); 20 } 21 }, 22 23 _refreshTexture: function (texture) { 24 var self = this; 25 if (self._texture !== texture) { 26 var locLoaded = texture.loaded; 27 this._textureLoaded = locLoaded; 28 this._texture = texture; 29 function textureLoadedCallback () { 30 if (!self._texture) { 31 // clearTexture called while loading texture... 32 // 在加载纹理的时候调用了clearTexture方法 33 return; 34 } 35 self._textureLoaded = true; 36 var w = texture.width, h = texture.height; 37 // 如果在Canvas模式下,图片有旋转,需要进行旋转的特殊处理(_cutRotateImageToCanvas) 38 if (self._rotated && cc._renderType === cc.game.RENDER_TYPE_CANVAS) { 39 var tempElement = texture.getHtmlElementObj(); 40 tempElement = _ccsg.Sprite.CanvasRenderCmd._cutRotateImageToCanvas(tempElement, self.getRect()); 41 var tempTexture = new cc.Texture2D(); 42 tempTexture.initWithElement(tempElement); 43 tempTexture.handleLoadedTexture(); 44 self._texture = tempTexture; 45 self._rotated = false; 46 w = self._texture.width; 47 h = self._texture.height; 48 self.setRect(cc.rect(0, 0, w, h)); 49 } 50 51 if (self._rect) { 52 self._checkRect(self._texture); 53 } else { 54 self.setRect(cc.rect(0, 0, w, h)); 55 } 56 57 if (!self._originalSize) { 58 self.setOriginalSize(cc.size(w, h)); 59 } 60 61 if (!self._offset) { 62 self.setOffset(cc.v2(0, 0)); 63 } 64 65 // dispatch 'load' event of cc.SpriteFrame 66 // cc.SpriteFrame的触发load事件 67 self.emit("load"); 68 } 69 70 // 如果图片已加载完,则直接执行回调,否则监听texture的load方法 71 if (locLoaded) { 72 textureLoadedCallback(); 73 } else { 74 texture.once("load", textureLoadedCallback); 75 } 76 } 77 },
  • 禁止延迟加载

在Creator的官方文档中介绍到“Spine 和 TiledMap 依赖的资源永远都不会被延迟加载”,这主要是因为它们对Raw资源是一个强依赖,也就是说节点被激活时就必须使用到它们的纹理,所以不能延迟加载。那么它们是如何实现禁止延迟加载的呢?

在canDeferredLoad方法中,如果资源的asset.constructor.preventDeferredLoadDependents为true时,会强制返回false。在引擎中进行搜索可以发现,除了Spine和TiledMap,还有DragonBones也是被禁止延迟加载的。

image

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

springboot的起步依赖

!(https://oscimg.oschina.net/oscnet/f3acbe4cf3b00c68207e091c172d6b45a27.png)加载自动配置的方式2:!(https://oscimg.oschina.net/oscnet/40341228c10f7a56d82323a1d622521d92d.png) spring

KVM调整cpu和内存

一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid