尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HBuilder X 版本更新实战:wgt热更与APK整包更新避坑指南

HBuilder X 版本更新实战:wgt热更与APK整包更新避坑指南 简介面向使用HBuilder X进行应用开发与更新的开发者这份资源集中提供了热更资源及APK安装包所需的各类文件可帮助解决版本迭代时资源同步、界面更新和安装包生成等常见问题。包内共35个文件涵盖PNG图片、JS脚本、CSS样式、JSON配置、HTML页面和TTF字体等主要类型总大小约718KB图片用于界面视觉素材JS负责交互逻辑CSS定义页面布局JSON保存项目与热更配置HTML构建页面骨架TTF补充个性字体整体目录结构清晰便于按需查找。目前已有2910人学习/下载资源中包含manifest.json等关键配置文件和与说明文档的配合使用路径适合需要快速掌握热更新机制、理顺APK打包流程的初中级开发者也能为多版本维护提供直接参考减少反复排查时间提升版本发布效率。 做 HBuilder X 开发的人版本更新这事迟早会撞上。它不是简单在 manifest 里改个版本号就完事——热更资源走的是 wgt 资源包更新安装 APK 走的是整包更新两套逻辑混在一起处理时最容易翻车。用户端会出现“点了升级没反应”“下载完装不上”“安装后自动回退版本”等各种莫名其妙的问题。这篇东西把我自己的做法、踩过的坑、排查思路都整理一遍给还在为版本更新挠头的朋友做个参考。1. 版本更新的整体思路热更资源和 APK 是两条线先用一句话把概念理清楚用 HBuilder X 开发 App最后产物是一个 5 App 或 uni-app 打包的安装包。更新服务器时你面对的是两套完全不同的东西——热更资源包和 APK 整包。热更资源最典型的就是wgt文件它是 HBuilder X 专属的资源包格式打包后只有前端代码和数据文件体积小、更新静默用户几乎无感。APK 则是完整的安装包用户需要手动下载、确认权限后安装。这俩的用户体验、审核要求、实现难度差别很大。对比项热更wgt整包APK更新内容仅前端资源JS/CSS/HTML/图片等整个 App含原生层用户操作无感后台下载后重启生效需要下载安装包并确认安装更新速度快差量资源包通常几百 KB 到几 MB慢APK 动辄几十上百 MB应用市场兼容部分市场不允许或限制正常走市场审核流程失败影响低最多资源不生效高用户装不上或闪退影响面大我现在的项目策略很简单能用热更解决的一定走热更省时省力省审核。但涉及原生层的东西再麻烦也得老老实实打 APK 走整包更新。1.1 为什么大多数迭代优先走热更热更的优势用一次实际经历就能说透。之前我们上线了一个活动页面当天运营发现有个按钮跳转地址配错了前端代码改了一行。如果是整包更新改这一行代码也要重新打包、签名、上传应用市场审核周期少说一两天活动早黄了。走热更改完代码在 HBuilder X 里打个 wgt 包传到服务器App 下次启动时检测到版本号变化自动拉取十分钟内线上全量生效。这在业务迭代里是常态。只要不改 manifest.json 里的模块配置、不新增原生插件、不调整权限声明单纯改页面逻辑、修 bug、换图片热更都是首选方案。用户不用去应用市场看更新列表也不用下载安装对体验的打扰几乎为零。1.2 什么时候必须老老实实打 APK热更不是万能的这一点踩过坑才有深刻体会。它本质只能更新前端代码一切涉及原生底层的改动都必须走整包。必须打 APK 的情况我总结了几类一是 manifest.json 中的 AppID 发生变化二是新增或删除了原生插件三是模块权限有改动比如原本没有相机权限后来加了四是图标、启动图、App 名称这些基本信息变了。这些配置是在原生层生效的wgt 包根本没有能力去改。这里有个新手最容易踩的坑在页面上加了 uni.scanCode 或 uni.chooseImage 这类 API觉得只是前端操作打完 wgt 包热更上去结果真机上摄像头调不起来。原因很简单扫码和相册权限属于原生模块基础模块里没勾选或原生层没声明热更永远补不上。所以热更上线前一定要确认这次改动有没有碰到原生能力。另外 iOS 那边也得单独说一句App Store 生态不允许通过热更下载 wgt 资源包来更新应用强行做只会被拒审。苹果应用只能引导用户去 App Store 下载新版这是平台规则层面的硬限制。2. 更新前的关键准备版本号管理和升级服务器接口约定做更新系统第一件事不是写代码是把版本号规则定好。很多人觉得版本号不就是 manifest.json 里填个数字嘛但实际操作里它有两个字段要区分清楚。manifest.json 的基础设置里有“版本号”和“版本名称”两个概念。版本名称是给人看的比如1.2.0版本号是给程序判断用的通常是一个整数比如12。热更判断时拿的是版本号不是版本名称。{ versionName: 1.2.0, versionCode: 12 }这个版本号有两条铁律必须比线上已发布的版本大且只能递增不能回退。我有一次为了测试把版本号从 12 改成 11 再打 wgt 包结果用户端死活不触发更新排查了半天才反应过来是版本号比线上还小更新逻辑直接认为“本地已经是最新版”。版本号递增还有一个实际心得每次打包前先改版本号再执行打包操作。因为 HBuilder X 的 wgt 包制作是以 manifest.json 里的配置为准的如果先打包装后改版本号这个包实际记录的版本号还是旧值上传到服务器后永远无法覆盖线上的旧资源。2.1 资源版本号和 App 版本号的分离管理热更场景下我建议维护两套版本号一套是 App 版本号对应整包更新另一套是资源版本号对应 wgt 包更新。两者可以完全独立比如 App 一直停留在 1.0.0但资源版本已经从v1迭代到v12了。这在实际项目中太常见了——App 发布频率低业务迭代快全靠热更在撑。我习惯把资源版本号放在前端的配置文件中维护比如config.jsconst APP_CONFIG { // 资源版本号每次热更必须递增 resVersion: 1.0.12, // App 版本号对应 manifest 中的 versionCode appVersion: 12 }热更检测时服务器返回最新的资源版本号客户端拿它和本地resVersion对比一致就跳过不一致就下载新 wgt。这里有个细节本地资源版本号不能写死后再打包否则每次热更完重启 App配置又重置回旧值就会出现“明明更新了重启后还是旧版”的诡异问题。建议用uni.getStorageSync存储服务端下发的资源版本号更新成功后同步写入本地。2.2 升级服务器接口应该返回什么信息升级服务器其实不需要太复杂我常用的就两个接口。第一个用于 App 检查整包更新第二个用于检查热更资源包# 检查整包更新返回最新 APK 信息 GET /api/app/version/latest?typeapk # 检查热更资源包返回最新 wgt 信息 GET /api/app/version/latest?typewgt接口返回的 JSON 结构大致如下{ code: 0, data: { versionCode: 13, versionName: 1.3.0, url: https://download.example.com/app/1.3.0/app-release.apk, forceUpdate: true, updateLog: 修复若干 bug优化体验, wgtUrl: https://download.example.com/update/1.0.12.wgt } }字段含义不复杂versionCode用于和本地版本号比较wgtUrl返回 wgt 包的下载地址forceUpdate标记是强制更新还是可选更新。有一个经验值得分享wgt 包的下载地址建议带上wgt的完整文件名和版本号比如1.0.12.wgt而不是update.wgt。原因有两点一是可以避免服务器缓存覆盖问题二是出问题时方便在浏览器里直接访问 URL 排查包是否存在。3. 热更资源的生成、上传和客户端检测逻辑热更的具体操作分三步本地打 wgt 包、上传到服务器、客户端检测并安装。每一步都有细节漏一个都会出问题。3.1 在 HBuilder X 中生成 wgt 资源包wgt 包的生成入口在 HBuilder X 顶部菜单栏发行 - 原生App - 制作应用wgt包。点击后 HBuilder X 会自动编译工程几分钟后弹出输出目录里面就是.wgt格式的资源包。制作 wgt 包前务必确认三点一是 manifest.json 里的版本号已经改大二是本次改动没有涉及原生模块配置三是代码没有引用新的原生插件。这三点是我反复踩坑后形成的检查清单每次打 wgt 包前都过一遍。顺带提一个细节wgt 包里不要包含unpackage目录中的旧构建产物HBuilder X 默认会排除但如果你手动修改过工程目录最好检查一下。否则打包出来体积莫名变大下载也慢。3.2 上传服务器不要随手丢根目录上传 wgt 包到服务器不建议直接覆盖同名文件。我现在的做法是按版本号建目录/update/wgt ├── 1.0.10.wgt ├── 1.0.11.wgt ├── 1.0.12.wgt这样做的价值在于灰度和回滚方便。想让一部分用户先更新就控制接口对特定白名单返回新版本的wgtUrl想回滚服务器接口直接返回上一版本的 URL 即可客户端自然去下载旧包。如果只有一个update.wgt想回滚只能重新打一个旧版本的包操作成本和失误率都会明显上升。3.3 客户端检测更新与安装 wgt 包的完整代码热更的核心代码不复杂核心是plus.runtime的几个 API。贴一份我项目里在用的代码// 获取当前 App 版本信息 function getLocalVersion() { return new Promise((resolve, reject) { plus.runtime.getProperty(plus.runtime.appid, (widgetInfo) { resolve({ version: widgetInfo.version, // manifest 中填写的版本号 versionName: widgetInfo.versionName // 版本名称 }); }); }); } // 检查热更资源 async function checkWgtUpdate() { const local await getLocalVersion(); uni.request({ url: https://api.example.com/api/app/version/latest?typewgt, method: GET, success: async (res) { if (res.data.code ! 0) return; const remote res.data.data; // 版本号比较大小时注意字符串转数字 if (parseInt(remote.versionCode) parseInt(local.version)) { downloadWgt(remote.wgtUrl); } } }); } // 下载并安装 wgt 包 function downloadWgt(url) { uni.downloadFile({ url: url, success: (res) { if (res.statusCode ! 200) { console.error(wgt 下载失败, res.statusCode); return; } plus.runtime.install(res.tempFilePath, { force: false }, () { uni.showModal({ title: 更新完成, content: 新版本已就绪重启应用后生效, showCancel: false }); }, (err) { console.error(wgt 安装失败, err); }); } }); }plus.runtime.install是热更安装的关键方法它接受两个参数第一个是 wgt 包路径第二个是选项对象。force: false表示静默安装安装完不会自动重启等用户下次冷启动时加载新资源。如果设成true安装完成后 App 会强制重启体验比较暴力除非是紧急修复否则不建议用。res.tempFilePath是临时文件路径plus.runtime.install安装的是这个路径下的 wgt 文件。这里有个容易忽略的点uni.downloadFile下载到临时目录后理论上可以由系统自动清理但如果下载失败或安装失败临时文件可能残留占用空间。我在安装结束后会主动调用plus.io.resolveLocalFileSystemURL清理临时文件这个是在实际项目中遇到存储空间异常上涨才发现的。4. APK 安装包的打包和分发从本地到用户端的完整路径当改动涉及原生层或者用户需要去应用市场下载新版本时就要回到 APK 整包更新的路子上来。这块的坑更多打包方式选型、安装权限、签名一致性任何一个都能让用户停留在旧版本上。4.1 云打包和本地打包怎么选HBuilder X 打 APK 有两条路云打包和本地打包。云打包在菜单栏 发行 - 原生App云打包不需要本机安装 Android Studio 和 SDK配置好证书后把工程传到云端构建。本地打包则是 发行 - 原生App 本地打包需要自己生成 Android 工程配合 Android Studio 完成构建。对多数人来说云打包是首选门槛低、速度快。我最初用云打包是因为电脑上没装 Android SDK也不想为了一次打包去折腾几十个 GB 的开发环境。但本地打包有它的不可替代性如果你的项目里集成了一些自定义原生插件或者需要深度定制原生工程比如修改 AndroidManifest.xml 里的自定义权限、接入自家的签名体系云打包就满足不了了。我现在的做法是纯 uni-app 项目用云打包涉及自定义原生模块的项目用本地打包。4.2 APK 安装测试的几种方式开发阶段装 APK 到手机最省事的当然是数据线连接手机在 HBuilder X 里直接运行到手机。但如果测试包已经生成或者你要把 APK 给测试同事以下三种方式我都试过第一种手机浏览器或扫码下载。把 APK 放到公司内网服务器上生成二维码手机扫码后浏览器下载安装。这种方式最接近用户真实场景适合做整体流程验证。第二种Android 模拟器。对没有真机的场景很友好直接把 APK 拖进模拟器窗口模拟器会自动安装。这种方式写 UI 自动化脚本时更常用。第三种adb 命令行安装。对开发和测试来说效率最高顺手把常用的三条命令列一下# 通过 USB 连接的设备列表 adb devices # 安装 APK-r 表示覆盖安装保留数据 adb install -r app-release.apk # 通过 IP 连接局域网中的 Android 设备 adb connect 192.168.1.100:5555adb install报错时信息非常关键。INSTALL_FAILED_UPDATE_INCOMPATIBLE说明旧包签名不一致通常要先卸载旧版再安装INSTALL_FAILED_INSUFFICIENT_STORAGE是设备存储空间不足FAILED_INVALID_APK则可能因为 APK 下载不完整或本身就是损坏文件。这些提示能直接帮你锁定问题方向比用户那边一句“装不上”有效太多。4.3 Android 8 以后安装未知来源应用的处理APK 分发到用户手机上后最大的拦路虎是权限设置。Android 8.0 之后从浏览器或第三方渠道下载 APK 时系统默认会拦截提示“禁止安装未知来源应用”。这个问题不解决用户下载完安装包点了没反应体验直接打折。正确做法是在代码里主动引导用户授权。核心代码是跳转到系统设置中的安装未知应用页面function gotoInstallPermission() { // Android 8.0 及以上的处理方式 plus.runtime.openURL(package:// plus.android.runtimeMainActivity().getPackageName() /com.android.settings); }短信和写法上有不同版本兼容所以我常常在项目中加一层判断先检测当前是否可以直接安装被拦截时再跳转设置页。用户授权后回到 App 继续安装流程整体感受会顺很多。另外APK 的下载地址必须走 HTTPS。虽然只是内部分发不涉及上架规范但 HTTP 明文传输容易被运营商或热点链路劫持下载回来的文件和解压后的内容可能被篡改。我们生产环境曾经遇到过 Android 机型下载 APK 后提示“包损坏”查到最后就是部分网络下 HTTP 传输被劫持导致包体不完整。切到 HTTPS 之后再没出现过。如果 App 需要上架应用市场分发逻辑就变了。应用市场有自己的更新通道不能再在 App 内弹窗引导下载 APK否则会被拒审。市场内更新走市场自身的机制App 内的整包更新逻辑只服务于企业内部分发或测试环境这层区分要理清楚。5. 常见问题与排查实录热更和安装 APK 的典型翻车现场这两条线路上的典型问题我整理成了一个速查表每个问题都是实际遇到过的排查思路也是验证过的问题现象可能原因排查步骤及解决热更后重启 App版本还是旧的版本号没递增或代码里缓存了旧的资源版本号检查 manifest 中的版本号和config.js中的resVersion是否比线上大清掉 App 缓存后重试热更后白屏或部分页面报错wgt 包里混入了原生层改动或新增 API 在原生层不存在热更只更新前端资源原生模块、权限、SDK 相关改动必须打整包wgt 下载失败或一直转圈服务器返回的 URL 不可达或 wgt 文件不存在浏览器直接访问接口返回的wgtUrl确认文件能下载且非空Android 下载完 APK 提示“解析包错误”APK 下载不完整、文件被劫持、文件名后缀异常确认 HTTPS 下载复查包大小是否和服务器一致重新打包签名用户手机上提示“应用未安装”签名证书不一致或覆盖安装时签名冲突测试签名与正式签名必须一致已装的旧版签名不同需先卸载再安装Android 8 以上点击 APK 无反应未授予“安装未知来源应用”权限跳转系统安装未知应用设置页引导用户开启安装新包后数据丢失adb install -r未保留数据或系统自动恢复失败覆盖安装时保留包名和签名一致重要数据提前做迁移逻辑iOS 用户不会收到热更苹果不支持 wgt 热更引导用户去 App Store 更新版本5.1 热更不触发的排查顺序热更是最常出问题的环节现象是用户端完全没有更新迹象。我自己的排查顺序是先看本地版本号再看接口返回。本地那条线要确认 manifest 中的版本号真的比线上大并且没有在代码里用内置配置覆盖了从服务器存储的资源版本号。接口那条线要确认请求确实发出去了服务器有正常返回。如果两条线都对还没有触发那就检查plus.runtime.install是否真正执行到了。在 HBuilder X 的调试面板里打日志是最快的办法。很多时候问题出在force参数配置上如果之前版本把force设成true安装完成后 App 已经自动重启了后续代码里的重启提示弹窗自然看不到误以为没更新。5.2 APK 下载完成却无法安装的处理经验这类问题在 Android 碎片化环境下特别常见。我遇到过的一个典型案例是用户华为手机上提示“解析软件包时出现问题”同一个 APK 在小米手机上安装正常。网上很多答案都指向 APK 下载不完整或签名问题但我们签了名也换了下载方式都没解决。最后定位到是部分机型对 APK 内的 targetSdkVersion 和系统版本兼容性敏感。HBuilder X 云打包时默认 targetSdkVersion 偏高部分老旧机型和定制 ROM 兼容性差可以尝试在云打包配置里降低 targetSdkVersion 或改用兼容性更好的 Android 版本打包。这个不是标准流程但实测能解决部分“解析包错误”问题值得记下来。5.3 灰度更新和回滚的小技巧上线不只是“发一个包”这么简单尤其是热更这种无感更新用户根本没意识到自己更新了出问题也难主动反馈。我的习惯是分批次放量先在接口层做一个简单的灰度逻辑比如根据用户 ID 的尾号先让 10% 的用户拿到新 wgt 包观察一两天没问题再全量放开。回滚也要能自动化。因为 wgt 包是按版本号存放的一旦发现新包有问题让服务器接口直接返回上一个版本的下载地址客户端检测到版本号小于本地时就不会重新下载已经在用新包的用户继续用有 bug 的版本但新增用户和未更新的用户不会进入坑里。配合运营发通知推动重启基本能在小时内止血。这套灰度机制在服务器端只是配置一个百分比阈值的事但能避免很多线上事故的尴尬局面。说到底HBuilder X 的版本更新其实不是一个技术难题而是一个工程规范问题。把版本号规则定死、把热更和整包的分界线画清楚、把下载和安装的异常场景都要考虑到后面每次发版其实就是执行一套固定流程了。我自己在实际操作中最大的体会是永远先在测试环境完整走一遍热更和 APK 安装的所有步骤再考虑放量上线省下来的绝对不是一两个小时而是一整天的救火时间。本文还有配套的精品资源点击获取
返回列表