场景实践

最近修改时间2023-04-03 01:51:34

优化原生和热更包体积

iOS 原生包优化(ipa)

在导出 ipa 以便上传到 pushy 服务时,可以取消 bitcode 选项以大幅减小 ipa 大小。上架 app store 或者通过 testflight 分发测试包时仍然可以保留 bitcode 选项,不影响热更新。

bitcode

Android 原生包优化(apk)

apk 的优化主要考虑两个方向:

  • 启用 proguard 压缩混淆源码。但这一步可能导致一些使用反射的代码运行时报错,启用后需要充分测试每个页面和功能,以及需要阅读一些第三方关于 proguard 的特别设置说明。
  • 分开编译不同的 cpu 架构。找到android/app/build.gradle中的 cpu 架构部分,如下所示启用enable选项:
splits {
    abi {
        reset()
-       enable enableSeparateBuildPerCPUArchitecture
+       enable true        // 启用单独的 cpu 架构编译
        universalApk false  // If true, also generate a universal APK
    }
}

如此一来会在编译目录中输出多个 apk 文件,分发和上传到热更新服务时只需要使用app-arm64-v8a-release.apk文件,可以大幅减小 apk 的大小。

热更新包优化(ppk)

热更新包的主要内容是 js 包和其所引用的静态资源(主要是图片)。

  • js 包成分分析。可以借助一些第三方工具(如react-native-bundle-visualizer)来分析 js 文件中哪些占比较大,是否可以用其他库替换等(如 dayjs 替换 moment,lodash-es 替换 lodash)。
  • 图片优化。

有很多渠道包需要热更,如何操作比较方便?

  1. 如果渠道包的js代码和初始资源有差别(无论多么细微的差别都会生成不同的jsbundle),那么只能单独生成 apk,分别上传和绑定。可以考虑写一些脚本自动调用 cli 来执行批量操作。
  2. 如果渠道包的js代码和初始资源完全一致,可以考虑使用Flavor构建,或其他一些动态生成渠道包的方案(比如腾讯的 VasDolly美团的 walle等),这样所有的渠道包基于同一个基础 apk 生成(因而会有相同的编译时间戳和jsbundle)。这样可以只用上传一个基础 apk,对此 apk 的热更操作可以对所有渠道包生效。

如何支持 aab 格式的原生包?

如果您需要使用 aab 格式的 android 原生包,那么可以在上传到 Google play 之后,在其控制台中下载转换后的 apk 格式(见下图),然后将这个 apk 包上传到热更新的后台,即可正常支持热更新。

aab

CI 的集成

在开发环境中,每次 bundle 都会生成一个不同名字的 ppk 文件,这不利于持续集成(CI)系统的引入。

要解决这个问题,你可以使用--output参数来指定输出 ppk 文件的名字和路径,便于进行自动发布。

测试、发布与回滚

我们强烈建议您先发布一个测试包,再发布一个除了版本号以外均完全相同的正式包

例如,假设我们有一个正式包,版本为1.6.0,那么可以修改版本号重新打包一个1001.6.0,以一个明显不太正常的版本号来标识它是一个测试版本,同时后几位相同,可以表明它和某个正式版本存在关联(内容/依赖一致)。

在每次往发布包发起热更新之前,先对测试包1001.6.0进行更新操作,基本测试通过之后,再在网页后台上将热更包重新绑定到正式包1.6.0上。如果在测试包中发现了重大问题,你就可以先进行修复,更新测试确认通过后再部署到正式线上环境。这样,可以最大程度的避免发生线上事故。

万一确实发生线上事故需要回滚的话,首先利用版本控制系统回滚代码到正常的状态,然后重新生成热更包并推送即可。

元信息(Meta Info)的使用

在发布热更新版本时,或者在网页端,你可以编辑版本的元信息。这是一段在检查更新时可以获得的字符串,你可以在其中按你所想的格式(一般建议用JSON 格式)保存一些信息。

比如我们可以在元信息中约定字段标志silent,表示需要静默更新。当我们上传热更包填写 metainfo 时,以JSON 格式输入:

{ "silent": true }

请注意,我们并不对输入做任何格式校验和约束,请自行校验输入是否正确。

此时在客户端检查更新时,能获取到我们刚刚输入的元信息,但它并不具备任何功能,只是一个字符串而已。所以我们其实需要预先在更新流程中加入对应的处理逻辑:

// 调用 checkUpdate 获取 info
if (info.expired) {
  // ... 原生包版本过期,下载或跳转下载页面
} else if (info.upToDate) {
  // ... 没有更新,弹提示或忽略
} else {
  // 有更新,一般来说我们在这里给用户弹窗提示,让用户选择是否更新
  // 那么静默更新的本质其实就是不弹窗,直接执行,所以可以在这里加入额外的判断流程
  Alert.alert('提示', '检查到新的版本' + info.name + ',是否下载?\n' + info.description, [
    {
      text: '是',
      onPress: () => {
        this.doUpdate(info);
      },
    },
    { text: '否' },
  ]);
}

我们在原有的更新流程中加入元信息的读取和判断:

let metaInfo = {};
try {
  // 注意 JSON 输入有可能有错误,需要用 try 语句来避免应用被带崩
  metaInfo = JSON.parse(info.metaInfo);
} catch (e) {
  // 异常处理,忽略或上报?
}

if (metaInfo.silent) {
  // 如果热更包携带有 silent 字段,不询问用户,直接执行更新
  this.doUpdate(info);
} else {
  // 否则还是走之前的询问流程
  // Alert.alert('提示', '检查到新的版本.......
}

又比如,可能某个版本包含一些重要的公告内容,所以还可以在上面插入一个公告字段等等。如何使用元信息,完全取决于您的想象力!