| Project: /_project.yaml |
| Book: /_book.yaml |
| |
| # bazel mobile-install |
| |
| {% include "_buttons.html" %} |
| {# disableFinding(LINK_EXTERNAL) #} |
| |
| <p class="lead">Fast iterative development for Android</p> |
| |
| This page describes how `bazel mobile-install` makes iterative development |
| for Android much faster. It describes the benefits of this approach versus the |
| drawbacks of separate build and install steps. |
| |
| ## Summary {:#summary} |
| |
| To install small changes to an Android app very quickly, do the following: |
| |
| 1. Find the `android_binary` rule of the app you want to install. |
| 2. Connect your device to `adb`. |
| 3. Run `bazel mobile-install :your_target`. App startup will be a little |
| slower than usual. |
| 4. Edit the code or Android resources. |
| 5. Run `bazel mobile-install :your_target`. |
| 6. Enjoy a fast and minimal incremental installation! |
| |
| Some command line options to Bazel that may be useful: |
| |
| - `--adb` tells Bazel which adb binary to use |
| - `--adb_arg` can be used to add extra arguments to the command line of `adb`. |
| One useful application of this is to select which device you want to install |
| to if you have multiple devices connected to your workstation: |
| `bazel mobile-install :your_target -- --adb_arg=-s --adb_arg=<SERIAL>` |
| |
| When in doubt, look at the |
| [example](https://github.com/bazelbuild/rules_android/tree/main/examples/basicapp){: .external}, |
| contact us on [Google Groups](https://groups.google.com/forum/#!forum/bazel-discuss){: .external}, |
| or [file a GitHub issue](https://github.com/bazelbuild/rules_android/issues){: .external} |
| |
| ## Introduction {:#introduction} |
| |
| One of the most important attributes of a developer's toolchain is speed: there |
| is a world of difference between changing the code and seeing it run within a |
| second and having to wait minutes, sometimes hours, before you get any feedback |
| on whether your changes do what you expect them to. |
| |
| Unfortunately, the traditional Android toolchain for building an .apk entails |
| many monolithic, sequential steps and all of these have to be done in order to |
| build an Android app. At Google, waiting five minutes to build a single-line |
| change was not unusual on larger projects like Google Maps. |
| |
| `bazel mobile-install` makes iterative development for Android much faster by |
| using a combination of change pruning, work sharding, and clever manipulation of |
| Android internals, all without changing any of your app's code. |
| |
| ## Problems with traditional app installation {:#problems-app-install} |
| |
| Building an Android app has some issues, including: |
| |
| - Dexing. By default, the Dexer tool (historically `dx`, now `d8` or `r8`) |
| is invoked exactly once in the build and it does not know how to reuse work from |
| previous builds: it dexes every method again, even though only one method was |
| changed. |
| |
| - Uploading data to the device. adb does not use the full bandwidth of a USB 2.0 |
| connection, and larger apps can take a lot of time to upload. The entire app is |
| uploaded, even if only small parts have changed, for example, a resource or a |
| single method, so this can be a major bottleneck. |
| |
| - Compilation to native code. Android L introduced ART, a new Android runtime, |
| which compiles apps ahead-of-time rather than compiling them just-in-time like |
| Dalvik. This makes apps much faster at the cost of longer installation |
| time. This is a good tradeoff for users because they typically install an app |
| once and use it many times, but results in slower development where an app is |
| installed many times and each version is run at most a handful of times. |
| |
| ## The approach of `bazel mobile-install` {:#approach-mobile-install} |
| |
| `bazel mobile-install `makes the following improvements: |
| |
| - Sharded desugaring and dexing. After building the app's Java code, Bazel |
| shards the class files into approximately equal-sized parts and invokes `d8` |
| separately on them. `d8` is not invoked on shards that did not change since |
| the last build. These shards are then compiled into separate sharded APKs. |
| |
| - Incremental file transfer. Android resources, .dex files, and native |
| libraries are removed from the main .apk and are stored in under a separate |
| mobile-install directory. This makes it possible to update code and Android |
| resources independently without reinstalling the whole app. Thus, |
| transferring the files takes less time and only the .dex files that have |
| changed are recompiled on-device. |
| |
| - Sharded installation. Mobile-install uses Android Studio's |
| [`apkdeployer`](https://maven.google.com/web/index.html?q=deployer#com.android.tools.apkdeployer:apkdeployer){: .external} |
| tool to combine sharded APKs on the connected device and provide a cohesive |
| experience. |
| |
| ### Sharded Dexing {:#sharded-dexing} |
| |
| Sharded dexing is reasonably straightforward: once the .jar files are built, a |
| [tool](https://github.com/bazelbuild/rules_android/blob/main/src/tools/java/com/google/devtools/build/android/ziputils/DexMapper.java){: .external} |
| shards them into separate .jar files of approximately equal size, then invokes |
| `d8` on those that were changed since the previous build. The logic that |
| determines which shards to dex is not specific to Android: it just uses the |
| general change pruning algorithm of Bazel. |
| |
| The first version of the sharding algorithm simply ordered the .class files |
| alphabetically, then cut the list up into equal-sized parts, but this proved to |
| be suboptimal: if a class was added or removed (even a nested or an anonymous |
| one), it would cause all the classes alphabetically after it to shift by one, |
| resulting in dexing those shards again. Thus, it was decided to shard Java |
| packages rather than individual classes. Of course, this still results in |
| dexing many shards if a new package is added or removed, but that is much less |
| frequent than adding or removing a single class. |
| |
| The number of shards is controlled by command-line configuration, using the |
| `--define=num_dex_shards=N` flag. In an ideal world, Bazel would |
| automatically determine how many shards are best, but Bazel currently must know |
| the set of actions (for example, commands to be executed during the build) before |
| executing any of them, so it cannot determine the optimal number of shards |
| because it doesn't know how many Java classes there will eventually be in the |
| app. Generally speaking, the more shards, the faster the build and the |
| installation will be, but the slower app startup becomes, because the dynamic |
| linker has to do more work. The sweet spot is usually between 10 and 50 shards. |
| |
| ### Incremental deployment {:#incremental-deployment} |
| |
| Incremental APK shard transfer and installation is now handled by the |
| `apkdeployer` utility described in ["The approach of mobile-install"](#approach-mobile-install). |
| Whereas earlier (native) versions of mobile-install required manually tracking |
| first-time installations and selectively apply the `--incremental` |
| flag on subsequent installation, the most recent version in [`rules_android`](https://github.com/bazelbuild/rules_android/tree/main/mobile_install){: .external} |
| has been greatly simplified. The same mobile-install |
| invocation can be used regardless of how many times the app has been installed |
| or reinstalled. |
| |
| At a high level, the `apkdeployer` tool is a wrapper around various `adb` |
| sub-commands. The main entrypoint logic can be found in the |
| [`com.android.tools.deployer.Deployer`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/deployer/src/main/java/com/android/tools/deployer/Deployer.java){: .external} |
| class, with other utility classes colocated in the same package. |
| The `Deployer` class ingests, among other things, a list of paths to split |
| APKs and a protobuf with information about the installation, and leverages |
| deployment features for [Android app bundles](https://developer.android.com/guide/app-bundle){: .external} |
| in order to create an install session and incrementally deploy app splits. |
| See the [`ApkPreInstaller`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/deployer/src/main/java/com/android/tools/deployer/ApkPreInstaller.java){: .external} |
| and [`ApkInstaller`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:deploy/deployer/src/main/java/com/android/tools/deployer/ApkInstaller.java){: .external} |
| classes for implementation details. |
| |
| ## Results {:#results} |
| |
| ### Performance {:#performance} |
| |
| In general, `bazel mobile-install` results in a 4x to 10x speedup of building |
| and installing large apps after a small change. |
| |
| The following numbers were computed for a few Google products: |
| |
| <img src="/docs/images/mobile-install-performance.svg"/> |
| |
| This, of course, depends on the nature of the change: recompilation after |
| changing a base library takes more time. |
| |
| ### Limitations {:#limitations} |
| |
| The tricks the stub application plays don't work in every case. |
| The following cases highlight where it does not work as expected: |
| |
| - Mobile-install is only supported via the Starlark rules of `rules_android`. |
| See the ["brief history of mobile-install"](#mobile-install-history) for |
| more detail. |
| |
| - Only devices running ART are supported. Mobile-install uses API and runtime features |
| that only exist on devices running ART, not Dalvik. Any Android runtime more |
| recent than Android L (API 21+) should be compatible. |
| |
| - Bazel itself must be run with a tool Java runtime _and_ language version |
| of 17 or higher. |
| |
| - Bazel versions prior to 8.4.0 must specify some additional flags for |
| mobile-install. See [the Bazel Android tutorial](/start/android-app). These |
| flags inform Bazel where the Starlark mobile-install aspect is and which |
| rules are supported. |
| |
| ### A brief history of mobile-install {:#mobile-install-history} |
| Earlier Bazel versions _natively_ included built-in build and test rules for |
| popular languages and ecosystems such as C++, Java, and Android. These rules |
| were therefore referred to as _native_ rules. Bazel 8 (released in 2024) removed |
| support for these rules because many of them had been migrated to the |
| [Starlark](/rules/language) language. See the ["Bazel 8.0 LTS blog post"](https://blog.bazel.build/2024/12/09/bazel-8-release.html){: .external} |
| for more details. |
| |
| The legacy native Android rules also supported a legacy _native_ version of |
| mobile-install functionality. This is referred to as "mobile-install v1" or |
| "native mobile-install" now. This functionality was deleted in Bazel 8, along |
| with the built-in Android rules. |
| |
| Now, all mobile-install functionality, as well as all Android build and test |
| rules, are implemented in Starlark and reside in the `rules_android` GitHub |
| repository. The latest version is known as "mobile-install v3" or "MIv3". |
| |
| _Naming note_: There was a "mobile-install **v2**" available only internally |
| at Google at one point, but this was never published externally, and only v3 |
| continues to be used for both Google-internal and OSS rules_android deployment. |
| |
| |