blob: f273871d0dde70adc6242ab6247b884fa3fb94d5 [file] [log] [blame] [view]
John Field1d74b682015-09-11 21:14:09 +00001---
dzc22b85a22017-05-31 20:37:50 +02002layout: documentation
3title: mobile-install
John Field1d74b682015-09-11 21:14:09 +00004---
dzc22b85a22017-05-31 20:37:50 +02005
6# bazel mobile-install
7
8<p class="lead">Fast iterative development for Android</p>
9
10## TL;DR
11
12To install small changes to an Android app very quickly, do the following:
13
14 1. Find the `android_binary` rule of the app you want to install.
15 2. Disable Proguard by removing the `proguard_specs` attribute.
16 3. Set the `multidex` attribute to `native`.
17 4. Set the `dex_shards` attribute to `10`.
18 5. Connect your device running ART (not Dalvik) over USB and enable USB
19 debugging on it.
20 6. Run `bazel mobile-install :your_target`. App startup will be a little
21 slower than usual.
22 7. Edit the code or Android resources.
23 8. Run `bazel mobile-install --incremental :your_target`.
24 9. Enjoy not having to wait a lot.
25
26Some command line options to Bazel that may be useful:
27
28 - `--adb` tells Bazel which adb binary to use
29 - `--adb_arg` can be used to add extra arguments to the command line of `adb`.
30 One useful application of this is to select which device you want to install
31 to if you have multiple devices connected to your workstation:
32 `bazel mobile-install --adb_arg=-s --adb_arg=<SERIAL> :your_target`
33 - `--start_app` automatically starts the app
34
35When in doubt, look at the
36[example](https://github.com/bazelbuild/bazel/tree/master/examples/android)
37or [contact us](https://groups.google.com/forum/#!forum/bazel-discuss).
38
39## Introduction
40
41One of the most important attributes of a developer's toolchain is speed: there
42is a world of difference between changing the code and seeing it run within a
43second and having to wait minutes, sometimes hours, before you get any feedback
44on whether your changes do what you expect them to.
45
46Unfortunately, the traditional Android toolchain for building an .apk entails
47many monolithic, sequential steps and all of these have to be done in order to
48build an Android app. At Google, waiting five minutes to build a single-line
49change was not unusual on larger projects like Google Maps.
50
51`bazel mobile-install` makes iterative development for Android much faster by
52using a combination of change pruning, work sharding, and clever manipulation of
53Android internals, all without changing any of your app's code.
54
55## Problems with traditional app installation
56
57We identified the following bottlenecks of building an Android app:
58
59- Dexing. By default, "dx" is invoked exactly once in the build and it does not
60know how to reuse work from previous builds: it dexes every method again, even
61though only one method was changed.
62
63- Uploading data to the device. adb does not use the full bandwidth of a USB 2.0
64connection, and larger apps can take a lot of time to upload. The entire app is
65uploaded, even if only small parts have changed, for example, a resource or a
66single method, so this can be a major bottleneck.
67
68- Compilation to native code. Android L introduced ART, a new Android runtime,
69which compiles apps ahead-of-time rather than compiling them just-in-time like
70Dalvik. This makes apps much faster at the cost of longer installation
71time. This is a good tradeoff for users because they typically install an app
72once and use it many times, but results in slower development where an app is
73installed many times and each version is run at most a handful of times.
74
75## The approach of `bazel mobile-install`
76
77`bazel mobile-install `makes the following improvements:
78
79 - Sharded dexing. After building the app's Java code, Bazel shards the class
80 files into approximately equal-sized parts and invokes `dx` separately on
81 them. `dx` is not invoked on shards that did not change since the last build.
82
83 - Incremental file transfer. Android resources, .dex files, and native
84 libraries are removed from the main .apk and are stored in under a separate
85 mobile-install directory. This makes it possible to update code and Android
86 resources independently without reinstalling the whole app. Thus,
87 transferring the files takes less time and only the .dex files that have
88 changed are recompiled on-device.
89
90 - Loading parts of the app from outside the .apk. A tiny stub application is
91 put into the .apk that loads Android resources, Java code and native code
92 from the on-device mobile-install directory, then transfers control to the
93 actual app. This is all transparent to the app, except in a few corner cases
94 described below.
95
96### Sharded Dexing
97
98Sharded dexing is reasonably straightforward: once the .jar files are built, a
99[tool](https://github.com/bazelbuild/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/ziputils/DexMapper.java)
100shards them into separate .jar files of approximately equal size, then invokes
101`dx` on those that were changed since the previous build. The logic that
102determines which shards to dex is not specific to Android: it just uses the
103general change pruning algorithm of Bazel.
104
105The first version of the sharding algorithm simply ordered the .class files
106alphabetically, then cut the list up into equal-sized parts, but this proved to
107be suboptimal: if a class was added or removed (even a nested or an anonymous
108one), it would cause all the classes alphabetically after it to shift by one,
109resulting in dexing those shards again. Thus, we settled upon sharding not
110individual classes, but Java packages instead. Of course, this still results in
111dexing many shards if a new package is added or removed, but that is much less
112frequent than adding or removing a single class.
113
114The number of shards is controlled by the BUILD file (using the
115`android_binary.dex_shards` attribute). In an ideal world, Bazel would
116automatically determine how many shards are best, but Bazel currently must know
117the set of actions (i.e. commands to be executed during the build) before
118executing any of them, so it cannot determine the optimal number of shards
119because it doesn't know how many Java classes there will eventually be in the
120app. Generally speaking, the more shards, the faster the build and the
121installation will be, but the slower app startup becomes, because the dynamic
122linker has to do more work. The sweet spot is usually between 10 and 50 shards.
123
124### Incremental File Transfer
125
126After building the app, the next step is to install it, preferably with the
127least effort possible. Installation consists of the following steps:
128
129 1. Installing the .apk (i.e. `adb install`)
130 2. Uploading the .dex files, Android resources, and native libraries to the
131 mobile-install directory
132
133There is not much incrementality in the first step: the app is either installed
134or not. Bazel currently relies on the user to indicate if it should do this step
135through the `--incremental` command line option because it cannot determine in
136all cases if it is necessary.
137
138In the second step, the app's files from the build are compared to an on-device
139manifest file that lists which app files are on the device and their
140checksums. Any new files are uploaded to the device, any files that have changed
141are updated, and any files that have been removed are deleted from the
142device. If the manifest is not present, it is assumed that every file needs to
143be uploaded.
144
145Note that it is possible to fool the incremental installation algorithm by
146changing a file on the device, but not its checksum in the manifest. We could
147have safeguarded against this by computing the checksum of the files on the
148device, but this was deemed to be not worth the increase in installation time.
149
150### The Stub Application
151
152The stub application is where the magic to load the dexes, native code and
153Android resources from the on-device `mobile-install` directory happens.
154
155The actual loading is implemented by subclassing `BaseDexClassLoader` and is a
156reasonably well-documented technique. This happens before any of the app's
157classes are loaded, so that any application classes that are in the apk can be
158placed in the on-device `mobile-install` directory so that they can be updated
159without `adb install`.
160
161This needs to happen before any of the
162classes of the app are loaded, so that no application class needs to be in the
163.apk which would mean that changes to those classes would require a full
164re-install.
165
166This is accomplished by replacing the `Application` class specified in
167`AndroidManifest.xml` with the
168[stub application](https://github.com/bazelbuild/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/StubApplication.java). This
169takes control when the app is started, and tweaks the class loader and the
170resource manager appropriately at the earliest moment (its constructor) using
171Java reflection on the internals of the Android framework.
172
173Another thing the stub application does is to copy the native libraries
174installed by mobile-install to another location. This is necessary because the
175dynamic linker needs the `X` bit to be set on the files, which is not possible to
176do for any location accessible by a non-root `adb`.
177
178Once all these things are done, the stub application then instantiates the
179actual `Application` class, changing all references to itself to the actual
180application within the Android framework.
181
182## Results
183
184### Performance
185
186In general, `bazel mobile-install` results in a 4x to 10x speedup of building
187and installing large apps after a small change. We computed the following
188numbers for a few Google products:
189
190<img src="/assets/mobile-install-performance.svg"/>
191
192This, of course, depends on the nature of the change: recompilation after
193changing a base library takes more time.
194
195### Limitations
196
197The tricks the stub application plays don't work in every case. We have
198identified the following cases where it does not work as expected:
199
200 - When `Context` is cast to the `Application` class in
201 `ContentProvider#onCreate()`. This method is called during application
202 startup before we have a chance to replace the instance of the `Application`
203 class, therefore, `ContentProvider` will still reference the stub application
204 instead of the real one. Arguably, this is not a bug since you are not
205 supposed to downcast `Context` like this, but this seems to happen in a few
206 apps at Google.
207
208 - Resources installed by `bazel mobile-install` are only available from within
209 the app. If resources are accessed by other apps via
210 `PackageManager#getApplicationResources()`, these resources will be from the
211 last non-incremental install.
212
213 - Devices that aren't running ART. While the stub application works well on
214 Froyo and later, Dalvik has a bug that makes it think that the app is
215 incorrect if its code is distributed over multiple .dex files in certain
216 cases, for example, when Java annotations are used in a
217 [specific](https://code.google.com/p/android/issues/detail?id=78144) way. As
218 long as your app doesn't tickle these bugs, it should work with Dalvik, too
219 (note, however, that support for old Android versions isn't exactly our
220 focus)