安卓脚本开发脚本分发

安卓脚本热更新怎么做?不重新打包 APK 也能更新代码

不重新打包 APK 也能把新代码下发到用户手机:update.json 配置、服务端接口返回格式、UI 启动自动更新与脚本内手动更新的完整流程。

约 8 分钟

热更新热的是 iec,先看 update.json

脚本已经装到几十台手机上,突然要改一个逻辑 bug,或者加一个功能。常规做法是重新打包 APK,一台台传包,让用户重装。台数一多,这个过程很慢。热更新不用重装 APK,把编译后的代码文件直接下发到手机。

EasyClick 工作流画布展示代码热更新与版本管理流程

热更新下发的是编译后的 iec 文件,不是 APK。APK 是壳,装一次即可。业务逻辑编译成 iec,再通过热更新下发。改完代码不用重新打包、签名、分发。用户下次打开脚本会拉最新代码。线上出了问题,改一版就能盖上去,不用等用户自己升级。

在工程目录下找到(或新建)update.json,热更新的所有基础配置都在这里:

{
  "update_url": "http://baidu.com/update",
  "version": "1.0.0",
  "appendDeviceInfo": true,
  "timeout": 30000
}

各字段含义:

字段 说明
update_url 服务端更新接口地址,需要自己编写服务端接口(或用官方热更新服务)
version 当前脚本的版本号
timeout 请求超时时间,单位毫秒,最低 1000
appendDeviceInfo 是否在请求时附加设备基础信息

把 appendDeviceInfo 设为 true 后,请求会自动带上设备参数,方便服务端做差异化下发,地址会变成类似:

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。版本怎么比,在服务端自己处理。

服务端返回分两种。不用更新,直接返回空字符串,不要返回 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。返回里加上 md5(iec 文件的 MD5 值,有这个字段会强制校验文件完整性)和 download_timeout(下载超时时间,秒,不填默认 60 秒):

{
  "download_url": "http://baidu.com/aaa.iec",
  "version": "1.1.0",
  "dialog": true,
  "msg": "优化部分问题",
  "force": false,
  "md5": "服务器自行校验的 iec 文件的 md5 值",
  "download_timeout": 60
}

更新时机有两种。

UI 启动更新不用写代码。update.json 配好之后,用户打开脚本界面就会自动请求更新。多数情况用这种方式。

脚本内热更新是脚本执行期间用代码触发,流程是请求更新、下载新包、重启脚本:

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 会被当成有更新。

deviceId 是 EC 生成的,可能丢失。做设备绑定不要只靠它。

生产环境打开 appendDeviceInfo,按设备做灰度,不要一次全量下发。

update.json 配好之后,后面的改动都走热更新,不用每次重新打包传包。打开界面时自动更新,适合大多数情况。脚本里自己请求更新,适合要卡时间点的业务。生产环境带上 md5,避免下到不完整的文件。不想自己写服务端,可以用官方热更新服务,对接阿里云 OSS。上传 iec 之后,下载地址和 md5 会自动生成。脚本要卖出去、要铺到几十上百台手机时,省掉的是反复打包、传包的时间。

想要真实跑起来?

本文介绍的方案均可基于 EasyClick 能力在 iEasyClick 落地。官网提供完整文档、开发工具与自动化产品,免费体验。

访问 iEasyClick 官网 →