David 'Digit' Turner | 9094474 | 2018-08-03 13:22:08 | [diff] [blame] | 1 | # Introduction |
| 2 | |
| 3 | This document describes how the Chromium build system supports Android app |
| 4 | bundles. |
| 5 | |
| 6 | [TOC] |
| 7 | |
| 8 | # Overview of app bundles |
| 9 | |
| 10 | An Android app bundle is an alternative application distribution format for |
| 11 | Android applications on the Google Play Store, that allows reducing the size |
| 12 | of binaries sent for installation to individual devices that run on Android L |
| 13 | and beyond. For more information about them, see the official Android |
| 14 | documentation at: |
| 15 | |
| 16 | https://developer.android.com/guide/app-bundle/ |
| 17 | |
| 18 | For the context of this document, the most important points are: |
| 19 | |
| 20 | - Unlike a regular APK (e.g. `foo.apk`), the bundle (e.g. `foo.aab`) cannot |
| 21 | be installed directly on a device. |
| 22 | |
| 23 | - Instead, it must be processed into a set of installable split APKs, which |
| 24 | are stored inside a special zip archive (e.g. `foo.apks`). |
| 25 | |
| 26 | - The splitting can be based on various criteria: e.g. language or screen |
| 27 | density for resources, or cpu ABI for native code. |
| 28 | |
| 29 | - The bundle also uses the notion of modules to separate several application |
| 30 | features. Each module has its own code, assets and resources, and can be |
| 31 | installed separately from the rest of the application if needed. |
| 32 | |
| 33 | - The main application itself is stored in the '`base`' module (this name |
| 34 | cannot be changed). |
| 35 | |
| 36 | |
| 37 | # Declaring app bundles with GN templates |
| 38 | |
| 39 | Here's an example that shows how to declare a simple bundle that contains |
| 40 | a single base module, which enables language-based splits: |
| 41 | |
| 42 | ```gn |
| 43 | |
| 44 | # First declare the first bundle module. The base module is the one |
| 45 | # that contains the main application's code, resources and assets. |
| 46 | android_app_bundle_module("foo_base_module") { |
| 47 | # Declaration are similar to android_apk here. |
| 48 | ... |
| 49 | } |
| 50 | |
| 51 | # Second, declare the bundle itself. |
| 52 | android_app_bundle("foo_bundle") { |
| 53 | # Indicate the base module to use for this bundle |
| 54 | base_module_target = ":foo_base_module" |
| 55 | |
| 56 | # The name of our bundle file (without any suffix). Default would |
| 57 | # be 'foo_bundle' otherwise. |
| 58 | bundle_name = "FooBundle" |
| 59 | |
| 60 | # Signing your bundle is required to upload it to the Play Store |
| 61 | # but since signing is very slow, avoid doing it for non official |
| 62 | # builds. Signing the bundle is not required for local testing. |
| 63 | sign_bundle = is_official_build |
| 64 | |
| 65 | # Enable language-based splits for this bundle. Which means that |
| 66 | # resources and assets specific to a given language will be placed |
| 67 | # into their own split APK in the final .apks archive. |
| 68 | enable_language_splits = true |
| 69 | |
| 70 | # Proguard settings must be passed at the bundle, not module, target. |
| 71 | proguard_enabled = !is_java_debug |
| 72 | } |
| 73 | ``` |
| 74 | |
| 75 | When generating the `foo_bundle` target with Ninja, you will end up with |
| 76 | the following: |
| 77 | |
| 78 | - The bundle file under `out/Release/apks/FooBundle.aab` |
| 79 | |
| 80 | - A helper script called `out/Release/bin/foo_bundle`, which can be used |
| 81 | to install / launch / uninstall the bundle on local devices. |
| 82 | |
| 83 | This works like an APK wrapper script (e.g. `foo_apk`). Use `--help` |
| 84 | to see all possible commands supported by the script. |
| 85 | |
| 86 | If you need more modules besides the base one, you will need to list all the |
| 87 | extra ones using the extra_modules variable which takes a list of GN scopes, |
| 88 | as in: |
| 89 | |
| 90 | ```gn |
| 91 | |
| 92 | android_app_bundle_module("foo_base_module") { |
| 93 | ... |
| 94 | } |
| 95 | |
| 96 | android_app_bundle_module("foo_extra_module") { |
| 97 | ... |
| 98 | } |
| 99 | |
| 100 | android_app_bundle("foo_bundle") { |
| 101 | base_module_target = ":foo_base_module" |
| 102 | |
| 103 | extra_modules = [ |
| 104 | { # NOTE: Scopes require one field per line, and no comma separators. |
| 105 | name = "my_module" |
| 106 | module_target = ":foo_extra_module" |
| 107 | } |
| 108 | ] |
| 109 | |
| 110 | ... |
| 111 | } |
| 112 | ``` |
| 113 | |
| 114 | Note that each extra module is identified by a unique name, which cannot |
| 115 | be '`base`'. |
| 116 | |
| 117 | |
| 118 | # Bundle signature issues |
| 119 | |
| 120 | Signing an app bundle is not necessary, unless you want to upload it to the |
| 121 | Play Store. Since this process is very slow (it uses `jarsigner` instead of |
| 122 | the much faster `apkbuilder`), you can control it with the `sign_bundle` |
| 123 | variable, as described in the example above. |
| 124 | |
| 125 | The `.apks` archive however always contains signed split APKs. The keystore |
| 126 | path/password/alias being used are the default ones, unless you use custom |
| 127 | values when declaring the bundle itself, as in: |
| 128 | |
| 129 | ```gn |
| 130 | android_app_bundle("foo_bundle") { |
| 131 | ... |
| 132 | keystore_path = "//path/to/keystore" |
| 133 | keystore_password = "K3y$t0Re-Pa$$w0rd" |
| 134 | keystore_name = "my-signing-key-name" |
| 135 | } |
| 136 | ``` |
| 137 | |
| 138 | These values are not stored in the bundle itself, but in the wrapper script, |
| 139 | which will use them to generate the `.apks` archive for you. This allows you |
| 140 | to properly install updates on top of existing applications on any device. |
| 141 | |
| 142 | |
| 143 | # Proguard and bundles |
| 144 | |
| 145 | When using an app bundle that is made of several modules, it is crucial to |
| 146 | ensure that proguard, if enabled: |
| 147 | |
| 148 | - Keeps the obfuscated class names used by each module consistent. |
| 149 | - Does not remove classes that are not used in one module, but referenced |
| 150 | by others. |
| 151 | |
| 152 | To achieve this, a special scheme called *synchronized proguarding* is |
| 153 | performed, which consists of the following steps: |
| 154 | |
| 155 | - The list of unoptimized .jar files from all modules are sent to a single |
| 156 | proguard command. This generates a new temporary optimized *group* .jar file. |
| 157 | |
| 158 | - Each module extracts the optimized class files from the optimized *group* |
| 159 | .jar file, to generate its own, module-specific, optimized .jar. |
| 160 | |
| 161 | - Each module-specific optimized .jar is then sent to dex generation. |
| 162 | |
| 163 | This synchronized proguarding step is added by the `android_app_bundle()` GN |
| 164 | template. In practice this means the following: |
| 165 | |
| 166 | - If `proguard_enabled` and `proguard_jar_path` must be passed to |
| 167 | `android_app_bundle` targets, but not to `android_app_bundle_module` ones. |
| 168 | |
| 169 | - `proguard_configs` can be still passed to individual modules, just |
| 170 | like regular APKs. All proguard configs will be merged during the |
| 171 | synchronized proguard step. |
| 172 | |
| 173 | |
| 174 | # Manual generation and installation of .apks archives |
| 175 | |
| 176 | Note that the `foo_bundle` script knows how to generate the .apks archive |
| 177 | from the bundle file, and install it to local devices for you. For example, |
| 178 | to install and launch a bundle, use: |
| 179 | |
| 180 | ```sh |
| 181 | out/Release/bin/foo_bundle run |
| 182 | ``` |
| 183 | |
| 184 | If you want to manually look or use the `.apks` archive, use the following |
| 185 | command to generate it: |
| 186 | |
| 187 | ```sh |
| 188 | out/Release/bin/foo_bundle build-bundle-apks \ |
| 189 | --output-apks=/tmp/BundleFoo.apks |
| 190 | ``` |
| 191 | |
| 192 | All split APKs within the archive will be properly signed. And you will be |
| 193 | able to look at its content (with `unzip -l`), or install it manually with: |
| 194 | |
| 195 | ```sh |
| 196 | build/android/gyp/bundletool.py install-apks \ |
| 197 | --apks=/tmp/BundleFoo.apks \ |
| 198 | --adb=$(which adb) |
| 199 | ``` |
Matthew Cary | 39e6228 | 2019-03-07 14:56:08 | [diff] [blame] | 200 | |
| 201 | The task of examining the manifest is simplified by running the following, |
| 202 | which dumps the application manifest as XML to stdout: |
| 203 | |
| 204 | ```sh |
| 205 | build/android/gyp/bundletool.py dump-manifest |
| 206 | ``` |