17.总结
17.1 知识点

主要讲解内容

完成目标

主要使用的知识点

实践优化

17.2 核心要点速览
编辑器上传与客户端增量更新
编辑器侧(有资源变更时)
- 打完 AB 后,把 AB 包 和 资源对比文件 一起上传到资源服务器;远端以对比文件描述「当前应有哪些 AB、各自版本或校验信息如何」。
客户端侧(每次启动流程的常见骨架)
- 先向服务器要 最新的资源对比文件。
- 和 本地 已有的对比文件做差异分析:哪些 AB 缺失、哪些需要被新版本替换,形成待办列表。
- 按列表下载缺的 / 过期的 AB,全部落盘并视为成功之后,再 回写本地资源对比文件,然后进入正式游戏逻辑。
这样做的直接好处是 增量:不必每次全量拖整块资源;同时 先资源、后清单 的顺序能减少「清单已写新、磁盘上 AB 还是旧的」这种半截子状态——中途下失败了,本地还不会谎报自己已经对齐远端。
上手本系列前建议自带的基础
- 下面表格只列「你最好已经会什么」,减少对着长文补前置时的心算成本。
- 某一格薄弱,优先补 C# IO + Unity 异步,再啃 FTP 细节。
| 方向 | 建议已掌握的内容 |
|---|---|
| 语言与引擎 | C#、Unity 常规开发;热更路线里若走过 Lua 方案,对 AB 在整个架构里的位置会更清晰 |
| 本地 IO | 读写持久化目录、保存对比文件与 AB 落盘路径 |
| 异步与回调 | 网络请求不要卡死主线程,async / 协程等按你项目习惯选用 |
| 网络 | FTP、HTTP 上传下载思路;Unity 里常用 UnityWebRequest 等 |
准备 AB 与 AssetBundles-Browser
- 工程里用 AssetBundles-Browser-master 做资源划分与打 AB;与当前 Unity 版本不兼容时,按包说明处理(例如去掉仅用于测试的目录),避免一进项目就报错。
- 打哪些资源、拆几个包,仍要服从你自己项目的资源规范;这一组课的目标是:本地稳定产出 某一目录下的 AB,供后面生成对比文件和上传。
资源对比文件:MD5 在清单里的角色
- 光看文件名 无法知道文件内容有没有悄悄改过;光看大小 也不够稳妥(长度撞车虽少见,但毕竟不是内容校验)。所以正文选 MD5:同一文件每次算出来一致,内容只要动哪怕一个字节,摘要就变,适合做「要不要重新下这个 AB」的判断依据。
- 落地步骤和课内代码一致:
FileStream把文件以流方式打开 →MD5CryptoServiceProvider的ComputeHash得到 16 个字节 → 按字节转 小写十六进制(ToString("x2"))拼成 32 位字符串。
编辑器里生成 ABCompareInfo.txt
- 只在 Unity 编辑器 用的工具函数,放进
Editor文件夹(或 Editor 程序集),不会进玩家包。 - 典型流程:
[MenuItem]点菜单 → 指向课内约定的输出目录(例如Application.dataPath + "/ArtRes/AB/PC/")→DirectoryInfo.GetFiles()→ 过滤 扩展名为空 的条目当作 AB 文件 → 每个 AB 追加一条记录:文件名 文件长度 MD5,记录之间用|串进同一段文本 → 去掉**末尾多余的那一个|**(字符串为空时不要裁剪,需先判断长度大于 0)→ 写入ABCompareInfo.txt→AssetDatabase.Refresh()。 - 客户端以后要 按同一套分隔符和字段顺序 解析;若文件名或将来扩展字段里出现空格、
|,课内这种纯拼接格式就要升级成更稳的编码(例如 JSON)——这是正文格式隐含的限制。
FTP 资源服务器与编辑器上传
- 课内用 Serv-U 等软件搭 FTP:建域、单向加密、上传用账号密码、下载可开匿名等。也强调了一句现实分工:正式环境经常是后端 / 运维先把 FTP(或其它存储)搭好,你再拿主机、路径、账号去传。
- 上传侧用
FtpWebRequest:Method设为WebRequestMethods.Ftp.UploadFile,UseBinary = true,配好Credentials,Proxy = null,GetRequestStream()拿到上传流后,从本地FileStream按块读(课内 2048)往里写。 - 远端 URI 形如
ftp://127.0.0.1/AB/PC/+ 文件名;FTP 服务端要先存在AB/PC目录,否则容易直接失败(不会像某些 HTTP API 那样自动替你建多级路径)。 - 批量上传:课内对目录里 无后缀 AB 和
.txt对比文件 逐个调用上传;实现上常用Task.Run+async void「发完就走」,多场 FTP 会 并行 起来——要不要限流、要不要等全部完成再打日志,是工程上要补的约束。
上传链路的落地顺序
- 新建或打开工程,导入 AssetBundles-Browser,把测试资源打进固定输出目录(课内为
ArtRes/AB/PC)。 - 菜单 AB包工具 / 创建对比文件 扫描该目录,为每个无扩展名 AB 写 MD5,生成
ABCompareInfo.txt。 - 在 FTP 侧建好
AB/PC(或与 URI 一致的路径);生产环境通常由运维给主机、账号、权限,课内仅演示 Serv-U 一类搭建流程。 - 菜单 AB包工具 / 上传AB包和对比文件 把无后缀 AB 与
.txt一并UploadFile到远端;上传前把账号密码、根 URL 抽到配置,别长期写死在脚本里。
客户端:FTP 下载对比文件与远端字典
- 与上传对称:
FtpWebRequest,Method用WebRequestMethods.Ftp.DownloadFile,UseBinary = true,Credentials/Proxy/KeepAlive配置方式同类;GetResponse()拿到FtpWebResponse,GetResponseStream()读网侧流,按块 写入File.Create(localPath)(课内缓冲 2048)。 - 课内把
ABCompareInfo.txt落到Application.persistentDataPath再ReadAllText;后续 AB 也用persistentDataPath + "/" + 包名,和包内只读目录区分开,方便热更写盘与跨版本保留。 - 解析协议与编辑器生成端一致:
Split('|')拆记录,每条再按空格拆成 包名、大小、MD5 三段,long.Parse大小后进Dictionary<string, AssetBundleInfo>。分隔符或字段里若出现空格,这种手写格式就会翻车,成熟项目多半换 JSON / 二进制表。
AssetBundleInfo 与相等语义
- 模型三个字段:包名、
size、md5;==/!=/Equals/GetHashCode都围绕 md5,含义是「内容是否同一版」,不是比显示名。 - 自定义相等时
GetHashCode跟Equals要一致,后面才能把条目放进依赖哈希的容器或做 LINQ 去重而不踩坑。
批量异步下载 AB 与重试壳
DownLoadFile改成 bool:FTP 整段包在try里,成功return true,异常打日志并false。DownLoadABFile为async void:先把remoteAssetBundleInfoDictionary里所有 key 推进待下列表(本段示例等价「远端清单里列出的 AB 全量下载」;与本地对比后只下差集的做法在后续资源更新流程里再接上)。- 结构是 外层 while 管「轮数」(课内
reDownLoadMaxNum从 5 递减),内层 for 管当前列表里每一项;每项await Task.Run(() => { isOver = DownLoadFile(...); }),成功则推进度回调并把名字塞进 tempList,内层结束再统一从列表 Remove;失败的条目留在表里等下一轮。 - 收尾:
overCallBack(assetBundleDownLoadList.Count == 0),列表非空表示仍有失败项且重试用尽或需要你上层另做策略。
练习要求的调用顺序
- 对比文件:异步下载 + 最多重试,
UnityAction<bool>为 true 再ReadAllText临时文件(如ABCompareInfo_TMP.txt)并GetABCompareFileInfoToDictionary填远端字典;不要在未确认下载成功时解析正式清单或半截文件。 - 再
DownLoadABFile,全程成功后再进游戏逻辑;失败分支留给 UI / 重试按钮,这一点和概述里「清单可信再动资源」一回事。
下载侧落地顺序
- FTP 下载
ABCompareInfo.txt到持久化目录,可加重试。 - 成功后解析进远端字典。
- 再跑批量
DownLoadABFile;已做本地远端对比时,待下列表应只含需要更新的包名。
编辑器:默认 AB 进包与清单格式
- 菜单 AB包工具 / 移动选中资源到StreamingAssets中 实际是
AssetDatabase.CopyAsset:选中项经Selection.GetFiltered(..., DeepAssets)拿出,AssetDatabase.GetAssetPath取工程内路径,从最后一个/切出文件名。 - 无扩展名才当作 AB:文件名里出现
.就continue;更稳的是正文提到的FileInfo.Extension,避免「无后缀但名里带点」误判。 - 拷贝目标为
Assets/StreamingAssets+ 文件名;对每个已拷贝文件用 **FileInfo取Name、Length**,再配合CreateABCompare.GetMD5(FullName),拼成包名 大小 MD5,多条之间用|,去掉末尾多余分隔后写入StreamingAssets/ABCompareInfo.txt,最后AssetDatabase.Refresh。 |+ 空格三分段 与编辑器生成 FTP 清单、客户端解析 必须一致,同一套Split逻辑才能吃下去。
StreamingAssets 与 persistentDataPath 分工
- 两路径在热更里 分工明确:谁只读、谁可写、清单和 AB 最终信谁,下面一张表按「路径 → 角色」压缩对照。
- 加载策略(是否转存默认包)仍回链上一节 是否要做默认资源转存。
| 路径 | 典型内容 | 读写 | 在更新流程里的角色 |
|---|---|---|---|
Application.streamingAssetsPath |
安装包随带的默认 AB、ABCompareInfo.txt |
多数平台 只读 | 首装、未写过 persistent 时的 本地基准;包内资源运行期不能当「可删缓存」处理 |
Application.persistentDataPath |
热更 AB、成功闭环后的 ABCompareInfo.txt、下载中的 ABCompareInfo_TMP.txt |
可读可写 | 设备上「当前认账」的清单与资源根;跨启动保留 |
是否要做默认资源转存
- 只看 加载器从哪儿
LoadFromFile:只认 persistent 时,首启常要把 StreamingAssets 里默认 AB 整批复制 到可写目录,逻辑直白,默认包很大时会 双份占盘。 - 先 persistent、没有再回退 StreamingAssets 可 不转存,省空间,封装多一层路径解析即可。
- 热更主干仍是:拉远端对比文件 → 与本地基准算差集 → 只下缺的或 md5 变的 AB → 全流程成功后再把远端正文写入 persistent;与是否转存正交,差在本地字典从哪读。
首启与非首启的常见分支
- 首次且 StreamingAssets 有默认清单:本地基准来自只读目录,与远端对比后 只下差集。
- 首次且两处都无清单:本地字典为空,通常 远端列出的包都要下。
- **persistent 已有正式
ABCompareInfo.txt**:以可写目录为基准做差量,属热更成功后的常态。
远端清单:临时文件与不破坏正式清单
DownLoadABCompareFile把服务器ABCompareInfo.txt落到ABCompareInfo_TMP.txt,填remote字典也在 TMP 上读,避免半截下载 覆盖 正在使用的正式ABCompareInfo.txt。- 也可 流读完整串再解析;共通约束是:AB 整轮没落稳前,别把未闭环的远端内容当成正式本地清单。
CheckUpdate在下载成功后用 **ReadAllText(TMP)得到remoteInfo**,解析与收尾WriteAllText(..., remoteInfo)用 同一份字符串,避免中间态不一致。
本地清单:共用解析与协程读取
GetABCompareFileInfoToDictionary:Split('|'),每条再Split(' ')得 名、长、MD5;远端 TMP、本地 persistent、本地 StreamingAssets 共用。GetLocalABCompareFileInfo:先判File.Exists(persistentDataPath + "/ABCompareInfo.txt")→UnityWebRequest.Get绝对路径读文;否则 试streamingAssetsPath + "/ABCompareInfo.txt";都没有 时overCallBack(true)表示加载步骤未报失败,字典为空,后续 diff 会得到 待下满列表,勿理解成「无更新」。- 协程里以
UnityWebRequest.Result.Success为准,失败走overCallBack(false)。
CheckUpdate 与容器清理
- 入口
Clear:remoteAssetBundleInfoDictionary、localAssetBundleInfoDictionary、assetBundleDownLoadList。 - 顺序:远端 TMP 成功 → 读串填 remote → 加载 local → foreach remote 建待下列、顺带从 local 摘掉已对齐的名 → 删 persistent 应删文件 →
DownLoadABFile只跑列表 → 全成功再WriteAllText正式清单。
差量、删包与进度字符串
- 遍历
remote:本地无 key → 待下;有 key、md5 不同 → 待下;有 key、md5 相同 → 不下,且local.Remove(abName)。 - 循环后
local剩余 key 表示 远端清单已不再包含 这些包;对persistentDataPath + "/" + abName存在则File.Delete。StreamingAssets 默认资源 不在此路径删。 DownLoadABFile去掉「全量 remote key 进列表」的旧写法后,待下 仅由对比产生;UnityAction<string>进度用已完成 + "/" + 总数。- 仅当下载回调表示全部成功,才
WriteAllText更新正式清单;失败保留旧清单,利于重试。
资源更新自检顺序
- 可选:打包阶段菜单写入 StreamingAssets 默认 AB 与
ABCompareInfo.txt。 CheckUpdate:清容器 → TMP → remote → local(persistent 优先)。- diff → 删 persistent 废弃 AB → 下待下列 → 成功后写 persistent 正式清单。
ABTools:把散菜单收成 EditorWindow
- 自定义
EditorWindow(课内类名ABTools),[MenuItem("AB包工具/打开工具窗口")]里GetWindowWithRect(typeof(ABTools), new Rect(0, 0, 350, 220))再Show,得到带标题栏的浮动面板(与控制台里 AB包工具 菜单入口一致)。 OnGUI用绝对Rect排版:上面GUI.Toolbar维护nowSelIndex,字符串数组{"PC","IOS","Android"};中间GUI.TextField填serverIP(默认形如ftp://...);下面三个GUI.Button分别触发 生成对比文件、默认资源进 StreamingAssets、上传 AB 与对比文件,从上到下就是「选平台 → 填服务器 → 本地准备 → 上传」一条龙。- 相比分散
MenuItem,策划 / TA 不用记多条菜单,平台与 FTP 根地址 在同屏,减少「生成在 PC 目录却上传到别的子路径」的手误。
工具窗逻辑与 FTP 目录约定
CreateABCompareFile:读Application.dataPath + "/ArtRes/AB/" + targetStrings[nowSelIndex],只收Extension == ""的 AB,拼名 长度 MD5,|分隔,去掉尾部分隔后写入 **同目录ABCompareInfo.txt**,再AssetDatabase.Refresh。目录里若没有无扩展名 AB,课内会先判空再Substring,避免异常。MoveABToStreamingAssets:与早期篇一致,**Selection.GetFiltered+CopyAsset** 到 StreamingAssets,写StreamingAssets/ABCompareInfo.txt;若选中里没有合法无后缀 AB,拼接串为空则直接返回。UploadAllABFile:同一平台目录下Extension == ""或".txt"的文件逐个FtpUploadFile;URI 为serverIP + "/AB/" + targetStrings[nowSelIndex] + "/" + fileName,与本地ArtRes/AB/{平台}同名文件夹 对齐,FTP 服务端需预先存在AB/PC、AB/IOS、AB/Android这类子目录。FtpUploadFile:**async void+Task.Run** 里组FtpWebRequest(UploadFile、UseBinary、账号等),与早课同款隐患:多文件 foreach 会并行上传、难以统一 await 收口,生产工具可改为Task+ 限并发。
客户端:平台宏与远端路径对齐
AssetBundleUpdateManager内维护serverIP,DownLoadFile里用预编译符号得到pInfo:UNITY_IOS→"IOS",UNITY_ANDROID→"Android",else→"PC",再拼serverIP + "/AB/" + pInfo + "/" + fileName。- 与编辑器
targetStrings必须是同一套拼写与大小写(课内为 IOS 而非 iOS),否则打 Android 包仍会请求 PC 目录,表现为远端路径对不上。 - 真机与编辑器 FTP 根、账号、防火墙也要一致;工具窗改的
serverIP与 运行时字段 建议最终都来自配置表或构建参数,避免两处各写各的。
UnityWebRequest 读本地清单的 URI 规则
| 场景 | UnityWebRequest |
File / FileStream(课内对比用) |
|---|---|---|
persistentDataPath 下文件 |
PC / Android / iOS 课内用 file:/// + 绝对路径 |
路径可直接 ReadAllText,无需 file:/// |
PC、iOS 的 streamingAssetsPath |
课内 file:/// + streamingAssets 根 再拼 /ABCompareInfo.txt |
可用原始路径直读 |
Android 的 streamingAssetsPath |
APK 内资源,课内 直接用 Application.streamingAssetsPath 拼接 给 UWR |
标准文件流 通常不能直接当普通磁盘路径读 |
- 正文强调:编辑器里省略
file:///有时仍能跑,打进包后 UWR 对本地 URI 更挑剔,故按上表补全;Android StreamingAssets 不要照搬 PC 的file:///套法。 - 需要按平台拆逻辑时,除
UNITY_IOS/UNITY_ANDROID外,条件编译还可配合UNITY_EDITOR等符号,避免编辑器专属代码进包。
17.3 面试题精选
基础题
1. 资源对比文件与 AB 包在客户端更新时的合理顺序
题目
热更场景下,客户端每次启动:资源对比文件什么时候下载、什么时候和本地比对、本地对比文件什么时候更新,和 AB 包下载的大致先后关系是怎样的?为什么要这样排?
深入解析
- 对比文件先行:先拿到远端最新对比文件,才有机会和本地清单对齐,算出「缺谁、谁过期」。
- AB 后于决策:知道差集之后再去拉 AB,避免盲下全量或下错包。
- 本地对比文件最后提交:应当在 相关 AB 已全部更新成功 之后再写本地对比文件。否则会出现清单宣称已是最新、但实际磁盘缺包或旧的;失败重试时也不易判断真实状态。
- 这和「把对比文件当真相来源、AB 当内容载体」的角色分工一致:内容没落稳,就先别改指针。
答题示例
先进游戏或进热更阶段,先下远端资源对比文件,和本地对比算出需要更新的 AB 列表。
按列表下载并替换 AB,全部成功后再更新本地资源对比文件,最后再进入游戏。
这样本地清单和磁盘上的 AB 能成对升级;中途失败时本地清单仍是旧版,便于下次继续差量下。
参考文章
- 1.概述
2. 对比文件与 AB 为何优先放在 persistentDataPath 一类目录
题目
课内把远端的 ABCompareInfo.txt 和后续 AB 都写到 Application.persistentDataPath 下,而不是塞进 StreamingAssets 或只读包内路径,主要出于哪些工程上的考虑?
深入解析
- 可写:热更落盘必须能覆盖写;StreamingAssets 在多数平台上只读,不适合当「下载缓存根」。
- 生命周期:持久化目录跨启动保留;重装或清数据前,本地已有资源可复用,减少重复下载。
- 与包体解耦:包内资源走安装包版本,热更内容走外置目录,职责清晰,也利于排查「包内默认资源 vs 外置更新资源」。
答题示例
热更下来的清单和 AB 要能写磁盘、下回启动还能用,persistentDataPath 就是干这个的。
StreamingAssets 往往是随包只读,不适合长期往里追加下载内容。
参考文章
- 7.下载相关-下载资源对比文件
- 8.下载相关-下载AB包
3. 加载本地对比文件时为何优先 persistentDataPath 再找 StreamingAssets
题目
GetLocalABCompareFileInfo 里先判断 persistentDataPath/ABCompareInfo.txt 是否存在,没有再尝试 streamingAssetsPath;若两个都没有则直接回调成功。这套分支在热更流程里分别对应什么含义?
深入解析
- persistent 有文件:说明玩家侧至少完成过一次「把远端清单落到可写目录」的流程,本地 应以这份为准 做差量,而不是回退去读包内旧清单。
- persistent 没有、StreamingAssets 有:典型 首装带默认资源,本地基准来自 只读包内清单,再和远端算差量。
- 两个都没有:极小客户端或尚未配置默认包,本地字典为空,后续与远端对比会表现为 远端列出的包都去下;回调 true 表示「加载本地清单这一步没有 IO 错误」,不是「没有更新」。
答题示例
可写路径里有清单,说明之前已经按热更路线更新过,本地真相在那儿。
没有才用 StreamingAssets 里的默认清单;两个都没有就等于本地没有任何条目,后面全靠远端清单驱动。
参考文章
- 12.资源更新-资源更新删除-获取本地对比文件
- 9.资源更新-设置默认资源
进阶题
1. 热更清单里为什么用 MD5 一类摘要,而不是只靠文件名或文件大小
题目
做 AB 资源对比文件时,为什么单靠资源名或文件大小不够判断「文件是否更新」,MD5 这类摘要能解决什么问题?
深入解析
- 文件名不变、内容已变:覆盖同名 AB 是常态,名字对不上「内容版本」。
- 大小不可靠:不同内容可能凑出相同长度;即便概率低,也不等价于内容一致。
- MD5(课内演示用的摘要算法):对整文件做散列,内容一改摘要就变,适合做「是否同一份字节」的判据;工程上也会用 SHA 族等更强算法,思路相同。
- 代价是算摘要要读完整文件一遍,打包机或 CI 上做通常可接受。
答题示例
文件名可以不变但内容已经重新导过;只看大小不能证明内容一致。
MD5 对文件全体算指纹,内容变就变,适合做对比文件里的校验字段;算摘要有开销,一般在打资源或生成清单时做。
参考文章
- 3.上传相关-获取AB包文件的MD5码
- 4.上传相关-生成AB包资源对比文件
2. 为何强调对比文件下载成功后再解析
题目
DownLoadABCompareFile 为何要在 isOver == true 之后才对本地文件做 **ReadAllText + GetABCompareFileInfoToDictionary**?若下载失败或中途仍去解析,会出现哪些典型问题?
深入解析
- 失败或半截写入时,目标文件可能 不存在、0 字节、截断文本;
Split/ 取下标可能 越界,或拼出 错误字典条目 却不抛异常。 - 后续待下列表、删包逻辑都以这份「伪远端」为准,表现为 漏下、错下、误删,线上难复现。
- 与「对比文件是增量策略的输入」一致:输入不可信则整条链不可信;成功回调表示传输层已把可信字节落到约定路径(或你改用内存完整串后再解析,同理)。
答题示例
没下成功就别读;否则可能是空文件、半截文件或旧文件,解析出来全是脏数据。
先等下载成功再 ReadAllText / 填字典,保证用的这一版清单是完整的。
参考文章
- 8.下载相关-下载AB包
- 11.资源更新-资源更新删除-获取远端对比文件时不覆盖
3. 远端资源对比文件为何先落到临时文件再参与解析
题目
课内把服务器上的 ABCompareInfo.txt 下载为 ABCompareInfo_TMP.txt,解析远端字典也基于 TMP;正式路径下的 ABCompareInfo.txt 要到 AB 全部下载成功后才写入。这样设计主要规避哪些问题?
深入解析
- 下载中断或网络异常时,若直接覆盖正式清单,磁盘上可能出现 空文件、半截文本或新旧混杂,下一轮启动会把 不可信的串 当本地真相,差量计算全废。
- 正式
ABCompareInfo.txt与 persistent 上的 AB 文件是 配套状态:清单先宣称新版本、AB 还没下完,加载器会按新名去找文件却缺失或仍是旧字节。 - TMP 与 缓存下来的整串
remoteInfo把「远端本轮清单」收口在一处;AB 闭环成功后再WriteAllText正式路径,失败则旧清单仍在,利于重试。流读内存不落地是同类思路,关键是 不要把未闭环的远端内容当成已提交的本地清单。
答题示例
直接覆盖正式清单,下崩了就会留下坏清单或半截文件,后面 diff 全错。
先写 TMP,成功读完再解析;AB 都下完再把远端正文写进正式对比文件,这样清单和磁盘上的包同步升级。
参考文章
- 11.资源更新-资源更新删除-获取远端对比文件时不覆盖
- 12.资源更新-资源更新删除-获取本地对比文件
4. UnityWebRequest 读本地对比文件时 file:/// 与 Android StreamingAssets
题目
优化篇里对 persistentDataPath 下的 ABCompareInfo.txt 使用 file:/// 前缀交给 UnityWebRequest.Get;读 StreamingAssets 时 Android 用裸 Application.streamingAssetsPath 拼接,PC、iOS 则先加 file:///。这么分叉是为了什么?
深入解析
UnityWebRequest按 URI 解析;裸文件系统路径在编辑器偶发可用,真机或部分 ROM 上容易失败,故 persistent 侧课内统一file:///+ 绝对路径。- Android 的
streamingAssetsPath指向 APK 内资源,由 UWR 内部按 jar 访问;再套一层与 PC 相同的file:///规则往往不对,课内分支 **Android 不加file:///**。 - PC、iOS 侧课内
"file:///" + Application.streamingAssetsPath + "/ABCompareInfo.txt";与 仅 Android 的分支要在预编译里写死,避免一键宏用错。 - 若不用 UWR、改用
File.ReadAllText(persistent),则不必写file:///,这是 API 选型差异,不是「必须用 file」的绝对真理。
答题示例
UWR 吃 URI,persistent 一般用 file 协议挂全路径才稳。
Android 的 StreamingAssets 在包体里,UWR 直接吃工程给的路径;PC、iOS 用 file 包一层。混用会表现为某个平台永远读清单失败。
参考文章
- 16.上传指定资源服务器-客户端热更新路径优化
深度题
1. 编辑器里批量 FTP 上传与 async void 的组合,工程上要警惕什么
题目
课内在菜单里 foreach 多个文件,每个文件走 Task.Run + async void 的 FTP 上传。这种写法在真实工具里可能带来哪些问题?可以怎么改进方向(不要求背具体 API,讲清思路即可)?
深入解析
async void几乎无法被外层 await:意味着「全部完成、全部失败」没有统一收口点,菜单一点完编辑器就继续跑别的去了。- 并发上传:多个文件同时开 FTP 连接,可能被服务器限连、插队导致顺序乱、日志交错;失败重试也要各自处理。
- 异常:
async void里未捕获异常可能直接进领域外失败,排错困难。 - 改进方向:可改为返回
Task的方法,用async Task+await串联或Task.WhenAll限并发;编辑器工具里至少统计成功/失败数、支持取消;账号与 URL 不要硬编码,用配置或环境注入。
答题示例
async void不能 await,foreach 一轮发出去多场上传,很难知道「是不是都传完了」、谁先失败。并行还可能顶到 FTP 连接数限制。一般会改成返回 Task,
WhenAll或一个个 await,加并发上限和统一错误汇总;地址密码走配置。
参考文章
- 6.上传相关-上传AB包和信息文件-上传AB包和资源对比文件
- 5.上传相关-上传AB包和信息文件-搭建FTP资源服务器
- 15.上传指定资源服务器-工具窗口逻辑处理
2. 对比时「删掉本地字典条目」如何推出「要删磁盘上的 AB」
题目
正文遍历远端 remoteAssetBundleInfoDictionary:若本地有同名包且 MD5 一致,就从 localAssetBundleInfoDictionary 里 Remove 掉。循环结束后,为什么对 仍留在 local 里的 key 要去 persistentDataPath 下 File.Delete 对应 AB 文件?
深入解析
- 循环体里,只要 本地也存在同名包(进入
else),最后都会对该名执行一次local.Remove:要更新的、MD5 已一致的,名字都会从 local 里清掉。 - 因此
foreach跑完远端所有 key 之后,还能留在 local 字典里的,只可能是 远端清单里压根没出现过的包名——属于服务端已下架、本地 persistent 仍可能残留文件的那批。 - 对这些残留名做
File.Delete,避免加载器继续用旧 AB;StreamingAssets 侧只读,课内只对 persistentDataPath 动刀。
答题示例
远端有的名字在对比时都会从 local 字典里摘掉,最后剩的全是「远端没有、本地还多出来的」。
那些文件在 persistent 里就该删掉,避免还用旧包;包内 StreamingAssets 动不了。
参考文章
- 13.资源更新-资源更新删除-下载更新删除资源
- 12.资源更新-资源更新删除-获取本地对比文件
3. ABTools 与运行时如何约定 FTP 上的平台子目录
题目
编辑器工具用 targetStrings 拼本地 ArtRes/AB/{平台} 和上传 URI serverIP/AB/{平台}/文件名;客户端 DownLoadFile 用 UNITY_IOS、UNITY_ANDROID 等得到 pInfo 再访问同结构路径。要防止「编辑器传得进、真机下不到」,必须对齐哪些约定?
深入解析
- 平台文件夹名字节级一致:如
IOS全大写、**Android首字母大写**,须与 FTP 实建目录、targetStrings、pInfo分支 完全相同;任意一处改成iOS、iphone都会定点错位。 serverIP同一事实来源:工具窗填写与AssetBundleUpdateManager.serverIP长期分流时,易出现内网 / 公网、端口、尾随斜杠 不一致;应抽到 ScriptableObject、环境配置或 CI 注入。- 预编译覆盖:默认
else走 PC;若目标平台宏未定义或被剥离,手机包仍请求 PC 目录,现象为 仅某构建管线失败。 - 服务端预制目录:
AB/PC、AB/IOS、AB/Android需存在且权限正确;仅本地改了产出目录、远端未同步,会整批 550/404。
答题示例
工具里显示的平台名、本机输出子文件夹、FTP 上的子文件夹、客户端宏里拼的字符串,四者要一致。
serverIP 也要跟运行时同源;宏写错或缺目录,就会固定在某个平台全红。
参考文章
- 14.上传指定资源服务器-编辑器窗口设计
- 15.上传指定资源服务器-工具窗口逻辑处理
- 16.上传指定资源服务器-客户端热更新路径优化
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com