17.AssetBundle上传下载总结

  1. 17.总结
    1. 17.1 知识点
      1. 主要讲解内容
      2. 完成目标
      3. 主要使用的知识点
      4. 实践优化
    2. 17.2 核心要点速览
      1. 编辑器上传与客户端增量更新
      2. 上手本系列前建议自带的基础
      3. 准备 AB 与 AssetBundles-Browser
      4. 资源对比文件:MD5 在清单里的角色
      5. 编辑器里生成 ABCompareInfo.txt
      6. FTP 资源服务器与编辑器上传
      7. 上传链路的落地顺序
      8. 客户端:FTP 下载对比文件与远端字典
      9. AssetBundleInfo 与相等语义
      10. 批量异步下载 AB 与重试壳
      11. 练习要求的调用顺序
      12. 下载侧落地顺序
      13. 编辑器:默认 AB 进包与清单格式
      14. StreamingAssets 与 persistentDataPath 分工
      15. 是否要做默认资源转存
      16. 首启与非首启的常见分支
      17. 远端清单:临时文件与不破坏正式清单
      18. 本地清单:共用解析与协程读取
      19. CheckUpdate 与容器清理
      20. 差量、删包与进度字符串
      21. 资源更新自检顺序
      22. ABTools:把散菜单收成 EditorWindow
      23. 工具窗逻辑与 FTP 目录约定
      24. 客户端:平台宏与远端路径对齐
      25. UnityWebRequest 读本地清单的 URI 规则
    3. 17.3 面试题精选
      1. 基础题
        1. 1. 资源对比文件与 AB 包在客户端更新时的合理顺序
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 对比文件与 AB 为何优先放在 persistentDataPath 一类目录
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 加载本地对比文件时为何优先 persistentDataPath 再找 StreamingAssets
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. 热更清单里为什么用 MD5 一类摘要,而不是只靠文件名或文件大小
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 为何强调对比文件下载成功后再解析
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 远端资源对比文件为何先落到临时文件再参与解析
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. UnityWebRequest 读本地对比文件时 file:/// 与 Android StreamingAssets
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. 编辑器里批量 FTP 上传与 async void 的组合,工程上要警惕什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 对比时「删掉本地字典条目」如何推出「要删磁盘上的 AB」
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. ABTools 与运行时如何约定 FTP 上的平台子目录
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

17.总结


17.1 知识点

主要讲解内容

完成目标

主要使用的知识点

实践优化


17.2 核心要点速览

编辑器上传与客户端增量更新

编辑器侧(有资源变更时)

  • 打完 AB 后,把 AB 包资源对比文件 一起上传到资源服务器;远端以对比文件描述「当前应有哪些 AB、各自版本或校验信息如何」。

客户端侧(每次启动流程的常见骨架)

  1. 先向服务器要 最新的资源对比文件
  2. 本地 已有的对比文件做差异分析:哪些 AB 缺失、哪些需要被新版本替换,形成待办列表。
  3. 按列表下载缺的 / 过期的 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 把文件以流方式打开 → MD5CryptoServiceProviderComputeHash 得到 16 个字节 → 按字节转 小写十六进制ToString("x2"))拼成 32 位字符串。

编辑器里生成 ABCompareInfo.txt

  • 只在 Unity 编辑器 用的工具函数,放进 Editor 文件夹(或 Editor 程序集),不会进玩家包
  • 典型流程:[MenuItem] 点菜单 → 指向课内约定的输出目录(例如 Application.dataPath + "/ArtRes/AB/PC/")→ DirectoryInfo.GetFiles() → 过滤 扩展名为空 的条目当作 AB 文件 → 每个 AB 追加一条记录:文件名 文件长度 MD5记录之间用 | 串进同一段文本 → 去掉**末尾多余的那一个 |**(字符串为空时不要裁剪,需先判断长度大于 0)→ 写入 ABCompareInfo.txtAssetDatabase.Refresh()
  • 客户端以后要 按同一套分隔符和字段顺序 解析;若文件名或将来扩展字段里出现空格、|,课内这种纯拼接格式就要升级成更稳的编码(例如 JSON)——这是正文格式隐含的限制。

FTP 资源服务器与编辑器上传

  • 课内用 Serv-U 等软件搭 FTP:建域、单向加密、上传用账号密码、下载可开匿名等。也强调了一句现实分工:正式环境经常是后端 / 运维先把 FTP(或其它存储)搭好,你再拿主机、路径、账号去传。
  • 上传侧用 FtpWebRequestMethod 设为 WebRequestMethods.Ftp.UploadFileUseBinary = true,配好 CredentialsProxy = nullGetRequestStream() 拿到上传流后,从本地 FileStream 按块读(课内 2048)往里写。
  • 远端 URI 形如 ftp://127.0.0.1/AB/PC/ + 文件名;FTP 服务端要先存在 AB/PC 目录,否则容易直接失败(不会像某些 HTTP API 那样自动替你建多级路径)。
  • 批量上传:课内对目录里 无后缀 AB.txt 对比文件 逐个调用上传;实现上常用 Task.Run + async void「发完就走」,多场 FTP 会 并行 起来——要不要限流、要不要等全部完成再打日志,是工程上要补的约束。

上传链路的落地顺序

  1. 新建或打开工程,导入 AssetBundles-Browser,把测试资源打进固定输出目录(课内为 ArtRes/AB/PC)。
  2. 菜单 AB包工具 / 创建对比文件 扫描该目录,为每个无扩展名 AB 写 MD5,生成 ABCompareInfo.txt
  3. 在 FTP 侧建好 AB/PC(或与 URI 一致的路径);生产环境通常由运维给主机、账号、权限,课内仅演示 Serv-U 一类搭建流程。
  4. 菜单 AB包工具 / 上传AB包和对比文件 把无后缀 AB 与 .txt 一并 UploadFile 到远端;上传前把账号密码、根 URL 抽到配置,别长期写死在脚本里。

客户端:FTP 下载对比文件与远端字典

  • 与上传对称:FtpWebRequestMethodWebRequestMethods.Ftp.DownloadFileUseBinary = trueCredentials / Proxy / KeepAlive 配置方式同类;GetResponse() 拿到 FtpWebResponseGetResponseStream() 读网侧流,按块 写入 File.Create(localPath)(课内缓冲 2048)。
  • 课内把 ABCompareInfo.txt 落到 Application.persistentDataPathReadAllText;后续 AB 也用 persistentDataPath + "/" + 包名,和包内只读目录区分开,方便热更写盘与跨版本保留。
  • 解析协议与编辑器生成端一致:Split('|') 拆记录,每条再按空格拆成 包名、大小、MD5 三段,long.Parse 大小后进 Dictionary<string, AssetBundleInfo>。分隔符或字段里若出现空格,这种手写格式就会翻车,成熟项目多半换 JSON / 二进制表。

AssetBundleInfo 与相等语义

  • 模型三个字段:包名、sizemd5== / != / Equals / GetHashCode 都围绕 md5,含义是「内容是否同一版」,不是比显示名。
  • 自定义相等时 GetHashCodeEquals 要一致,后面才能把条目放进依赖哈希的容器或做 LINQ 去重而不踩坑。

批量异步下载 AB 与重试壳

  • DownLoadFile 改成 bool:FTP 整段包在 try 里,成功 return true,异常打日志并 false
  • DownLoadABFileasync void:先把 remoteAssetBundleInfoDictionary所有 key 推进待下列表(本段示例等价「远端清单里列出的 AB 全量下载」;与本地对比后只下差集的做法在后续资源更新流程里再接上)。
  • 结构是 外层 while 管「轮数」(课内 reDownLoadMaxNum 从 5 递减),内层 for 管当前列表里每一项;每项 await Task.Run(() => { isOver = DownLoadFile(...); }),成功则推进度回调并把名字塞进 tempList,内层结束再统一从列表 Remove;失败的条目留在表里等下一轮。
  • 收尾:overCallBack(assetBundleDownLoadList.Count == 0),列表非空表示仍有失败项且重试用尽或需要你上层另做策略。

练习要求的调用顺序

  • 对比文件:异步下载 + 最多重试UnityAction<bool>trueReadAllText 临时文件(如 ABCompareInfo_TMP.txt)并 GetABCompareFileInfoToDictionary 填远端字典;不要在未确认下载成功时解析正式清单或半截文件。
  • DownLoadABFile,全程成功后再进游戏逻辑;失败分支留给 UI / 重试按钮,这一点和概述里「清单可信再动资源」一回事。

下载侧落地顺序

  1. FTP 下载 ABCompareInfo.txt 到持久化目录,可加重试。
  2. 成功后解析进远端字典。
  3. 再跑批量 DownLoadABFile;已做本地远端对比时,待下列表应只含需要更新的包名。

编辑器:默认 AB 进包与清单格式

  • 菜单 AB包工具 / 移动选中资源到StreamingAssets中 实际是 AssetDatabase.CopyAsset:选中项经 Selection.GetFiltered(..., DeepAssets) 拿出,AssetDatabase.GetAssetPath 取工程内路径,从最后一个 / 切出文件名。
  • 无扩展名才当作 AB:文件名里出现 .continue;更稳的是正文提到的 FileInfo.Extension,避免「无后缀但名里带点」误判。
  • 拷贝目标为 Assets/StreamingAssets + 文件名;对每个已拷贝文件用 **FileInfoNameLength**,再配合 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)同一份字符串,避免中间态不一致。

本地清单:共用解析与协程读取

  • GetABCompareFileInfoToDictionarySplit('|'),每条再 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 与容器清理

  • 入口 ClearremoteAssetBundleInfoDictionarylocalAssetBundleInfoDictionaryassetBundleDownLoadList
  • 顺序:远端 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 更新正式清单;失败保留旧清单,利于重试。

资源更新自检顺序

  1. 可选:打包阶段菜单写入 StreamingAssets 默认 AB 与 ABCompareInfo.txt
  2. CheckUpdate:清容器 → TMP → remote → local(persistent 优先)。
  3. 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.TextFieldserverIP(默认形如 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/PCAB/IOSAB/Android 这类子目录。
  • FtpUploadFile:**async void + Task.Run** 里组 FtpWebRequestUploadFileUseBinary、账号等),与早课同款隐患:多文件 foreach 会并行上传、难以统一 await 收口,生产工具可改为 Task + 限并发。

客户端:平台宏与远端路径对齐

  • AssetBundleUpdateManager 内维护 serverIPDownLoadFile 里用预编译符号得到 pInfoUNITY_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:///。这么分叉是为了什么?

深入解析
  • UnityWebRequestURI 解析;裸文件系统路径在编辑器偶发可用,真机或部分 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 一致,就从 localAssetBundleInfoDictionaryRemove 掉。循环结束后,为什么对 仍留在 local 里的 key 要去 persistentDataPathFile.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/{平台}/文件名;客户端 DownLoadFileUNITY_IOSUNITY_ANDROID 等得到 pInfo 再访问同结构路径。要防止「编辑器传得进、真机下不到」,必须对齐哪些约定?

深入解析
  • 平台文件夹名字节级一致:如 IOS 全大写、**Android 首字母大写**,须与 FTP 实建目录、targetStringspInfo 分支 完全相同;任意一处改成 iOSiphone 都会定点错位。
  • serverIP 同一事实来源:工具窗填写与 AssetBundleUpdateManager.serverIP 长期分流时,易出现内网 / 公网、端口、尾随斜杠 不一致;应抽到 ScriptableObject、环境配置或 CI 注入。
  • 预编译覆盖:默认 elsePC;若目标平台宏未定义或被剥离,手机包仍请求 PC 目录,现象为 仅某构建管线失败
  • 服务端预制目录AB/PCAB/IOSAB/Android 需存在且权限正确;仅本地改了产出目录、远端未同步,会整批 550/404。
答题示例

工具里显示的平台名、本机输出子文件夹、FTP 上的子文件夹、客户端宏里拼的字符串,四者要一致。

serverIP 也要跟运行时同源;宏写错或缺目录,就会固定在某个平台全红。

参考文章
  • 14.上传指定资源服务器-编辑器窗口设计
  • 15.上传指定资源服务器-工具窗口逻辑处理
  • 16.上传指定资源服务器-客户端热更新路径优化


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏