bazel 移动安装

报告问题 查看源代码 每夜版 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

Android 快速迭代开发

本页介绍了 bazel mobile-install 如何大幅加快 Android 的迭代开发速度。它描述了这种方法的优势,以及单独的 build 和安装步骤的缺点。

摘要

如需非常快速地将细微更改安装到 Android 应用中,请执行以下操作:

  1. 找到要安装的应用的 android_binary 规则。
  2. 将设备连接到 adb
  3. 运行 bazel mobile-install :your_target。应用启动速度会比平时稍慢。
  4. 修改代码或 Android 资源。
  5. 运行 bazel mobile-install :your_target
  6. 享受快速且最小的增量安装!

以下是一些可能对您有用的 Bazel 命令行选项:

  • --adb 告知 Bazel 要使用哪个 adb 二进制文件
  • --adb_arg 可用于向 adb 的命令行添加额外的实参。此功能的一项实用应用是,如果您有多个设备连接到工作站,则可以选择要安装到的设备: bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>

如有疑问,请查看示例、通过 Google 群组与我们联系,或提交 GitHub 问题

简介

开发者工具链最重要的属性之一是速度:更改代码后,在一秒内看到代码运行与等待数分钟甚至数小时才能获得有关更改是否达到预期效果的反馈,这两者之间存在天壤之别。

遗憾的是,用于构建 .apk 的传统 Android 工具链包含许多单体式顺序步骤,所有这些步骤都必须完成才能构建 Android 应用。在 Google,在 Google 地图等大型项目中,等待五分钟来构建单行更改并不罕见。

bazel mobile-install 通过结合使用更改剪枝、工作分片和对 Android 内部结构的巧妙操控,可大幅加快 Android 的迭代开发速度,而无需更改应用的任何代码。

传统应用安装存在的问题

构建 Android 应用时存在一些问题,包括:

  • Dexing。默认情况下,Dexer 工具(以前为 dx,现在为 d8r8)在 build 中仅调用一次,并且不知道如何重用之前 build 中的工作:即使仅更改了一个方法,它也会再次对每个方法进行 dex 处理。

  • 将数据上传到设备。adb 不会使用 USB 2.0 连接的全部带宽,因此上传较大的应用可能需要很长时间。即使只有小部分(例如资源或单个方法)发生了变化,系统也会上传整个应用,因此这可能会成为一个主要瓶颈。

  • 编译为原生代码。Android L 引入了 ART,这是一种新的 Android 运行时,它会预先编译应用,而不是像 Dalvik 那样即时编译应用。这样可以大幅提高应用速度,但会延长安装时间。对于用户来说,这种权衡是合理的,因为他们通常只安装一次应用,但会多次使用该应用,但如果应用安装次数很多,且每个版本最多只运行几次,则会导致开发速度变慢。

bazel mobile-install 的方法

bazel mobile-install 进行了以下改进:

  • 分片脱糖和 dexing。构建应用的 Java 代码后,Bazel 会将类文件分片为大小大致相等的部分,并分别对这些部分调用 d8。对于自上次 build 以来未发生更改的分片,不会调用 d8。然后,这些分片会被编译为单独的分片 APK。

  • 增量文件传输。Android 资源、.dex 文件和原生库会从主 .apk 中移除,并存储在单独的移动安装目录下。这样一来,您就可以独立更新代码和 Android 资源,而无需重新安装整个应用。因此,传输文件所需的时间更短,并且只有已更改的 .dex 文件会在设备上重新编译。

  • 分片安装。移动安装使用 Android Studio 的 apkdeployer 工具来合并连接设备上的分片 APK,从而提供顺畅的体验。

分片 Dexing

分块 dexing 相当简单:一旦构建了 .jar 文件,工具就会将它们分块为大小大致相等的单独 .jar 文件,然后对自上次构建以来发生更改的文件调用 d8。用于确定要 dex 的分片的逻辑并非特定于 Android:它只是使用了 Bazel 的常规更改剪枝算法。

分片算法的第一个版本只是按字母顺序对 .class 文件进行排序,然后将列表分成大小相等的部分,但事实证明这种方法并不理想:如果添加或移除了某个类(即使是嵌套类或匿名类),也会导致其后按字母顺序排列的所有类移动一个位置,从而导致这些分片再次进行 dex 处理。因此,我们决定对 Java 软件包进行分片,而不是对单个类进行分片。当然,如果添加或移除了新软件包,这仍然会导致对许多分片进行 dexing,但这种情况的发生频率远低于添加或移除单个类。

分片数量由命令行配置控制,使用 --define=num_dex_shards=N 标志。在理想情况下,Bazel 会自动确定最佳分片数量,但 Bazel 目前必须在执行任何操作之前了解操作集(例如,在 build 期间要执行的命令),因此无法确定最佳分片数量,因为它不知道应用中最终会有多少 Java 类。一般来说,分片越多,build 和安装速度就越快,但应用启动速度会变慢,因为动态链接器必须执行更多工作。最佳分片数通常介于 10 到 50 之间。

增量部署

增量 APK 分片传输和安装现在由 “移动安装方法”中描述的 apkdeployer 实用程序处理。而早期(原生)版本的 mobile-install 需要手动跟踪首次安装,并在后续安装中选择性地应用 --incremental 标志,rules_android 中的最新版本已大大简化。无论应用已安装或重新安装多少次,都可以使用相同的移动安装调用。

从宏观层面来看,apkdeployer 工具是各种 adb 子命令的封装容器。主要入口点逻辑位于 com.android.tools.deployer.Deployer 类中,其他实用程序类位于同一软件包中。Deployer 类会接收拆分 APK 的路径列表和包含安装信息的 protobuf 等内容,并利用 Android App Bundle 的部署功能来创建安装会话并以增量方式部署应用拆分。 如需了解实现详情,请参阅 ApkPreInstallerApkInstaller 类。

结果

性能

一般来说,bazel mobile-install 可将大型应用在小幅更改后的构建和安装速度提高 4 到 10 倍。

以下数字是针对部分 Google 产品计算得出的:

当然,这取决于更改的性质:更改基础库后重新编译需要更多时间。

限制

桩应用所用的技巧并非在所有情况下都有效。 以下情况表明,该功能无法按预期运行:

  • 仅通过 rules_android 的 Starlark 规则支持移动应用安装。 如需了解详情,请参阅“移动安装的简要历史记录”

  • 仅支持运行 ART 的设备。移动安装使用仅在运行 ART 的设备上存在的 API 和运行时功能,而不是 Dalvik。任何比 Android L (API 21+) 更新的 Android 运行时都应兼容。

  • Bazel 本身必须使用工具 Java 运行时语言版本 17 或更高版本运行。

  • 低于 8.4.0 的 Bazel 版本必须为移动安装指定一些额外的标志。请参阅 Bazel Android 教程。这些标志会告知 Bazel Starlark 移动安装方面的位置以及支持哪些规则。

移动应用安装广告简史

早期的 Bazel 版本原生包含适用于 C++、Java 和 Android 等热门语言和生态系统的内置 build 和测试规则。因此,这些规则被称为“原生”规则。Bazel 8(于 2024 年发布)移除了对这些规则的支持,因为其中许多规则已迁移到 Starlark 语言。如需了解详情,请参阅 Bazel 8.0 LTS 博文

旧版原生 Android 规则还支持旧版原生移动安装功能。现在称为“移动应用安装广告 v1”或“原生移动应用安装广告”。此功能已在 Bazel 8 中删除,同时删除的还有内置的 Android 规则。

现在,所有移动安装功能以及所有 Android build 和测试规则均已在 Starlark 中实现,并位于 rules_android GitHub 代码库中。最新版本称为“移动设备安装 v3”或“MIv3”。

命名注意事项:Google 内部曾提供过“移动安装 v2”,但从未在外部发布,目前只有 v3 继续用于 Google 内部和 OSS rules_android 部署。