针对 Android 的快速迭代开发
本页介绍 bazel mobile-install
如何进行迭代开发
快得多它描述了此方法相对于
传统应用安装方法所面临的挑战。
摘要
如需非常快速地对 Android 应用进行细微更改,请执行以下操作:
- 找到要安装的应用的
android_binary
规则。 - 通过移除
proguard_specs
属性停用 Proguard。 - 将
multidex
属性设置为native
。 - 将
dex_shards
属性设置为10
。 - 通过 USB 连接运行 ART(而非 Dalvik)的设备并启用 USB 进行调试
- 运行
bazel mobile-install :your_target
。应用启动速度会有点小 比平时慢。 - 修改代码或 Android 资源。
- 运行
bazel mobile-install --incremental :your_target
。 - 不必等待太久,
Bazel 的一些命令行选项可能很有用:
--adb
告知 Bazel 使用哪个 adb 二进制文件--adb_arg
可用于向adb
的命令行添加额外的参数。 一种实用的方法是选择 如果有多个设备连接到工作站:bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target
- “
--start_app
”会自动启动应用
简介
开发者工具链最重要的属性之一是速度: 更改代码与查看代码在一种环境下运行的 而且必须等待几分钟(有时是几个小时)才能收到任何反馈 判断您所做的更改是否符合您的预期
遗憾的是,用于构建 .apk 的传统 Android 工具链 必须执行所有这些连续的步骤 构建 Android 应用在 Google,等待五分钟来构建单行 这在 Google 地图等大型项目中并不罕见
bazel mobile-install
通过
将更改剪枝、工作分片和对
Android 内部机制,无需更改任何应用代码。
与传统应用安装相关的问题
构建 Android 应用时会遇到一些问题,包括:
Dexing。默认情况下,在构建中仅调用一次,且 知道如何重复使用之前构建中的工作:它会再次对每个方法进行 dex 处理, 但只更改了一种方法。
正在将数据上传到设备。adb 不使用 USB 2.0 的全带宽 大型应用可能需要很长时间才能上传完毕。整个应用 即使只有一小部分内容发生了更改,例如某个资源或 因此这可能会成为一个主要瓶颈。
编译为原生代码。Android L 引入了 ART,一种新的 Android 运行时, 这种应用会预先编译应用,而不是像以前那样即时编译 Dalvik。这样可以显著提升应用运行速度,但代价是需要更长的安装时间 。这对用户来说是一个很好的权衡,因为用户通常会安装应用 并且多次使用,但是会导致应用的开发速度 多次安装,每个版本最多运行几次。
bazel mobile-install
的方法
bazel mobile-install
做出了以下改进:
dex 分片。在构建应用的 Java 代码后,Bazel 会将类分片 文件拆分为大小大致相同的部分,并在
dx
。自上次构建以来没有更改的分片上不会调用dx
。增量文件传输。Android 资源、.dex 文件和原生代码 这些库会从主 .apk 中删除,并存储在单独的 mobile-install 目录。这样就可以更新代码 而无需重新安装整个应用。因此, 传输文件所需的时间更短, 会在设备端重新编译。
从 .apk 外部加载应用的某些部分。一个微小的桩应用 放入会加载 Android 资源、Java 代码和原生代码的 .apk 中 然后从设备端移动设备安装目录中复制控制权,然后将控制权转移给 实际应用除了在少数情况下,这对应用而言是透明的 如下所述。
分片 Dexing
分片 dexing 比较简单:构建 .jar 文件后,
工具
将它们分成大致相等大小的单独 .jar 文件,然后调用
dx
。部署的逻辑
用于确定哪些 dex 碎片不是特定于 Android 的:它只使用
Bazel 的常规更改剪枝算法
第一版分片算法只是对 .class 文件进行排序。 然后将该列表分割成大小相等的部分,但事实证明, 不是最佳的:如果添加或移除了类(即使是嵌套或匿名类) 导致再次对这些分片执行 dex 处理。因此,决定将 Java 分片 而不是单个类。当然,这仍然会导致 对多个分片执行 dex 处理(如果添加或移除了新软件包),但这个数量要少得多 比添加或移除单个类更频繁。
分片数由 BUILD 文件(使用
android_binary.dex_shards
属性)。在理想情况下,Bazel 会
会自动确定最佳分片数量,但目前 Bazel 必须知道
一系列操作(例如,构建期间要执行的命令)
因此它无法确定最佳的分片数
因为它不知道最终会有多少 Java 类
应用。一般来说,分片越多,构建速度越快,
但应用启动速度会变慢,因为动态
就必须做更多工作最佳点通常介于 10 到 50 个分片之间。
增量文件传输
构建应用后,下一步是安装该应用,最好使用 尽可能省力安装包括以下步骤:
- 安装 .apk(通常使用
adb install
) - 将 .dex 文件、Android 资源和原生库上传到 移动设备安装目录
第一步没有太大的增量:应用要么已经安装到
。Bazel 目前依赖于用户指示是否应执行此步骤
--incremental
命令行选项发送,因为它无法确定
所有情况。
在第二步中,系统会将 build 中的应用文件与设备端的应用文件进行比较 清单文件列出了设备上安装了哪些应用文件 校验和。系统会将所有新文件上传到此设备,以及所有已更改的文件 且所有已移除的文件都会从 设备。如果清单不存在,系统会假定每个文件都需要 上传。
请注意,您可以通过 更改设备上的文件,但不更改清单中的校验和。这可能会造成 通过计算 但我们认为不值得增加安装时间。
桩应用
存根应用程序是加载 dex 文件、原生代码和
设备端 mobile-install
目录中的 Android 资源发生。
实际加载是通过创建 BaseDexClassLoader
的子类实现的,
有据可查的分析法。这种情况发生在应用
以便系统能够加载 APK 中的任何应用类
放置在设备上的 mobile-install
目录中,以便可以更新
不使用 adb install
。
必须先执行 类,因此无需在 .apk,这意味着对这些类进行更改将需要完整的 重新安装。
可通过替换在 Application
AndroidManifest.xml
替换为
存根应用。本次
在应用启动时获得控制权,并调整类加载器和
合理使用资源管理器(其构造函数)
对 Android 框架内部机制的 Java 反射。
存根应用的另一件事是复制原生库
通过移动设备安装功能安装到其他位置。这是必要的,因为
动态链接器需要对文件设置 X
位,但这
执行任何由非根 adb
可访问的位置的操作。
完成所有这些操作后,存根应用程序随后会将
实际的 Application
类,将所有对自身的引用更改为实际的
在 Android 框架中实现。
结果
性能
一般来说,bazel mobile-install
可使构建速度提高 4 到 10 倍
以及在稍作更改之后安装大型应用
以下数据是根据一些 Google 产品计算得出的:
当然,这取决于更改的性质:在 更改基本库需要更多时间。
限制
存根应用程序发挥的技巧并非在所有情况下都适用。 以下情况会突出显示它在哪些情况下无法按预期运行:
当
Context
转换为Application
类时,ContentProvider#onCreate()
。应用期间会调用此方法 然后才能替换Application
的实例, 因此,ContentProvider
仍会引用桩应用 而不是真实的。当然,这不是错误 本来应该像这样将Context
下推,但好像过几次才出现 Google 应用。bazel mobile-install
安装的资源只能从 应用。如果其他应用通过PackageManager#getApplicationResources()
,这些资源将来自 上次非增量安装。未运行 ART 的设备。虽然桩应用适用于 在 Froyo 之后,Dalvik 有一个 bug,使它误以为该应用 如果代码分布在多个 .dex 文件上 例如,在 Java 注解用于 具体 。只要您的应用不会处理这些错误,就应该能使用 Dalvik, (但请注意,对旧版 Android 的支持并不是我们的 专注)