| # Introduction | |
| This document describes how the Chromium build system supports Android app | |
| bundles. | |
| [TOC] | |
| # Overview of app bundles | |
| An Android app bundle is an alternative application distribution format for | |
| Android applications on the Google Play Store, that allows reducing the size | |
| of binaries sent for installation to individual devices that run on Android L | |
| and beyond. For more information about them, see the official Android | |
| [documentation](https://developer.android.com/guide/app-bundle/). | |
| For the context of this document, the most important points are: | |
| - Unlike a regular APK (e.g. `foo.apk`), the bundle (e.g. `foo.aab`) cannot | |
| be installed directly on a device. | |
| - Instead, it must be processed into a set of installable split APKs, which | |
| are stored inside a special zip archive (e.g. `foo.apks`). | |
| - The splitting can be based on various criteria: e.g. language or screen | |
| density for resources, or cpu ABI for native code. | |
| - The bundle also uses the notion of dynamic features modules (DFMs) to | |
| separate several application features. Each module has its own code, assets | |
| and resources, and can be installed separately from the rest of the | |
| application if needed. | |
| - The main application itself is stored in the '`base`' module (this name | |
| cannot be changed). | |
| # Declaring app bundles with GN templates | |
| Here's an example that shows how to declare a simple bundle that contains a | |
| single base module, which enables language-based splits: | |
| ```gn | |
| # First declare the first bundle module. The base module is the one | |
| # that contains the main application's code, resources and assets. | |
| android_app_bundle_module("foo_base_module") { | |
| # Declaration are similar to android_apk here. | |
| ... | |
| } | |
| # Second, declare the bundle itself. | |
| android_app_bundle("foo_bundle") { | |
| # Indicate the base module to use for this bundle | |
| base_module_target = ":foo_base_module" | |
| # The name of our bundle file (without any suffix). Default would | |
| # be 'foo_bundle' otherwise. | |
| bundle_name = "FooBundle" | |
| # Enable language-based splits for this bundle. Which means that | |
| # resources and assets specific to a given language will be placed | |
| # into their own split APK in the final .apks archive. | |
| enable_language_splits = true | |
| # Proguard settings must be passed at the bundle, not module, target. | |
| proguard_enabled = !is_java_debug | |
| } | |
| ``` | |
| When generating the `foo_bundle` target with Ninja, you will end up with | |
| the following: | |
| - The bundle file under `out/Release/apks/FooBundle.aab` | |
| - A helper script called `out/Release/bin/foo_bundle`, which can be used | |
| to install / launch / uninstall the bundle on local devices. | |
| This works like an APK wrapper script (e.g. `foo_apk`). Use `--help` | |
| to see all possible commands supported by the script. | |
| # Declaring dynamic feature modules with GN templates | |
| Please see | |
| [Dynamic Feature Modules](../../../docs/android_dynamic_feature_modules.md) for | |
| more details. In short, if you need more modules besides the base one, you | |
| will need to list all the extra ones using the extra_modules variable which | |
| takes a list of GN scopes, as in: | |
| ```gn | |
| android_app_bundle_module("foo_base_module") { | |
| ... | |
| } | |
| android_app_bundle_module("foo_extra_module") { | |
| ... | |
| } | |
| android_app_bundle("foo_bundle") { | |
| base_module_target = ":foo_base_module" | |
| extra_modules = [ | |
| { # NOTE: Scopes require one field per line, and no comma separators. | |
| name = "my_module" | |
| module_target = ":foo_extra_module" | |
| } | |
| ] | |
| ... | |
| } | |
| ``` | |
| Note that each extra module is identified by a unique name, which cannot | |
| be '`base`'. | |
| # Bundle signature issues | |
| Signing an app bundle is not necessary, unless you want to upload it to the | |
| Play Store. Since this process is very slow (it uses `jarsigner` instead of | |
| the much faster `apkbuilder`), you can control it with the `sign_bundle` | |
| variable, as described in the example above. | |
| The `.apks` archive however always contains signed split APKs. The keystore | |
| path/password/alias being used are the default ones, unless you use custom | |
| values when declaring the bundle itself, as in: | |
| ```gn | |
| android_app_bundle("foo_bundle") { | |
| ... | |
| keystore_path = "//path/to/keystore" | |
| keystore_password = "K3y$t0Re-Pa$$w0rd" | |
| keystore_name = "my-signing-key-name" | |
| } | |
| ``` | |
| These values are not stored in the bundle itself, but in the wrapper script, | |
| which will use them to generate the `.apks` archive for you. This allows you | |
| to properly install updates on top of existing applications on any device. | |
| # Proguard and bundles | |
| When using an app bundle that is made of several modules, it is crucial to | |
| ensure that proguard, if enabled: | |
| - Keeps the obfuscated class names used by each module consistent. | |
| - Does not remove classes that are not used in one module, but referenced | |
| by others. | |
| To achieve this, a special scheme called *synchronized proguarding* is | |
| performed, which consists of the following steps: | |
| - The list of unoptimized .jar files from all modules are sent to a single | |
| proguard command. This generates a new temporary optimized *group* .jar file. | |
| - Each module extracts the optimized class files from the optimized *group* | |
| .jar file, to generate its own, module-specific, optimized .jar. | |
| - Each module-specific optimized .jar is then sent to dex generation. | |
| This synchronized proguarding step is added by the `android_app_bundle()` GN | |
| template. In practice this means the following: | |
| - `proguard_enabled` must be passed to `android_app_bundle` targets, but not | |
| to `android_app_bundle_module` ones. | |
| - `proguard_configs` can be still passed to individual modules, just | |
| like regular APKs. All proguard configs will be merged during the | |
| synchronized proguard step. | |
| # Manual generation and installation of .apks archives | |
| Note that the `foo_bundle` script knows how to generate the .apks archive | |
| from the bundle file, and install it to local devices for you. For example, | |
| to install and launch a bundle, use: | |
| ```sh | |
| out/Release/bin/foo_bundle run | |
| ``` | |
| If you want to manually look or use the `.apks` archive, use the following | |
| command to generate it: | |
| ```sh | |
| out/Release/bin/foo_bundle build-bundle-apks \ | |
| --output-apks=/tmp/BundleFoo.apks | |
| ``` | |
| All split APKs within the archive will be properly signed. And you will be | |
| able to look at its content (with `unzip -l`), or install it manually with: | |
| ```sh | |
| build/android/gyp/bundletool.py install-apks \ | |
| --apks=/tmp/BundleFoo.apks \ | |
| --adb=$(which adb) | |
| ``` | |
| The task of examining the manifest is simplified by running the following, | |
| which dumps the application manifest as XML to stdout: | |
| ```sh | |
| build/android/gyp/bundletool.py dump-manifest | |
| ``` |