bazel 移动安装

Android 的快速迭代开发

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

摘要

如需非常快速地安装对 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 问题

简介

开发者工具链最重要的属性之一是速度:在更改代码后,如果能在 1 秒内看到代码运行,与必须等待几分钟甚至几小时才能获得有关更改是否符合预期的反馈相比,体验会大相径庭。

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

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

传统应用安装存在的问题

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

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

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

  • 编译为原生代码。Android L 引入了 ART,这是一种新的 Android 运行时, 它会提前编译应用,而不是像 Dalvik 那样即时编译应用。这使得应用的运行速度更快,但安装时间 更长。对于用户来说,这是一个不错的权衡,因为他们通常安装一次应用 并多次使用,但对于开发来说,这会导致开发速度变慢,因为应用会 安装多次,并且每个版本最多运行几次。

bazel mobile-install 的方法

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

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

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

  • 分片安装。Mobile-install 使用 Android Studio 的 apkdeployer 工具在连接的设备上组合分片 APK,并提供一致的 体验。

分片 Dexing

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

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

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

增量部署

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

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

结果

性能

一般来说,bazel mobile-install 会将大型应用的构建 和安装速度提高 4 到 10 倍。

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

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

限制

桩应用所使用的技巧并非在所有情况下都有效。 以下情况突出显示了它未按预期工作的情况:

  • Mobile-install 仅通过 rules_android 的 Starlark 规则支持。 如需了解详情,请参阅"mobile-install 简史"

  • 仅支持运行 ART 的设备。Mobile-install 使用仅在运行 ART 而非 Dalvik 的设备上存在的 API 和运行时功能 。任何比 Android L (API 21+) 更新的 Android 运行时都应兼容。

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

  • 8.4.0 之前的 Bazel 版本必须为 mobile-install 指定一些额外的标志。请参阅Bazel Android 教程。这些 标志会告知 Bazel Starlark mobile-install 方面的位置以及支持哪些 规则。

mobile-install 简史

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

旧版原生 Android 规则还支持旧版 原生 版本的 mobile-install 功能。现在,这被称为“mobile-install v1”或 “原生 mobile-install”。此功能已在 Bazel 8 中删除,同时删除的还有内置 Android 规则。

现在,所有 mobile-install 功能以及所有 Android 构建和测试 规则都在 Starlark 中实现,并位于 rules_android GitHub 代码库中。最新版本称为“mobile-install v3”或“MIv3”。

命名说明:Google 内部曾经提供“mobile-install v2”,但从未在外部发布,并且只有 v3 继续用于 Google 内部和 OSS rules_android 部署。