很多人做安卓自动化脚本,都经历过这种场景:脚本部署到几十台手机上,突然发现一个逻辑 bug 或者想加个新功能,只能重新打包 APK、一个个传包、让用户重新安装。一台两台还能忍,几十台上百台就是灾难。
热更新就是解决这个问题的------不重新打包 APK,直接把编译后的代码文件下发到手机。这篇文章把安卓脚本热更新的完整链路讲清楚:怎么配置、服务端怎么写、怎么自动更新、怎么在脚本内手动触发。
一、先搞清楚:热更新热的是什么
很多人第一次接触"热更新"会误以为是要更新 APK,其实不是。热更新的是编译后的 iec 文件,不是打包的脚本 APK。
APK 只是壳,装一次就不用动了。真正的业务逻辑编译成 iec 文件,通过热更新机制下发。这样做的好处非常直接:
- 免发版:改代码不用重新走打包、签名、分发流程;
- 秒级下发:用户下次启动脚本,自动拉取最新代码;
- 故障止损:线上出问题,改一版代码立刻覆盖,不用等用户手动升级。
二、配置文件:update.json 是热更新的入口
在工程目录下找到(或新建)update.json,热更新的所有基础配置都在这里:
json
{
"update_url": "http://baidu.com/update",
"version": "1.0.0",
"appendDeviceInfo": true,
"timeout": 30000
}
各字段含义:
| 字段 | 说明 |
|---|---|
update_url |
服务端更新接口地址,需要自己编写服务端接口(或用官方热更新服务) |
version |
当前脚本的版本号 |
timeout |
请求超时时间,单位毫秒,最低 1000 |
appendDeviceInfo |
是否在请求时附加设备基础信息 |
把 appendDeviceInfo 设为 true 后,请求会自动带上设备参数,方便服务端做差异化下发。请求地址会变成类似:
ini
http://baidu.com/update?version=1&deviceId=7521e5d9eeec4f58b71dea8b78c414d5&apkVersion=9.22.0&osVersion=12&pkgName=com.gibb.easyclick&model=LNA-AL00&ecVersion=9.22.0&brand=HUAWEI&androidId=82a3b055470ebe1a
参数含义:
| 参数 | 说明 |
|---|---|
version |
当前运行的 iec 版本 |
deviceId |
EC 生成的设备 ID(可能丢失,不一定能作为设备唯一标识) |
apkVersion |
APK 打包版本 |
osVersion |
系统版本 |
ecVersion |
实际使用的 EC 版本 |
pkgName |
打包的包名 |
model / brand |
机型与品牌 |
androidId |
Android ID |
三、服务端怎么写:两种返回格式
配置好后,打包运行,客户端会自动用 GET 方式请求 update_url 并带上版本参数,例如 http://baidu.com/update?version=1.0.0。版本比较逻辑在服务端自行处理。
服务端返回分两种情况:
1. 无需更新 ------直接返回空字符串即可,不要返回 JSON。
2. 需要更新------返回更新信息 JSON:
json
{
"download_url": "http://baidu.com/aaa.iec",
"version": "1.1.0",
"dialog": true,
"msg": "优化部分问题",
"force": false
}
字段说明:
| 字段 | 说明 |
|---|---|
download_url |
新包的下载地址 |
version |
新包的版本号 |
dialog |
是否用对话框形式展示更新提示(true 弹窗 / false 静默) |
msg |
对话框中要显示的消息 |
force |
对话框模式下是否强制更新(true 无法取消) |
客户端拿到这个 JSON 后,会下载最新的 iec 包并加载使用。
担心下载失败?用严格模式校验 MD5:
json
{
"download_url": "http://baidu.com/aaa.iec",
"version": "1.1.0",
"dialog": true,
"msg": "优化部分问题",
"force": false,
"md5": "服务器自行校验的iec文件的md5值",
"download_timeout": 60
}
md5:iec 文件的 MD5 值,有该字段会强制校验文件完整性,保证下载的文件一定完整;download_timeout:下载超时时间(秒),不填默认 60 秒。
四、两种更新时机:启动自动更新 + 脚本内手动更新
4.1 UI 启动更新
只要 update.json 配置无误,用户打开脚本界面时会自动请求更新,无需任何额外代码。这是最常用的方式------用户打开脚本,最新代码已经在路上了。
4.2 脚本内热更新
脚本执行期间也可以做热更新,配合代码触发。核心流程是:请求更新 → 下载新包 → 重启脚本。
javascript
function main() {
// 从项目文件的 update.json 中获取版本号
let version = JSON.parse(readIECFileAsString("update.json")).version
toast("Hello World - " + version);
// 请求服务器是否有新版本(使用自定义 url 模式)
let updateResult = hotupdater.updateReq("http://baidu.com", version, true, 9000);
logd("请求更新是否有: " + updateResult);
if (!updateResult) {
logw("请求失败错误信息: " + hotupdater.getErrorMsg());
} else {
// 有更新,下载新的版本
let path = hotupdater.updateDownload();
logd("下载路径为: " + path);
if (!path) {
logw("下载IEC文件错误信息: " + hotupdater.getErrorMsg());
} else {
// 重启脚本,加载新版本
restartScript(path, true, 3)
return;
}
}
}
main();
脚本内热更新用到的方法:
| 方法 | 作用 |
|---|---|
hotupdater.updateReq(updateUrl, version, appendDeviceInfo, timeout) |
请求更新接口,返回 true 表示需要更新,false 可能是无需更新或请求失败(用 getErrorMsg 查看具体信息) |
hotupdater.updateDownload() |
下载热更新返回的 iec 文件,返回下载后文件路径 |
hotupdater.getUpdateResp() |
获取热更新的请求结果 |
hotupdater.getErrorMsg() |
获取请求或下载的错误信息 |
updateReq 的第一个参数不传时,会使用 update.json 里配置的数据;version 建议使用整型数字。下载完成后调用 restartScript(path, true, 3) 重启脚本,新代码立即生效。
五、几个容易踩的坑
- 版本号要一致 :
update.json里的版本号必须和服务端接口返回的版本号逻辑保持一致,否则可能更新异常; - 无需更新别返回 JSON:服务端判断无更新时直接返回空字符串,返回 JSON 会被当成更新数据处理;
- 设备指纹别当唯一标识 :
deviceId是 EC 生成的,可能丢失,做设备绑定类逻辑不要只依赖它; - 生产环境注意时效 :建议把
appendDeviceInfo打开,按设备维度控制灰度发布,避免一次全量下发出问题。
六、总结
热更新是安卓脚本从"个人玩具"走向"商业产品"的关键能力:
- 配置一次,长期受益:update.json 配好后,后续每次迭代都走热更新,告别打包传包;
- 两种更新方式:UI 启动自动更新适合绝大多数场景,脚本内手动更新适合对时机有要求的业务;
- 严格模式更稳:生产环境建议带上 md5 校验,防止下载不完整导致脚本异常;
- 不想写服务端? 直接用官方热更新服务(对接阿里云 OSS),上传 iec 文件即可,自动生成下载地址和 md5。
对于把脚本卖出去、部署到几十上百台设备的团队,热更新不是"锦上添花",而是运维效率的刚需。改一行代码要重装一次 APK 的时代,该翻篇了。