资讯动态

Unity安卓APK版本更新完整方案:检测、下载、MD5校验与安装适配

发布时间:2026/10/20 16:18:43 来源:尧图企业网站定制
做Unity安卓项目最躲不开的东西就是“更新”。无论是修复线上崩溃、上线新玩法还是替换过期的配置文件只要应用发出去官方商店审核的节奏往往是跟着版本周期走的自有渠道或者测试包反而等不起。直接让玩家点链接下载APK再手动安装的体验太粗暴了而且七八年前的Android机型装上还会因为文件来源问题直接打不开。后来我把整套APK版本更新链路收敛到Unity内部从版本检测、下载、MD5校验到拉起系统安装器全部在C#层搞定不需要额外写原生Activity也不用接入第三方SDK。这篇文章就把这套方案完整拆出来从技术选型到Android各版本适配的坑一次说清适合在Unity 2019/2020/2021上做过Android导出、但没系统处理过更新链路的开发者参考。1. 更新链路设计先搭框架再谈APK下载安装1.1 为什么需要一套独立的APK版本更新链路很多团队的第一反应是“直接用商店更新不就行了”。这个想法在Google Play和国内主流商店确实成立但放到自有渠道、官网下载站、定向测试、海外小众市场这些场景就很尴尬。商店更新需要上架审核灰度包和测试包根本走不了这个流程。另一个绕不开的现实是Unity打包出来的APK体积动不动就是两三百兆横跨多个Android大版本系统安装器对未知来源的限制越来越严如果只是丢一个下载链接给用户激活率会非常难看。更核心的需求在于“可控”。版本更新不只是让用户装新包它还得能表达“这个版本是强制还是建议”能告诉用户更新了什么能处理下载失败、校验失败、已下载过一半的缓存文件。这些逻辑放在服务器端动态下发客户端按约定执行才叫一套完整的更新链路而不是临时写一个跳转链接。把检测和下载都收到Unity内部之后策划和运营可以直接在后端配置更新策略客户端不用发版就能调整这套东西才真正具备生产价值。1.2 完整链路拆解版本检查、下载、安装三件事整个APK版本更新链路可以切成三段。第一段是客户端向服务器请求版本清单清单里包含最新版本号、APK下载地址、文件MD5、是否强制更新、更新日志。第二段是本地版本号比较一旦发现远端版本更高就弹出更新面板同时根据强制属性决定用户能否跳过。第三段是下载新APK到应用沙盒目录校验完MD5后通过系统安装器拉起安装界面用户点击确认后完成覆盖安装。为什么要让客户端来做版本号比较而不是服务器直接返回“需要更新”因为版本比较涉及本地的versionCode只有客户端自己最清楚。服务器如果维护一个“每个包名当前版本”的状态在多渠道多包名场景下逻辑会变得很笨重。反过来服务器只需要提供一个静态JSON文件客户端拿到后按约定比较更新策略的改动完全不需要客户端发版一套服务端配置可以同时服务多个不同包名的应用。这个设计从一开始就把复杂度放到了客户端但换来的是非常灵活的运营节奏。1.3 更新策略取舍全量APK、资源更新还是热更新方案聊到版本更新一定会有人问“为什么不用热更新”。这取决于项目的技术栈。如果用Unity官方AssetBundle或各家的热更框架代码逻辑通过IL2CPP解释执行资源走分包加载确实可以做到不装APK就换玩法。但热更方案天然有一套复杂的构建、加密、回退和版本管理成本而且Unity引擎本身和AndroidManifest、Application类等原生层改动仍然需要发新包。APK全量更新和热更新不是替代关系而是互补关系引擎级改动、原生SDK改动、targetSdk提升这些只能通过APK更新业务资源逻辑迭代才适合走热更。全量APK更新最大的问题就是流量和等待时间。几百兆的包在白天空闲下载还能接受但在弱网环境下很容易半途失败。因此这套方案里一定要有断点续传的替代手段和MD5校验避免用户反复下载损坏的包。如果团队资源充裕还可以做二进制差分更新把新老APK的diff包下发到客户端再本地合成但那个方案对服务器构建链路要求更高一般项目没必要一上来就上。先把全量APK更新做稳健已经能覆盖90%的场景。2. 服务端版本清单与版本判断别在versionName上做大小比较2.1 版本清单的数据结构设计版本清单本质上就是一个Json文件放在CDN或者任何静态文件服务上客户端用UnityWebRequest拉取。我建议在字段设计上一步到位避免后面频繁扩展字段导致解析兼容问题。下面是我在多个项目里沉淀下来的结构{ versionCode: 1024, versionName: 3.2.0, updateType: 0, apkUrl: https://cdn.example.com/packages/com.example.game_1024.apk, fileSize: 314572800, md5: 0a2f4c6d91e3f0b5a6b7c8d9e0f1a2b3, releaseNotes: 修复了若干崩溃\n新增活动功能\n性能优化, minVersionCode: 1000 }versionCode必须是整数它才是Android系统真正用来判断版本新旧的关键字段。versionName只是给人看的。updateType用0和1表示可选更新与强制更新比字符串类型更不容易出错。minVersionCode可以用来做“低于某个版本必须强更”的精细控制比如客户端当前版本实在太老、后台服务协议已经不兼容时即使服务端没有把updateType设成1客户端也可以根据minVersionCode自己判断成强制更新。这个字段很实用前置版本可以不用但字段要留下。fileSize和md5这两个字段绝大多数开发者会忽略但它们直接决定了下载体验。fileSize可以用来在下载前检查本地存储空间提前告诉用户空间不足。md5则是防止下了个残缺APK的最后一道防线。releaseNotes直接用换行符分隔客户端展示更新日志时按行拆开即可不需要在服务器端维护富文本。2.2 版本号比较的核心规则版本比较绝对不能靠versionName做字符串排序比如9.0.0字符串比较会大于10.0.0这是典型翻车现场。Android端的正确做法是读取应用自身的versionCode它是自增的整数新版本一定比旧版本大。Unity C#侧没有直接获取versionCode的API得通过AndroidJavaObject和PackageManager去拿。下面的代码是从Unity里读取当前应用versionCode的通用写法private int GetLocalVersionCode() { #if UNITY_ANDROID !UNITY_EDITOR try { using (var player new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity player.GetStaticAndroidJavaObject(currentActivity)) using (var packageManager activity.CallAndroidJavaObject(getPackageManager)) using (var packageInfo packageManager.CallAndroidJavaObject(getPackageInfo, Application.identifier, 0)) { return packageInfo.Getint(versionCode); } } catch (Exception e) { Debug.LogError(读取versionCode失败: e.Message); return 0; } #else return 1; #endif }这段代码需要注意几点。第一Unity的Application.identifier在Android上就是包名不需要写死。第二getPackageInfo的参数0表示不需要额外返回签名等信息只拿基本信息速度更快。第三老的Unity版本在编辑器下没有AndroidJavaClass环境所以一定要用UNITY_ANDROID和UNITY_EDITOR做条件编译隔离。拿到本地versionCode后和服务器返回的versionCode做整数比较remoteVersionCode localVersionCode才认为有新版本逻辑非常干净。2.3 强制更新与可选更新的判定逻辑有了versionCode、updateType、minVersionCode之后判定逻辑就可以统一写成这样先看远端versionCode是否大于本地versionCode如果不大于直接进入游戏如果大于再看本地版本是否小于minVersionCode或者updateType是否等于1只要满足其中一个条件就走强制更新流程否则走可跳过更新流程。强制更新和可选更新的交互差异是决定性的。可选更新弹窗需要给三个按钮立即更新、以后再说、忽略此版本。点了忽略要记住当前版本号下次启动时相同版本不再弹窗可以通过PlayerPrefs存一个字段来实现。强制更新则不给任何关闭入口只显示更新内容和一个下载按钮用户不更新就停留在更新面板。这里有一个容易被忽略的细节服务器端如果只想对部分用户发灰度强更versionCode和minVersionCode可以配合用户ID的服务端逻辑动态生成不同清单而客户端代码完全不用改动。这就是把决策逻辑放到客户端比较、把决策数据放到服务端生成的价值所在。3. Unity端实现检测、下载、校验、拉起安装3.1 版本检测模块从网络拿版本清单版本清单下载我用UnityWebRequest.GET超时时间设成10秒。不建议用WWW类老API在新版本Unity里已经退居二线处理字节流和异常都不够清晰。下面是版本清单请求的协程实现private IEnumerator GetVersionData() { using (var uwr UnityWebRequest.Get(versionJsonUrl)) { uwr.timeout 10; yield return uwr.SendWebRequest(); if (uwr.result UnityWebRequest.Result.Success) { remoteVersion JsonUtility.FromJsonVersionData(uwr.downloadHandler.text); Debug.Log(版本清单: uwr.downloadHandler.text); } else { Debug.LogError(版本清单请求失败: uwr.error); } } }这里有一个经常会遇到的坑JsonUtility不支持直接在类字段上定义为属性而且对于可空字段支持很差。所以VersionData这个类必须设计成普通字段类型并且所有字段都要有默认值。像fileSize如果用longJsonUtility能正确解析。md5字段可能为空也没关系后续校验逻辑里做判空处理。另外服务器的HTTP响应头Content-Type不要设置成text/html之类的要用application/json否则某些网关或解析库会异常实际项目中我就遇到过CDN回源后Content-Type被改写导致Unity无法正常解析的问题。3.2 APK下载与进度回调下载大APK最忌讳的是把整个文件先读到内存再写磁盘务必用DownloadHandlerFileUnity会直接边下载边写文件内存占用控制在非常低的水位上。下载协程需要挂在MonoBehaviour上因为要持续返回yield null来刷新进度。核心代码如下private IEnumerator DownloadApk(string url, string targetPath) { var directory Path.GetDirectoryName(targetPath); if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory); using (var uwr UnityWebRequest.Get(url)) { var dhf new DownloadHandlerFile(targetPath); dhf.removeFileOnAbort true; uwr.downloadHandler dhf; uwr.timeout 60; var operation uwr.SendWebRequest(); while (!operation.isDone) { downloadProgress operation.progress; OnDownloadProgress(downloadProgress); yield return null; } if (uwr.result ! UnityWebRequest.Result.Success) { if (File.Exists(targetPath)) File.Delete(targetPath); Debug.LogError(APK下载失败: uwr.error); } } }DownloadHandlerFile的removeFileOnAbort必须设置成true这样下载被中断或失败时Unity会自动删除残留的临时文件否则你会在沙盒目录里累积大量损坏的.apk文件。下载进度用operation.progress就能拿到0到1的浮点数直接赋值给UI的Slider或Image.fillAmount。需要提醒的是operation.progress是一个平滑估算值在HTTP下载大文件时可能会有轻微延迟但对用户展示完全够用。如果你需要更精确的下载字节数可以自己解析服务器返回的Content-Length响应头再通过DownloadHandler的下载字节回调来算不过日常项目里没这个必要。还有一个值得做的优化是缓存校验。如果目标APK文件已经存在而且MD5和服务器返回的一致那就可以跳过下载直接进入安装流程。这就避免用户上次下载完成后没装、下次还傻傻地重新下载一遍。代码如下bool needDownload true; if (File.Exists(apkSavePath)) { var localMd5 GetFileMd5(apkSavePath); if (localMd5 remoteVersion.md5) needDownload false; else File.Delete(apkSavePath); }3.3 下载后校验MD5内容完整性的底线因为下载过程可能被网络波动、磁盘写入等原因破坏文件甚至CDN节点本身会返回损坏内容所以下载完成后必须做MD5校验。C#计算MD5没有平台差异直接用System.Security.Cryptography即可private string GetFileMd5(string filePath) { if (!File.Exists(filePath)) return ; using (var stream File.OpenRead(filePath)) using (var md5 MD5.Create()) { var hash md5.ComputeHash(stream); var sb new StringBuilder(); foreach (var b in hash) sb.Append(b.ToString(x2)); return sb.ToString(); } }对大文件调用ComputeHash会流式读完不会一次性把文件载入内存所以几百兆的APK算MD5也很快基本上两三秒内能出结果。校验不一致就直接删除文件不能修复也没有修复的必要。有一种情况需要重点留意服务器上传APK后如果自己重新生成MD5这个MD5必须是APK文件的二进制摘要而不是文件名或文件大小的哈希。我踩过一次坑运营直接在网上下载了个“MD5计算工具”算出来的结果格式是大写十六进制而客户端代码比较的是小写结果每次都判定文件损坏。建议服务端统一生成小写MD5或者客户端比较时统一转小写。3.4 拉起系统安装器的Android桥接安装APK这一步绕不开Android原生的Intent机制。Android 7.0之后直接用file://路径启动安装界面会抛FileUriExposedException必须通过FileProvider把真实路径包装成content://URI并临时授权给系统安装器读取。Unity C#侧用AndroidJavaObject调用Java接口的代码如下public void InstallApk(string apkPath) { #if UNITY_ANDROID !UNITY_EDITOR using (var player new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity player.GetStaticAndroidJavaObject(currentActivity)) using (var file new AndroidJavaObject(java.io.File, apkPath)) using (var fileProvider new AndroidJavaClass(androidx.core.content.FileProvider)) using (var uri fileProvider.CallStaticAndroidJavaObject(getUriForFile, activity, Application.identifier .fileProvider, file)) using (var intent new AndroidJavaObject(android.content.Intent)) { intent.CallAndroidJavaObject(setAction, android.intent.action.VIEW); intent.CallAndroidJavaObject(setDataAndType, uri, application/vnd.android.package-archive); intent.CallAndroidJavaObject(addFlags, 0x00000001); // FLAG_GRANT_READ_URI_PERMISSION intent.CallAndroidJavaObject(addFlags, 0x10000000); // FLAG_ACTIVITY_NEW_TASK activity.Call(startActivity, intent); } #endif }这里最关键的是FileProvider的authority也就是getUriForFile的第二个参数。它必须跟AndroidManifest里配置的provider authorities完全一致我用的是“包名.fileProvider”。如果两边不一致调用getUriForFile时会直接抛出IllegalArgumentException而且崩溃日志不容易一眼找到根因。准备好了APK路径之后授权flag必须加0x00000001系统安装器才能读写这个content URI0x10000000保证这个Intent是跨任务启动的没有这个flag在Unity的Activity上下文中启动会失败。3.5 Android工程配置FileProvider与安装未知来源权限Unity不像Android Studio那样直观管理Manifest但通过Assets/Plugins/Android/AndroidManifest.xml覆盖默认配置是完全可行的。先声明安装未知来源需要的权限和FileProvideruses-permission android:nameandroid.permission.REQUEST_INSTALL_PACKAGES /application provider android:nameandroidx.core.content.FileProvider android:authorities${applicationId}.fileProvider android:exportedfalse android:grantUriPermissionstrue meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider /application接着在Assets/Plugins/Android/res/xml/目录下新建file_paths.xml告诉FileProvider哪些路径可以对外暴露?xml version1.0 encodingutf-8? paths files-path nameupdate_apk pathUpdate/ / /paths这里和代码里的保存路径必须严格配套。我把APK下载到Application.persistentDataPath /Update/目录下在Android上它对应的是Context.getFilesDir()而 正好映射filesDir所以path写成Update/。如果只用外部公共存储目录Download文件夹就要用 但那样会引入存储权限问题分区存储之后特别麻烦。尽量把APK保存到应用沙盒目录FileProvider帮你把授权搞定省掉一堆运行时权限申请。4. Android适配与打包不同系统版本的更新坑4.1 Android 7.0FileUriExposedException的高发地带很多老项目的代码是在Android 6及更早机型上写的用Uri.fromFile直接抛给安装器。Android 7.0开始这种file://URI会触发FileUriExposedException应用直接崩溃。最气人的是这类崩溃只在部分国产ROM的特定版本上复现测试机不覆盖就发现不了。解决方案只能是用FileProvider没有捷径。如果你同时还在用第三方分享SDK或系统相册也需要检查它们的URI传输方式FileProvider的配置可以同时服务多个场景但authority要区分。还有一点Unity打包时如果发现自己添加的provider和某个SDK里的provider冲突会报DuplicateProvider错误这种通常发生在两个aar都声明了相同的authority排查时直接搜索“authorities”关键字即可。4.2 Android 8.0与更高版本未知来源权限的二次确认Android 8.0开始允许用户安装未知来源应用的权限被拆得更细每个应用都必须单独授权直接跳系统安装界面有可能被系统拦截没有任何提示。所以要给玩家一个明确的授权入口。Unity侧可以利用PackageManager的canRequestPackageInstalls方法判断是否已经授权private bool CanRequestPackageInstalls() { #if UNITY_ANDROID !UNITY_EDITOR using (var player new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity player.GetStaticAndroidJavaObject(currentActivity)) using (var pm activity.CallAndroidJavaObject(getPackageManager)) { return pm.Callbool(canRequestPackageInstalls); } #else return true; #endif }未授权时跳转系统设置页让用户打开“允许来自此来源的应用”开关public void OpenUnknownSourceSetting() { #if UNITY_ANDROID !UNITY_EDITOR using (var player new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity player.GetStaticAndroidJavaObject(currentActivity)) using (var intent new AndroidJavaObject(android.content.Intent, android.settings.MANAGE_UNKNOWN_APP_SOURCES)) using (var uri new AndroidJavaClass(android.net.Uri).CallStaticAndroidJavaObject(parse, package: Application.identifier)) { intent.CallAndroidJavaObject(setData, uri); activity.Call(startActivity, intent); } #endif }实际体验最好的做法是检测到未授权弹一个讲解弹窗解释为什么要开这个权限再跳转到设置页。返回游戏后再检查一次授权状态如果已经授权自动继续安装流程。注意这类特殊权限不能通过运行时权限弹窗直接授予必须走设置页。4.3 Android 10及以上分区存储对下载路径的影响Android 10开始强制分区存储应用不能随意往公共目录写文件但这对于我们的方案反而是利好因为APK从一开始就写在应用私有目录里绕开了所有分区存储限制。真正要注意的是FileProvider的 映射不要写错尤其不能把persistentDataPath误认为外部存储。Unity文档里Application.persistentDataPath在Android上就是/data/user/0/包名/files我用 覆盖这个目录正好合理。Android 11还有一个包可见性概念应用默认看不到其它应用Partial的package信息。不过系统安装器是系统组件ACTION_VIEW安装APK不依赖包可见性所以目前不需要额外声明QUERY_ALL_PACKAGES。但如果你在更新模块里还要做“判断本机是否已经安装某应用”之类的操作就需要在Manifest里添加 节点否则PackageManager查询不到。这个先留个印象等真遇到对应需求再补。4.4 Unity打包导出设置与Gradle依赖Unity导出Android工程时默认Gradle模板在高版本Unity中已经启用AndroidX。如果你的项目还在用Android Support库FileProvider的类名要改成android.support.v4.content.FileProvider依赖也要换成com.android.support:support-compat:28.0.0。我建议新项目一律走AndroidX因为Android Support库早已停止维护各家SDK也都在强制迁移。如果Unity构建时提示找不到androidx.core.content.FileProvider在Assets/Plugins/Android/mainTemplate.gradle的dependencies块里加一行dependencies { implementation androidx.core:core:1.9.0 }然后在ProjectSettings里确认选中“Custom Main Gradle Template”否则这个文件不会被应用。另外还有一个和APK更新直接相关的设置Player Settings里的Package Name必须和实际发布包名一致因为FileProvider的authority拼接和读取versionCode都依赖包名。测试包和正式包如果用了不同包名一定要把authority的取值也改掉不然会出现测试包安装不上、正式包FileProvider找不到URI的诡异问题。5. 实战问题排查更新失败基本都是这几类原因5.1 安装包打不开或点击更新无响应最常见的第一种现象是点击“立即更新”后系统没有任何反应也不弹安装器。先排查Intent有没有成功启动在AndroidManifest里给provider配置了exportedfalse这个是正确的不必改。问题通常出在FileProvider的authority不一致或APK路径不在file_paths配置的范围内。可以把Android日志过滤出来搜“ActivityNotFoundException”或者“SecurityException”基本能定位到。另一个原因是Unity侧的Activity上下文不对一定要用UnityPlayer.currentActivity不要自己new一个没有生命周期的Context。5.2 解析包出现错误“解析包出现错误”是用户更新反馈的高频词背后的原因主要有三类APK下载损坏、APK不是完整的安装包、安装包签名和当前应用不一致。如果走完了MD5校验仍然解析失败先怀疑签名问题。系统要求升级安装的APK签名必须和旧包一致否则会在安装阶段提示包冲突或签名不一致。很多团队为了方便测试用调试签名打包正式发布拿另一个keystore签名测试机上升级就翻车。解决方法是所有对外包统一用正式签名测试机卸载旧包再安装新包只能临时解决不能作为常态。我还会建议在解析失败之前用PackageManager的getPackageArchiveInfo先解析要安装的APK看看能不能拿到包信息和签名。能拿到说明文件完好且格式正确拿不到直接判定包损坏。这个检测在Unity的C#层里写起来稍显繁琐但确实能提前给玩家一个准确的错误提示。5.3 明明更新了却仍然弹更新框这通常不是逻辑问题而是服务器VersionData缓存或者客户端本地versionCode没有刷新。常见的坑是本地版本号从Unity的PlayerSettings.bundleVersion读取而不是从PackageManager读versionCode。如果导出时没有每次提升版本号服务器上已经是新版本地拿到的版本号还是旧值就会永远提示更新。解决方案就是统一用versionCode并且养成每次发版都在Unity里修改androidVersionCode的好习惯。另一个被忽略的缓存点在PlayerPrefs忽略此版本字段如果存的是versionCode而不是versionName当服务器推高版本时就不会误判为同一版本。如果存的是versionName字符串一旦版本名从1.0.1跳到1.0.2比较逻辑还要额外处理字符串容易出错。统一用整数versionCode存干净利落。5.4 大版本更新时报存储空间不足下载大APK之前不检查空间走到一半系统提示空间不足然后删除文件让用户重新下载体验极差。做法是在下载前通过AndroidJNI调用StatFs获取应用沙盒所在分区的可用字节数private long GetAvailableStorageBytes() { #if UNITY_ANDROID !UNITY_EDITOR try { using (var player new AndroidJavaClass(com.unity3d.player.UnityPlayer)) using (var activity player.GetStaticAndroidJavaObject(currentActivity)) { var statFs new AndroidJavaObject(android.os.StatFs, activity.CallAndroidJavaObject(getFilesDir).Callstring(getAbsolutePath)); long blockSize statFs.Calllong(getBlockSizeLong); long availableBlocks statFs.Calllong(getAvailableBlocksLong); return blockSize * availableBlocks; } } catch (Exception e) { Debug.LogError(获取存储空间失败: e.Message); return long.MaxValue; } #else return long.MaxValue; #endif }用remoteVersion.fileSize和这个可用空间做比较如果空间不足直接提示清理同时把要下载的APK大小显示给玩家。注意这个检查只能作为预检下载过程中仍然可能被其它应用占用空间挤掉所以下载协程里依然要对WriteError等情况做兜底。5.5 常见错误速查表现象可能原因处理方式下载失败提示域名解析失败Unity所在的网络栈无法解析CDN域名检查域名HTTPS证书是否有效确认CDN回源正常安装时闪一下白屏就没了FileProvider权限或Intent action异常检查provider配置和setDataAndType参数看Android日志APK下载完成后MD5一直不对CDN节点缓存了旧文件或内容被改写清理CDN缓存重新上传APK并生成MD5弹更新框后无论怎么点都进不去Android上下文丢失或协程未正确停止检查MonoBehaviour对象是否被销毁协程生命周期要跟随场景根节点强制更新弹窗可以点击空白处关闭没有做模态处理Android返回键未屏蔽强制更新页面屏蔽返回键和桌面键在Unity层能做到一层系统级无法完全锁死但可以强化提示注意强制更新并不能真正意义上“锁死”Android返回键和桌面Home键去防止用户退出应用这是系统限制。合理做法是进入强制更新面板后不断让UI置顶同时后台服务端记录玩家退出行为后续通过补偿策略引导回归。结尾这套方案在真实项目里的落地经验我最早做APK更新时想得很简单下载完Intent打开就行结果单是FileProvider适配就折腾了两天。后来把所有逻辑收敛成一套UpdateManager模块才发现之前的大部分问题都不在下载本身而在版本数据协议、路径映射和权限适配这些“看不见的细节”上。像我这种只维护一个Unity客户端的人最舒服的维护模式是版本清单由后端一次性配置客户端只信versionCode和MD5所有下载路径都限制在应用沙盒目录内更新面板的文案统一从releaseNotes读。这样每次发版我只需要在构建机更新一次版本号上传APK和md5到CDN然后改一行JSON配置整个更新流程就自动生效不用再碰客户端代码。最后还有一个容易被忽略的小技巧更新面板的下载按钮在开始下载后建议把进度数字和百分比都展示出来同时允许用户取消下载。如果用户中途取消删除半成品文件并回到初始状态。这个“可取消”能力看着很小但在大包场景下非常影响用户体验能大幅减少因网络不稳定导致的差评。希望这篇文章能把你的APK更新链路一次搭稳后面换成商店分发或者做差分更新这套骨架也能顺势平滑过渡。

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

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

免费获取报价 →
↑