资讯动态

Unity AssetBundle 打包加载与热更新避坑指南

发布时间:2026/10/6 10:01:23 来源:尧图企业网站定制
AssetBundle 这东西刚接触 Unity 的人听到就头疼做热更新的团队又绕不开它。我在项目里和 AB 纠缠了三年从最开始把整个游戏打进一个包到后来按模块拆得干干净净中间踩过的坑不比写的代码少。这篇笔记我尽量把 AssetBundle 从打包到加载、从依赖管理到版本更新讲透包含可以直接抄的代码以及一堆文档里不会写、只有实际跑过才会踩到的坑。先说说这篇笔记适合谁看刚接触 Unity、想搞懂 AB 是什么的新手正在做热更新、不知道资源怎么拆分的初级开发者以及被 AB 内存问题折腾得想砸电脑的中级开发者。如果你已经是把 AB 玩出花来的老手可以直接跳到第 6 章的避坑清单那里有些问题可能你也没遇到过。1. 先搞懂 AssetBundle 解决什么问题1.1 什么时候该上 AssetBundleAssetBundle 的核心作用只有一个把资源按需加载。它允许你把模型、贴图、音频、预制体、甚至 TextAsset 打包成二进制文件放到本地或远端服务器运行时再去加载。但这不是说所有项目都得用 AB。我做过的几个项目里只有两种场景是真的需要它第一种是游戏体量太大首包扛不住。比如一个 MMORPG角色模型、场景贴图、动作文件加起来动辄几个 G如果全塞在首包里用户下载时早就流失了。这时候把低频资源打进 AB放在 CDN 上玩家跑到哪个地图才下哪个地图的资源。第二种是业务需要热更新。注意热更新不只是改代码很多时候运营活动要换 UI 皮肤、调数值表、加新角色如果这些资源打进了安装包就只能发版。用 AB 把资源从代码里剥离出来更新时只下发资源包玩家重启游戏就能看到新内容。如果你做的只是个单机小游戏资源总量撑死 200MB那真的没必要上 AB。Unity 的 Addressables 和 Resources 系统对这种体量完全够用。AB 引入的依赖管理、版本控制、加载顺序问题会让你多写不少代码而这些代码本身也是有维护成本的。1.2 别一上来就 AB三种替代方案对比我最常被问的一个问题是“我的项目要不要用 AB”我的回答一般是先看看你能不能接受另外三个方案。Resources 文件夹是最省事的方案。直接把资源丢进去Resources.Load()就能加载。好处是零配置坏处是 Resources 里的所有东西都会被打进安装包而且一次加载全部内存压力大。适合小项目、原型验证、或者那种永远不打算更新的单机游戏。Addressables 是官方在 AB 之上的封装面向对象变成了资源地址而不是 Bundle 文件。它帮你解决了依赖分析、远程加载、内存释放一大堆问题但学习曲线比 AB 陡不少而且底层还是 AB。如果你的项目是 2019.4 以上的版本团队又愿意投入学习成本Addressables 其实是比直接写 AB 更明智的选择。只是我见过不少团队被 Addressables 的黑盒坑过——出了问题根本不知道底层发生了什么。AssetBundle 裸用则是“全部自己来”。好处是完全可控打包粒度、加载时机、生命周期全在掌握中坏处是必须自己处理依赖、变体、缓存失效这些琐碎又不容出错的事。我个人的定位是如果你搞懂了裸 AB再去看 Addressables你会理解它每一步在做什么但如果你只会 Addressables出了底层的坑你会非常被动。1.3 热更新与 AssetBundle 的核心关系很多人一提到热更新就以为只有 Lua这其实是个误区。代码层面确实要用 Lua 或 ILRuntime 这类脚本方案但资源层面靠的就是 AssetBundle。完整的思路是这样游戏安装包里只放必要的启动资源和一段可更新的引导代码其余所有资源都在远端服务器上。启动时客户端先去请求一个版本配置对比服务器资源和本地缓存的差异把需要更新的 AB 下载下来然后挂载到 AssetBundle 管理器中。这样一来换皮活动、新角色、新地图全都变成了下发资源不需要经过应用商店审核。但这里有个隐藏前提代码和资源的版本必须同步。比如服务器端加了个新角色这个角色的预制体引用了某个新脚本而这个脚本是通过热更代码打进去的那资源包和代码包就必须同时更新不能只下资源。这个约束我在第 5 章讲版本管理时会详细展开。2. 打包前的资源规划与目录规范2.1 资源打组粒度怎么选这是 AB 里面分歧最大的问题。打组太粗一个包几百 MB加载时浪费内存打组太细几百个依赖文件管理起来想死。我在多个项目里调下来总结出了四条原则原则一同生命周期、同使用场景的资源放一起。一个关卡的场景模型、贴图、光照数据它们总是在同一个时刻被加载和卸载放一个包里是合理的。反之主菜单的资源和战斗场景的资源尽量不要混否则你点进主菜单也得把整个战斗包的内存占上。原则二高频共享资源单独成包。像 UI 通用的图集、通用 Shader、通用材质球这些资源被很多包引用必须独立打一个包。否则每个包都塞一份自己的 Shader打包时会自动拷贝副本导致几十 MB 的冗余。原则三一个资源不允许放入多个 AB 包。这是 Unity 的硬性规则同一资源被两个 Bundle 同时引用打包会直接报错或者产生不可控的依赖。这个规矩在目录结构设计时必须提前想清楚否则后续改打包脚本会非常痛苦。原则四粒度要服务于加载粒度。你加载 AB 的最小单位是 Bundle不是单个资源。所以你要想清楚玩家在哪个场景加载哪几个 Bundle卸载哪些 Bundle。如果每个单模型一个包加载一个关卡可能要拉几十个请求这个下载开销在弱网环境下是灾难级的。一个比较稳的粒度参考按功能模块划分。比如ui_commonsUI 共用、ui_mainmenu、ui_battle、scene_city_01、characters_players、characters_monsters、effect_commons。每个包控制在 15~50MB 之间比较合适既不会太大导致加载过慢也不会太小导致请求过多。2.2 目录结构与命名约定目录结构直接影响打包脚本的复杂度。我推荐用一个清晰的映射关系Assets 根目录下建一个Bundles文件夹下面是你要分组的子目录每个子目录下再放具体资源打包时遍历这个目录结构每个子目录生成一个 BundleBundle 名就是子目录名举例Assets/ ├── Bundles/ │ ├── ui_commons/ │ │ ├── bg_common.png │ │ └── btn_common.png │ ├── scene_city_01/ │ │ ├── map_01.fbx │ │ └── tex_ground.png我在项目里给 Bundle 命名都用了小写下划线一方面是兼容性问题另一方面是代码里传参时更统一。注意Bundle 名里的反斜杠\会被转成/这在某些平台上有坑我会在第 6 章细说。2.3 变体Variant一套资源多种表现AB 变体允许你维护同一套资源的多种版本比如高清贴图和标清贴图、简体中文文案和繁体中文文案。打包时通过AssetBundleBuild的assetBundleVariant字段指定变体名加载时指定具体变体。变体的坑在于依赖关系会翻倍。比如你的 UI 图集引用了同一个字体资源这个字体如果做了中文和英文两个变体那所有引用它的包都得跟着区分变体。忽略依赖时 Unity 默认加载第一个变体这个默认行为经常导致资源错乱。我建议能用简单配置解决的问题不要上变体。高清和标清贴图我一般用 AssetBundle 的内置压缩选项做区分而不是做两个 AB多语言文案我一般打到 TextAsset 里按语言版本切换文字而不是切 AB。变体功能存在的意义主要是极端场景——比如同一个资源在不同平台有完全不同的纹理压缩格式这种情况我一般直接打成两套包用平台目录区分也不走变体。3. 打包代码与管线配置3.1 构建API与关键参数Unity 的打包入口是BuildPipeline.BuildAssetBundles它挂在编辑器脚本里平时不参与游戏运行。一个最基础的调用BuildPipeline.BuildAssetBundles(outputPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);这条命令会把当前工程里手动设置了 AssetBundle 名的资源按名字打成包输出到指定目录。这种方式适合资源不多的小项目但它有个致命缺点你必须手动给每个资源设置 AB 名而且没有外围的目录逻辑全凭口头约定后期容易乱。所以我更推荐做好目录约定后用AssetBundleBuild数组来打包var builds new AssetBundleBuild[] { new AssetBundleBuild { assetBundleName ui_commons, assetNames new[] { Assets/Bundles/ui_commons/bg_common.png } } }; BuildPipeline.BuildAssetBundles(outputPath, builds, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);AssetBundleBuild的好处是打包逻辑和资源实际目录是两套体系——你可以在代码里筛选资源再决定它进哪个包。3.2 完整打包脚本编辑器菜单下面这个脚本是我项目里一直在用的逻辑是扫描Assets/Bundles下的每个子目录每个目录打成一个包生成清单文件并支持增量构建和清理输出目录。using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; public static class BundleBuilder { private const string SourceRoot Assets/Bundles; private const string OutputRoot BundlesOutput; [MenuItem(Tools/Build Bundles/Windows)] public static void BuildForWindows() { BuildBundle(BuildTarget.StandaloneWindows64); } [MenuItem(Tools/Build Bundles/Android)] public static void BuildForAndroid() { BuildBundle(BuildTarget.Android); } private static void BuildBundle(BuildTarget target) { string outputPath Path.Combine(OutputRoot, target.ToString()); if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } // 清理旧产物避免残留文件混入新包 var files Directory.GetFiles(outputPath); foreach (var f in files) { File.Delete(f); } ListAssetBundleBuild builds new ListAssetBundleBuild(); var dirs Directory.GetDirectories(SourceRoot); foreach (var dir in dirs) { string dirName Path.GetFileName(dir); string[] assetPaths Directory.GetFiles(dir, *.*, SearchOption.AllDirectories); var filtered new Liststring(); foreach (var asset in assetPaths) { // 跳过 .meta 文件 if (asset.EndsWith(.meta)) continue; filtered.Add(asset.Replace(\\, /)); } if (filtered.Count 0) continue; builds.Add(new AssetBundleBuild { assetBundleName dirName, assetNames filtered.ToArray() }); } var manifest BuildPipeline.BuildAssetBundles(outputPath, builds.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.DeterministicAssetBundle, target); if (manifest null) { Debug.LogError(Bundle 构建失败); return; } // 记录构建信息方便版本对比 File.WriteAllText(Path.Combine(outputPath, build_info.txt), $BuildTime{System.DateTime.Now:yyyy-MM-dd HH:mm:ss}\nTarget{target}\nBundleCount{manifest.GetAllAssetBundles().Length}\nBuildHash{manifest.GetAssetBundleHash(ui_commons)}); } }几个关键点说明一下其一清理旧产物这步必须有。AB 构建是增量式的如果你改了资源名或者删除了资源旧文件不会自动消失会残留在输出目录里。如果这些残留文件被上传到 CDN客户端拉版本清单时可能会拉到旧文件。其二ChunkBasedCompression是 LZ4 压缩适合本地加载和需要快速访问的场景BuildAssetBundleOptions.None对应 LZMA 压缩包体更小但每次加载要整包解压慢。Android/iOS 我一般用 LZ4因为闪存读取速度和内存解压开销比包体大那么 10MB 更让人在意。其三DeterministicAssetBundle保证同一份资源在任何机器、任何时间构建出的 Hash 一致。这个对增量更新非常重要——如果你不开启这项两个程序员各自的电脑打出完全相同的资源Hash 不同玩家每次都白重新下载。3.3 构建选项对照表选项压缩方式加载速度包体大小适用场景NoneLZMA慢整包解压最小冷门资源、一次性下载ChunkBasedCompressionLZ4快按需解压较大频繁加载的 UI、场景UncompressedAssetBundle无压缩最快最大本地方案、开发调试还有两个选项容易被忽略DisableWriteTypeTree关闭 UI 类型信息包体略小但加载时对类型匹配要求更严格老版本 Player 加载新包可能类型解析失败。IgnoreTypeTreeChanges忽略类型树变更检测可以做增量打包但如果你改了脚本的字段结构就会导致 AB 加载异常。这个选项和热更新放在一起几乎是定时炸弹我建议慎用。我个人最常用的组合是ChunkBasedCompression | DeterministicAssetBundle然后在需要下载型的小资源上用NoneLZMA在本地直接加载的巨大场景上用UncompressedAssetBundle。4. 加载与卸载内存管理的核心4.1 三种加载方式怎么选Unity 提供了三种主流加载方式AssetBundle.LoadFromFile、UnityWebRequestAssetBundle.GetAssetBundle、AssetBundle.LoadFromMemory。LoadFromFile是本地加载用的路径指向本地磁盘上的 AB 文件。它不会把整个文件读进内存而是从文件系统直接映射加载速度快内存占用小。这条 API 只能在 AB 文件确实存在本地时用。在编辑器里调试时我经常直接用它加载输出目录里的文件。AssetBundle bundle AssetBundle.LoadFromFile(Path.Combine(localPath, ui_commons));UnityWebRequestAssetBundle.GetAssetBundle是远程加载用的向 CDN 发 HTTP 请求下载完自动缓存到本地磁盘之后再加载走官方缓存。这条 API 支持断点续传Unity 内部做了缓存管理不用自己造轮子。string url https://your-cdn-host/bundles/ bundleName; UnityWebRequest request UnityWebRequestAssetBundle.GetAssetBundle(url); await request.SendWebRequest(); AssetBundle bundle DownloadHandlerAssetBundle.GetContent(request);LoadFromMemory我就不推荐了它把整个 AB 的二进制数据拷进内存再解析体验差容易内存暴涨。除非你从服务器端自定义加密、拿到的是解密后的字节数组才考虑用它。4.2 LoadAsset 的类型坑与异步加载拿到AssetBundle之后加载资源就简单了GameObject prefab bundle.LoadAssetGameObject(Assets/Bundles/characters_players/player_01.prefab); Instantiate(prefab);但这里藏着几个坑。坑一是资源名必须完整。AB 包里的资源名是Assets/...的完整路径不是文件名。如果你打包时用了AssetBundleBuild的assetNames那加载时就必须用原来的完整路径。用bundle.GetAllAssetNames()可以查看包内所有资源路径。坑二是类型匹配。LoadAssetT要求 T 和资源实际类型一致比如对预制体要用GameObject对贴图要用Texture2D对音频要用AudioClip。如果你不记得类型可以用无泛型的LoadAsset拿到Object再判断类型。Object obj bundle.LoadAsset(assetName); if (obj is GameObject go) { Instantiate(go); } else if (obj is Texture2D tex) { // use tex }异步加载一直是新手容易迷茫的地方。Unity 提供了LoadAssetAsync但它的异步回调本质是等待资源从磁盘读取完成。在 LZ4 压缩下这个异步效果往往不明显因为真正耗时的是实例化阶段而不是 AB 读取。所以我的建议是AB 加载本身用异步尤其远程下载阶段资源加载完拿到 Asset 后实例化走同步或者自己控制帧率反而是最稳的。4.3 Unload(false/true) 到底怎么用这是 AB 最经典的内存问题也是被踩得最惨的坑。bundle.Unload(true)会卸载 AB 的内存镜像同时把它加载出来的所有资源也一起卸载。如果你持有这些资源的引用比如一个GameObject还在场景里突然资源被卸载你就会看到紫红色的模型甚至直接报 MissingReferenceException。bundle.Unload(false)只卸载 AB 的内存镜像已经加载出来的资源不会自动销毁。这样你还在用的资源能继续用但等下不再需要它们时必须手动Destroy或让 GC 回收。这里的根本矛盾是你不知道一个资源是否还被外部引用着。所以千万别随意用Unload(true)。我项目的做法是让 AB 管理器记录每个 Bundle 的引用计数加载时 1实例销毁时 -1计数归零后才调用Unload(true)。private Dictionarystring, int _refCount new Dictionarystring, int(); public void Retain(string bundleName) { if (!_refCount.ContainsKey(bundleName)) _refCount[bundleName] 0; _refCount[bundleName]; } public void Release(string bundleName) { if (_refCount.ContainsKey(bundleName)) { _refCount[bundleName]--; if (_refCount[bundleName] 0) { _refCount.Remove(bundleName); AssetBundle bundle GetLoadedBundle(bundleName); bundle?.Unload(true); } } }用这套引用计数的前提是你的资源加载和释放入口是统一收口的不能出现绕过管理器直接Instantiate资源的情况。项目里前几个版本没做好收口漏了几个Release内存一直降不下来查了很久才定位到是一部分特效系统没走统一管理。5. 依赖、Manifest 与增量更新5.1 Manifest 是什么怎么读打包输出目录里会有一个和目录同名的 Manifest 文件比如BundlesOutput/Windows/Windows.manifest。这个文件记录了所有 Bundle 的 Hash、CRC以及每个 Bundle 依赖了哪些其他 Bundle。日常开发中你的 AB 包之间必然有依赖。比如scene_city_01包里的地面材质引用了ui_commons包里的某张贴图那么你加载scene_city_01之前必须先加载ui_commons否则材质球会是粉色的。Unity 提供了一个对象来读这份 ManifestAssetBundle manifestBundle AssetBundle.LoadFromFile(Path.Combine(outputPath, Windows)); AssetBundleManifest manifest manifestBundle.LoadAssetAssetBundleManifest(AssetBundleManifest); string[] deps manifest.GetAllDependencies(scene_city_01); foreach (string dep in deps) { // 先加载依赖 AssetBundle depBundle AssetBundle.LoadFromFile(Path.Combine(outputPath, dep)); }注意GetAllDependencies返回的是递归依赖列表不只是直接依赖。用GetDirectDependencies可以只拿直接依赖但模块多、链路复杂时直接用递归结果反而省事。依赖的管理是整个 AB 体系最容易崩的一环。我之前在项目里碰到一种情况某个 UI 包依赖了一个 Shader 包但加载 UI 包时没加载 Shader 包导致所有 UI 按钮的默认材质变成粉色。排查了两天最后发现那个 Shader 包是后来又新增的之前依赖列表里没有它而旧客户端的加载顺序调用了旧列表。5.2 版本管理与下载策略版本管理不只是比对 Hash。我设计了一套非常廉价但有效的方案服务端维护一个version.json内容是每个 Bundle 名到版本号的映射。{ version: 3.2.1, bundles: { ui_commons: 20250118_01, scene_city_01: 20250118_02, characters_players: 20250115_03 } }客户端启动时请求这个 JSON和本地缓存的版本列表做比对只下载版本号不一致的包。版本号是一个子版本号加发布时间这样不会误伤——同一份资源没被修改它的版本号就不会变下载量就能控制住。下载完成后用Caching系统或者自建缓存目录存放 AB 文件。自建缓存比Caching更可控尤其你要做加密或自定义存储路径时我一般自建Application.persistentDataPath/bundles目录配合本地版本 JSON 做校验。5.3 增量打包什么变了才打什么BuildAssetBundles本身就带增量能力如果你不清理输出目录只改了部分资源重跑一次打包脚本Unity 会沿用旧的未变更 Bundle 的 Hash只重新生成有改动的那部分。但要注意这个增量能力不是绝对的。以下几类改动会导致连带关联包一并重打Shader 的改动几乎所有引用它的材质、模型包都会变一个被大量资源依赖的公共 Prefab 改动版本升级时修改了打包配置选项比如压缩方式、是否开启DeterministicAssetBundle。我踩过最狠的一个坑是由于改了公共 UI 图集里的一张图导致所有 UI 相关 Bundle 全部重打版本号全部变化玩家被迫下载了几百 MB。后来我强制要求公共资源尽量独立成包别让一个 UI 包依赖另一个 UI 包的内部文件换图集就是换整个 Bundle。在增量更新这个层面上你越把高频变化的东西剥离成独立包下载量就越小。6. 避坑指南我踩过的那些 AB 坑6.1 资源重复与冗余资源重复的根源是同一个资源被多个 Bundle 引用。Unity 打包时如果某个资源没有成为任何 Bundle 的显式成员但被多个 Bundle 的显式成员引用它会被拷贝进每个引用它的 Bundle 里或者被打进依赖的公共 Bundle——具体取决于引用链。结果是 AB 打完后体积远超预期。排查方法很简单构建完成后写个小工具检查每个 Bundle 里是否有重复的 Asset。或者直接用 Unity 自带的 Asset Bundle Browser 工具查看每个包的内容。更省事的做法是设计阶段就避免贴图、Shader、图集这种被高频引用的资源独立成包不要让它们散落在各个业务包里。6.2 Shader 打包问题Shader 不打包进去几乎是每个 AB 新手都会踩的坑。Unity 在 Build 时会裁剪未使用的 Shader而 AB 里的材质引用的 Shader 如果被裁剪或者 Shader 在场景里没有直接出现运行时那个材质就是紫红色。解决办法把要用到的 Shader 统统放入Always Included Shaders图形设置里或者把 Shader 单独打成 AB 包在加载任何材质前先加载 Shader 包。我强烈推荐后者因为把 Shader 塞进Always Included会导致首包体积爆炸。Standard和Uber这类内建 Shader 尤其要小心。不加处理时新工程只打一个包含标准材质的 AB到手机上就会出现“紫红屏”因为移动端没有标准 Shader。6.3 平台差异AB 是跨平台的但跨平台加载资源能不能用取决于你打包时的 BuildTarget。你在 Windows 上打的 ABAndroid 上加载大概率会报错或者出现渲染异常。解决方案是每个平台独立出包、独立打 AB。我在项目里的做法是输出目录按平台分文件夹Windows/、Android/、iOS/每个平台的版本清单也是独立的。切平台时打包脚本自动锁定对应输出目录避免新旧平台文件混在一起。还有个细节Android 的 AssetBundle 文件后缀名没有任何要求但从 CDN 下载时要注意 MIME 类型。如果服务器把 AB 文件当作二进制流某些 Android 机型下载时会产生格式检测失败。建议 AB 后缀统一用.unity3d或.bundle并确认服务器返回的Content-Type是application/octet-stream。6.4 热更中常见的几个坑热更新里最邪门的问题是资源与代码版本错位。我经历过一次服务端下发了新角色资源但客户端热更代码还没生效玩家的角色列表里压根找不到新角色于是资源一直躺在缓存里占空间。后来加了一个统一入口所有远端资源必须先经过版本号校验版本号不符合就丢弃下次启动再拉。另一个坑是下载中断导致的 AB 文件损坏。即使 UnityWebRequest 自带缓存半路断网也可能留下不完整的缓存。我的做法是每次加载 AB 前做一次 CRC 校验用的是AssetBundleManifest.GetAssetBundleHash比对服务器下发的 Hash。这个小成本换来的稳定性在线上项目里非常重要。还有一个比较隐蔽的坑文件时间戳和 Hash 不匹配。有同事为了省时间手动把服务器上的文件拷到本地结果时间戳变了但内容没变客户端版本比对只比了时间戳导致每次都重新下载。我这里统一按 Hash 比对不用时间戳。6.5 编辑器调试的辅助方法在编辑器里切换平台、调试 AB 加载有个很实用的技巧利用AssetDatabase绕过实际 Load直接加载源资源。开发期不走 AB能大幅提升迭代效率。比如#if UNITY_EDITOR string assetPath Assets/Bundles/ui_commons/bg_common.png; Texture2D tex AssetDatabase.LoadAssetAtPathTexture2D(assetPath); #else AssetBundle bundle BundleManager.Instance.Load(ui_commons); Texture2D tex bundle.LoadAssetTexture2D(Assets/Bundles/ui_commons/bg_common.png); #endif这样你平时开发不用打 AB代码和真实打包运行时走的是同一套逻辑只是底层的资源来源不同。发布时切到真实 AB 加载即可。但要注意这种“编辑器特供”的路径很容易和生产环境行为不一致。比如编辑器里永远能加载到某个资源而真机上报 Missing因为你压根没把它打进去。我经常用这种对比定位问题写一个自动化检查遍历所有被打进 AB 的资源路径再遍历代码里所有LoadAsset调用把和 AB 内路径对不上的找出来。7. 经验收尾资源管理这件事急不得在我做的几个项目里AB 从来不是单独存在的技术点它跟资源规范、更新策略、团队协作方式强绑定。一开始直接上 AB 的项目到后面几乎都得返工反而是先花时间定好目录结构、命名规范、加载收口再动手写打包脚本的一路走得稳。我个人的体会是AssetBundle 用起来其实不复杂真正的复杂度都在组织与管理上。你越指望它救急、越随意打组后面的坑就越多。如果团队里每个人都遵循同一套规则AB 可以做到像 Resources 一样简单同时又拥有热更的能力。最后分享一个我从老项目里带过来的小技巧每次打包完成之后把 AB 的 Hash 名单生成一份并提交到版本库。这样一旦线上出现问题你可以精确还原出是“哪次打包导致的哪个包变化”排查效率能提升好几倍。配合构建机自动执行打包整个流程的稳定性会比你手动切来切去高很多。AssetBundle 这条路没有捷径把内存、依赖、版本这三个核心问题一个一个解决掉项目稳定之后你会觉得它其实也没那么可怕。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑