blob: 81182a5ac8945fe29deafbb8044ba35d897d0828 [file] [log] [blame] [view]
Andrew Grievef6069feb2021-04-29 18:29:171# App Bundles and Dynamic Feature Modules (DFMs)
Tibor Goldschwendt19364ba2019-04-10 15:59:552
3[TOC]
4
Andrew Grievef6069feb2021-04-29 18:29:175## About Bundles
6[Android App bundles] is a Play Store feature that allows packaging an app as
7multiple `.apk` files, known as "splits". Bundles are zip files with an `.aab`
8extension. See [android_build_instructions.md#multiple-chrome-targets] for a
9list of buildable bundle targets.
10
11Bundles provide three main advantages over monolithic `.apk` files:
121. Language resources are split into language-specific `.apk` files, known as
13 "resource splits". Delivering only the active languages reduces the overhead
14 of UI strings.
152. Features can be packaged into lazily loaded `.apk` files, known as
16 "feature splits". Feature splits have no performance overhead until used.
17 * Except on versions prior to Android O, where support for
18 [android:isolatedSplits] was added. On prior versions, all installed splits
19 are loaded on application launch.
20 * E.g.: The `chrome` feature split makes renderers more efficient by having
21 them not load Java code that they don't need.
22 * E.g.: The `image_editor` feature split defers loading of Share-related code
23 until a Share action is performed.
24 * See also: [go/isolated-splits-dev-guide] (Googlers only).
253. Feature splits can be downloaded on-demand, saving disk space for users that
26 do not need the functionality they provide. These are known as
27 "Dynamic feature modules", or "DFMs".
28 * E.g. Chrome's VR support is packaged in this way, via the `vr` module.
29
30You can inspect which `.apk` files are produced by a bundle target via:
31```
32out/Default/bin/${target_name} build-bundle-apks --output-apks foo.apks
33unzip -l foo.apks
34```
35
Andrew Grieve23c828d2021-05-13 19:37:2936*** note
37Adding new features vis feature splits is highly encouraged when it makes sense
38to do so:
39 * Has a non-trivial amount of Dex (>50kb)
40 * Not needed on startup
41 * Has a small integration surface (calls into it must be done with reflection).
42***
43
Andrew Grievef6069feb2021-04-29 18:29:1744The remainder of this doc focuses on DFMs.
45
46[android_build_instructions.md#multiple-chrome-targets]: android_build_instructions.md#multiple-chrome-targets
47[Android App Bundles]: https://developer.android.com/guide/app-bundle
48[android:isolatedSplits]: https://developer.android.com/reference/android/R.attr#isolatedSplits
49[go/isolated-splits-dev-guide]: http://go/isolated-splits-dev-guide
Tibor Goldschwendt19364ba2019-04-10 15:59:5550
51## Limitations
52
Tibor Goldschwendt68c5f722019-08-01 15:10:1553DFMs have the following limitations:
Tibor Goldschwendt19364ba2019-04-10 15:59:5554
55* **WebView:** We don't support DFMs for WebView. If your feature is used by
Tibor Goldschwendt68c5f722019-08-01 15:10:1556 WebView you cannot put it into a DFM.
Tibor Goldschwendt19364ba2019-04-10 15:59:5557
58## Getting started
59
60This guide walks you through the steps to create a DFM called _Foo_ and add it
Tibor Goldschwendtaef8e392019-07-19 16:39:1061to the Chrome bundles.
Tibor Goldschwendt19364ba2019-04-10 15:59:5562
63*** note
64**Note:** To make your own module you'll essentially have to replace every
65instance of `foo`/`Foo`/`FOO` with `your_feature_name`/`YourFeatureName`/
66`YOUR_FEATURE_NAME`.
67***
68
Christopher Grantf649d282020-01-09 22:56:0869### Reference DFM
70
71In addition to this guide, the
72[Test Dummy](https://cs.chromium.org/chromium/src/chrome/android/modules/test_dummy/test_dummy_module.gni)
73module serves as an actively-maintained reference DFM. Test Dummy is used in
74automated bundle testing, and covers both Java and native code and resource
75usage.
Tibor Goldschwendt19364ba2019-04-10 15:59:5576
77### Create DFM target
78
79DFMs are APKs. They have a manifest and can contain Java and native code as well
80as resources. This section walks you through creating the module target in our
81build system.
82
Tibor Goldschwendt68c5f722019-08-01 15:10:1583First, create the file
Henrique Nakashimacfdcce32020-04-24 22:19:3684`//chrome/android/modules/foo/internal/java/AndroidManifest.xml` and add:
Tibor Goldschwendt19364ba2019-04-10 15:59:5585
86```xml
Tibor Goldschwendt68c5f722019-08-01 15:10:1587<?xml version="1.0" encoding="utf-8"?>
Tibor Goldschwendt19364ba2019-04-10 15:59:5588<manifest xmlns:android="http://schemas.android.com/apk/res/android"
89 xmlns:dist="http://schemas.android.com/apk/distribution"
Tibor Goldschwendt5172118f2019-06-24 21:57:4790 featureSplit="foo">
Tibor Goldschwendt19364ba2019-04-10 15:59:5591
Tibor Goldschwendt19364ba2019-04-10 15:59:5592 <!-- dist:onDemand="true" makes this a separately installed module.
93 dist:onDemand="false" would always install the module alongside the
94 rest of Chrome. -->
95 <dist:module
96 dist:onDemand="true"
97 dist:title="@string/foo_module_title">
Ben Masone571ea5a2019-09-06 18:29:3798 <!-- This will fuse the module into the base APK if a system image
99 APK is built from this bundle. -->
100 <dist:fusing dist:include="true" />
Tibor Goldschwendt19364ba2019-04-10 15:59:55101 </dist:module>
102
Samuel Huang39c7db632019-05-15 14:57:18103 <!-- Remove android:hasCode="false" when adding Java code. -->
104 <application android:hasCode="false" />
Tibor Goldschwendt19364ba2019-04-10 15:59:55105</manifest>
106```
107
Tibor Goldschwendtaef8e392019-07-19 16:39:10108Next, create a descriptor configuring the Foo module. To do this, create
Henrique Nakashimacfdcce32020-04-24 22:19:36109`//chrome/android/modules/foo/foo_module.gni` and add the following:
Tibor Goldschwendt19364ba2019-04-10 15:59:55110
111```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10112foo_module_desc = {
113 name = "foo"
Tibor Goldschwendt68c5f722019-08-01 15:10:15114 android_manifest =
Henrique Nakashimacfdcce32020-04-24 22:19:36115 "//chrome/android/modules/foo/internal/java/AndroidManifest.xml"
Tibor Goldschwendt19364ba2019-04-10 15:59:55116}
117```
118
Tibor Goldschwendtaef8e392019-07-19 16:39:10119Then, add the module descriptor to the appropriate descriptor list in
120//chrome/android/modules/chrome_feature_modules.gni, e.g. the Chrome Modern
121list:
Tibor Goldschwendt19364ba2019-04-10 15:59:55122
123```gn
Henrique Nakashimacfdcce32020-04-24 22:19:36124import("//chrome/android/modules/foo/foo_module.gni")
Tibor Goldschwendt19364ba2019-04-10 15:59:55125...
Tibor Goldschwendtaef8e392019-07-19 16:39:10126chrome_modern_module_descs += [ foo_module_desc ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55127```
128
129The next step is to add Foo to the list of feature modules for UMA recording.
130For this, add `foo` to the `AndroidFeatureModuleName` in
131`//tools/metrics/histograms/histograms.xml`:
132
133```xml
134<histogram_suffixes name="AndroidFeatureModuleName" ...>
135 ...
136 <suffix name="foo" label="Super Duper Foo Module" />
137 ...
138</histogram_suffixes>
139```
140
Tibor Goldschwendtf430b272019-11-25 19:19:41141See [below](#metrics) for what metrics will be automatically collected after
142this step.
143
Tibor Goldschwendt19364ba2019-04-10 15:59:55144<!--- TODO(tiborg): Add info about install UI. -->
145Lastly, give your module a title that Chrome and Play can use for the install
146UI. To do this, add a string to
Adam Langley891ea2b2020-04-10 17:11:18147`//chrome/browser/ui/android/strings/android_chrome_strings.grd`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55148
149```xml
150...
151<message name="IDS_FOO_MODULE_TITLE"
152 desc="Text shown when the Foo module is referenced in install start, success,
153 failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to
154 'Installing Foo for Chrome…').">
155 Foo
156</message>
157...
158```
159
Samuel Huang7f2b53752019-05-23 15:10:05160*** note
161**Note:** This is for module title only. Other strings specific to the module
162should go in the module, not here (in the base module).
163***
164
Tibor Goldschwendt19364ba2019-04-10 15:59:55165Congrats! You added the DFM Foo to Monochrome. That is a big step but not very
166useful so far. In the next sections you'll learn how to add code and resources
167to it.
168
169
170### Building and installing modules
171
172Before we are going to jump into adding content to Foo, let's take a look on how
173to build and deploy the Monochrome bundle with the Foo DFM. The remainder of
174this guide assumes the environment variable `OUTDIR` is set to a properly
175configured GN build directory (e.g. `out/Debug`).
176
177To build and install the Monochrome bundle to your connected device, run:
178
179```shell
180$ autoninja -C $OUTDIR monochrome_public_bundle
181$ $OUTDIR/bin/monochrome_public_bundle install -m base -m foo
182```
183
184This will install Foo alongside the rest of Chrome. The rest of Chrome is called
Tibor Goldschwendtf430b272019-11-25 19:19:41185_base_ module in the bundle world. The base module will always be put on the
Tibor Goldschwendt19364ba2019-04-10 15:59:55186device when initially installing Chrome.
187
188*** note
Tibor Goldschwendtf430b272019-11-25 19:19:41189**Note:** The install script may install more modules than you specify, e.g.
190when there are default or conditionally installed modules (see
191[below](#conditional-install) for details).
Tibor Goldschwendt19364ba2019-04-10 15:59:55192***
193
194You can then check that the install worked with:
195
196```shell
197$ adb shell dumpsys package org.chromium.chrome | grep splits
198> splits=[base, config.en, foo]
199```
200
201Then try installing the Monochrome bundle without your module and print the
202installed modules:
203
204```shell
205$ $OUTDIR/bin/monochrome_public_bundle install -m base
206$ adb shell dumpsys package org.chromium.chrome | grep splits
207> splits=[base, config.en]
208```
209
210
Samuel Huang3dc9fce82020-02-26 18:09:57211### Adding Java code
Tibor Goldschwendt19364ba2019-04-10 15:59:55212
213To make Foo useful, let's add some Java code to it. This section will walk you
214through the required steps.
215
Tibor Goldschwendt573cf3022019-05-10 17:23:30216First, define a module interface for Foo. This is accomplished by adding the
217`@ModuleInterface` annotation to the Foo interface. This annotation
218automatically creates a `FooModule` class that can be used later to install and
219access the module. To do this, add the following in the new file
Henrique Nakashimacfdcce32020-04-24 22:19:36220`//chrome/browser/foo/android/java/src/org/chromium/chrome/browser/foo/Foo.java`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55221
222```java
Henrique Nakashimacfdcce32020-04-24 22:19:36223package org.chromium.chrome.browser.foo;
Tibor Goldschwendt19364ba2019-04-10 15:59:55224
Fred Mello2623e052019-10-02 20:18:04225import org.chromium.components.module_installer.builder.ModuleInterface;
Tibor Goldschwendt573cf3022019-05-10 17:23:30226
Tibor Goldschwendt19364ba2019-04-10 15:59:55227/** Interface to call into Foo feature. */
Henrique Nakashimacfdcce32020-04-24 22:19:36228@ModuleInterface(module = "foo", impl = "org.chromium.chrome.browser.FooImpl")
Tibor Goldschwendt19364ba2019-04-10 15:59:55229public interface Foo {
230 /** Magical function. */
231 void bar();
232}
233```
234
Tibor Goldschwendt19364ba2019-04-10 15:59:55235Next, define an implementation that goes into the module in the new file
Henrique Nakashimacfdcce32020-04-24 22:19:36236`//chrome/browser/foo/internal/android/java/src/org/chromium/chrome/browser/foo/FooImpl.java`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55237
238```java
Henrique Nakashimacfdcce32020-04-24 22:19:36239package org.chromium.chrome.browser.foo;
Tibor Goldschwendt19364ba2019-04-10 15:59:55240
241import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30242import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55243
Tibor Goldschwendt573cf3022019-05-10 17:23:30244@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55245public class FooImpl implements Foo {
246 @Override
247 public void bar() {
248 Log.i("FOO", "bar in module");
249 }
250}
251```
252
Tibor Goldschwendt19364ba2019-04-10 15:59:55253You can then use this provider to access the module if it is installed. To test
254that, instantiate Foo and call `bar()` somewhere in Chrome:
255
256```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30257if (FooModule.isInstalled()) {
258 FooModule.getImpl().bar();
Tibor Goldschwendt19364ba2019-04-10 15:59:55259} else {
260 Log.i("FOO", "module not installed");
261}
262```
263
Tibor Goldschwendt573cf3022019-05-10 17:23:30264The interface has to be available regardless of whether the Foo DFM is present.
Henrique Nakashimacfdcce32020-04-24 22:19:36265Therefore, put those classes into the base module, creating a new public
266build target in: `//chrome/browser/foo/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55267
268```gn
Henrique Nakashimacfdcce32020-04-24 22:19:36269import("//build/config/android/rules.gni")
270
271android_library("java") {
272 sources = [
273 "android/java/src/org/chromium/chrome/browser/foo/Foo.java",
274 ]
275}
Tibor Goldschwendt19364ba2019-04-10 15:59:55276```
277
Henrique Nakashimacfdcce32020-04-24 22:19:36278Then, depend on this target from where it is used as usual. For example, if the
279caller is in `chrome_java in //chrome/android/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55280
281```gn
282...
Tibor Goldschwendt19364ba2019-04-10 15:59:55283android_library("chrome_java") {
Henrique Nakashimacfdcce32020-04-24 22:19:36284 deps =[
285 ...
286 "//chrome/browser/foo:java",
287 ...
288 ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55289}
290...
291```
292
293The actual implementation, however, should go into the Foo DFM. For this
Henrique Nakashimacfdcce32020-04-24 22:19:36294purpose, create a new file `//chrome/browser/foo/internal/BUILD.gn` and
Tibor Goldschwendt68c5f722019-08-01 15:10:15295make a library with the module Java code in it:
Tibor Goldschwendt19364ba2019-04-10 15:59:55296
297```gn
298import("//build/config/android/rules.gni")
299
300android_library("java") {
301 # Define like ordinary Java Android library.
Natalie Chouinardcbdc6dc2019-12-24 00:02:35302 sources = [
Henrique Nakashimacfdcce32020-04-24 22:19:36303 "android/java/src/org/chromium/chrome/browser/foo/FooImpl.java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55304 # Add other Java classes that should go into the Foo DFM here.
305 ]
Fred Mellob32b3022019-06-21 18:10:11306 deps = [
Tibor Goldschwendt19364ba2019-04-10 15:59:55307 "//base:base_java",
Henrique Nakashimacfdcce32020-04-24 22:19:36308 # Put other Chrome libs into the classpath so that you can call into them
309 # from the Foo DFM.
310 "//chrome/browser/bar:java",
311 # The module can depend even on `chrome_java` due to factory magic, but this
312 # is discouraged. Consider passing a delegate interface in instead.
Tibor Goldschwendt19364ba2019-04-10 15:59:55313 "//chrome/android:chrome_java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55314 # Also, you'll need to depend on any //third_party or //components code you
315 # are using in the module code.
316 ]
317}
318```
319
Tibor Goldschwendtaef8e392019-07-19 16:39:10320Then, add this new library as a dependency of the Foo module descriptor in
Henrique Nakashimacfdcce32020-04-24 22:19:36321`//chrome/android/modules/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55322
323```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10324foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55325 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10326 java_deps = [
Henrique Nakashimacfdcce32020-04-24 22:19:36327 "//chrome/browser/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55328 ]
329}
330```
331
332Finally, tell Android that your module is now containing code. Do that by
Samuel Huang39c7db632019-05-15 14:57:18333removing the `android:hasCode="false"` attribute from the `<application>` tag in
Henrique Nakashimacfdcce32020-04-24 22:19:36334`//chrome/android/modules/foo/internal/java/AndroidManifest.xml`. You should be
Tibor Goldschwendt68c5f722019-08-01 15:10:15335left with an empty tag like so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55336
337```xml
338...
339 <application />
340...
341```
342
343Rebuild and install `monochrome_public_bundle`. Start Chrome and run through a
344flow that tries to executes `bar()`. Depending on whether you installed your
345module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
346logcat. Yay!
347
Christopher Grantf649d282020-01-09 22:56:08348### Adding pre-built native libraries
Tibor Goldschwendt19364ba2019-04-10 15:59:55349
Christopher Grant8fea5a12019-07-31 19:12:31350You can add a third-party native library (or any standalone library that doesn't
351depend on Chrome code) by adding it as a loadable module to the module descriptor in
Henrique Nakashimacfdcce32020-04-24 22:19:36352`//chrome/android/moduiles/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55353
354```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10355foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55356 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10357 loadable_modules_32_bit = [ "//path/to/32/bit/lib.so" ]
358 loadable_modules_64_bit = [ "//path/to/64/bit/lib.so" ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55359}
360```
361
Christopher Grant8fea5a12019-07-31 19:12:31362### Adding Chrome native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55363
Christopher Grantf649d282020-01-09 22:56:08364Chrome native code may be placed in a DFM. The easiest way to access native
365feature code is by calling it from Java via JNI. When a module is first
366accessed, its native library (or potentially libraries, if using a component
367build), are automatically opened by the DFM framework, and a feature-specific
368JNI method (supplied by the feature's implementation) is invoked. Hence, a
369module's Java code may freely use JNI to call module native code.
Christopher Grant8fea5a12019-07-31 19:12:31370
Christopher Grantf649d282020-01-09 22:56:08371Using the module framework and JNI to access the native code eliminates concerns
372with DFM library file names (which vary across build variants),
373`android_dlopen_ext()` (needed to open feature libraries), and use of dlsym().
Christopher Grant8fea5a12019-07-31 19:12:31374
Christopher Grantf649d282020-01-09 22:56:08375This mechanism can be extended if necessary by DFM implementers to facilitate
376subsequent native-native calls, by having a JNI-called initialization method
377create instance of a object or factory, and register it through a call to the
378base module's native code (DFM native code can call base module code directly).
Christopher Grant8fea5a12019-07-31 19:12:31379
Eric Stevenson8c9ab26b2019-08-30 15:44:40380#### JNI
381
382Read the `jni_generator` [docs](../base/android/jni_generator/README.md) before
383reading this section.
384
385There are some subtleties to how JNI registration works with DFMs:
386
387* Generated wrapper `ClassNameJni` classes are packaged into the DFM's dex file
388* The class containing the actual native definitions, `GEN_JNI.java`, is always
389 stored in the base module
Christopher Grantf649d282020-01-09 22:56:08390* If the DFM is only included in bundles that use [implicit JNI
391 registration](android_native_libraries.md#JNI-Native-Methods-Resolution) (i.e.
392 Monochrome and newer), then no extra consideration is necessary
Eric Stevenson8c9ab26b2019-08-30 15:44:40393* Otherwise, the DFM will need to provide a `generate_jni_registration` target
394 that will generate all of the native registration functions
395
Christopher Grantf649d282020-01-09 22:56:08396#### Calling DFM native code via JNI
397
398A linker-assisted partitioning system automates the placement of code into
399either the main Chrome library or feature-specific .so libraries. Feature code
400may continue to make use of core Chrome code (eg. base::) without modification,
401but Chrome must call feature code through a virtual interface (any "direct"
402calls to the feature code from the main library will cause the feature code to
403be pulled back into the main library).
404
405Partitioning is explained in [Android Native
406Libraries](android_native_libraries.md#partitioned-libraries).
407
408First, build a module native interface. Supply a JNI method named
409`JNI_OnLoad_foo` for the module framework to call, in
410`//chrome/android/modules/foo/internal/entrypoints.cc`. This method is invoked
411on all Chrome build variants, including Monochrome (unlike base module JNI).
412
413```c++
414#include "base/android/jni_generator/jni_generator_helper.h"
415#include "base/android/jni_utils.h"
416#include "chrome/android/modules/foo/internal/jni_registration.h"
417
418extern "C" {
419// This JNI registration method is found and called by module framework code.
420JNI_GENERATOR_EXPORT bool JNI_OnLoad_foo(JNIEnv* env) {
421 if (!base::android::IsSelectiveJniRegistrationEnabled(env) &&
422 !foo::RegisterNonMainDexNatives(env)) {
423 return false;
424 }
425 if (!foo::RegisterMainDexNatives(env)) {
426 return false;
427 }
428 return true;
429}
430} // extern "C"
431```
432
433Next, include the module entrypoint and related pieces in the build config at
434`//chrome/android/modules/foo/internal/BUILD.gn`:
435
436```gn
437import("//build/config/android/rules.gni")
438import("//chrome/android/modules/buildflags.gni")
439...
440
441# Put the JNI entrypoint in a component, so that the component build has a
442# library to include in the foo module. This makes things feel consistent with
443# a release build.
444component("foo") {
445 sources = [
446 "entrypoints.cc",
447 ]
448 deps = [
449 ":jni_registration",
Christopher Grantf649d282020-01-09 22:56:08450 "//base",
Henrique Nakashimacfdcce32020-04-24 22:19:36451 "//chrome/browser/foo/internal:native",
Christopher Grantf649d282020-01-09 22:56:08452 ]
453
454 # Instruct the compiler to flag exported entrypoint function as belonging in
455 # foo's library. The linker will use this information when creating the
456 # native libraries. The partition name must be <feature>_partition.
457 if (use_native_partitions) {
458 cflags = [ "-fsymbol-partition=foo_partition" ]
459 }
460}
461
462# Generate JNI registration for the methods called by the Java side. Note the
463# no_transitive_deps argument, which ensures that JNI is generated for only the
464# specified Java target, and not all its transitive deps (which could include
465# the base module).
466generate_jni_registration("jni_registration") {
Henrique Nakashimacfdcce32020-04-24 22:19:36467 targets = [ "//chrome/browser/foo/internal:java" ]
Christopher Grantf649d282020-01-09 22:56:08468 header_output = "$target_gen_dir/jni_registration.h"
469 namespace = "foo"
470 no_transitive_deps = true
471}
472
473# This group is a convenience alias representing the module's native code,
474# allowing it to be named "native" for clarity in module descriptors.
475group("native") {
476 deps = [
477 ":foo",
478 ]
479}
480```
481
482Now, over to the implementation of the module. These are the parts that
483shouldn't know or care whether they're living in a module or not.
484
485Add a stub implementation in
Henrique Nakashimacfdcce32020-04-24 22:19:36486`//chrome/browser/foo/internal/android/foo_impl.cc`:
Christopher Grantf649d282020-01-09 22:56:08487
488```c++
489#include "base/logging.h"
Henrique Nakashimacfdcce32020-04-24 22:19:36490#include "chrome/browser/foo/internal/jni_headers/FooImpl_jni.h"
Christopher Grantf649d282020-01-09 22:56:08491
492static int JNI_FooImpl_Execute(JNIEnv* env) {
493 LOG(INFO) << "Running foo feature code!";
494 return 123;
495}
496```
497
498And, the associated build config in
Henrique Nakashimacfdcce32020-04-24 22:19:36499`//chrome/browser/foo/internal/BUILD.gn`:
Christopher Grantf649d282020-01-09 22:56:08500
501```gn
502import("//build/config/android/rules.gni")
503
504...
505
506source_set("native") {
507 sources = [
Henrique Nakashimacfdcce32020-04-24 22:19:36508 "android/foo_impl.cc",
Christopher Grantf649d282020-01-09 22:56:08509 ]
510
511 deps = [
512 ":jni_headers",
513 "//base",
514 ]
515}
516
517generate_jni("jni_headers") {
518 sources = [
Henrique Nakashimacfdcce32020-04-24 22:19:36519 "android/java/src/org/chromium/chrome/browser/foo/FooImpl.java",
Christopher Grantf649d282020-01-09 22:56:08520 ]
521}
522```
523
524With a declaration of the native method on the Java side:
525
526```java
527public class FooImpl implements Foo {
528 ...
529
530 @NativeMethods
531 interface Natives {
532 int execute();
533 }
534}
535```
536
537Finally, augment the module descriptor in
538`//chrome/android/modules/foo/foo_module.gni` with the native dependencies:
539
540```gn
541foo_module_desc = {
542 ...
543 native_deps = [
Christopher Grantf649d282020-01-09 22:56:08544 "//chrome/android/modules/foo/internal:native",
Henrique Nakashimacfdcce32020-04-24 22:19:36545 "//chrome/browser/foo/internal:native",
Christopher Grantf649d282020-01-09 22:56:08546 ]
Samuel Huang3dc9fce82020-02-26 18:09:57547 load_native_on_get_impl = true
Christopher Grantf649d282020-01-09 22:56:08548}
549```
550
Samuel Huang3dc9fce82020-02-26 18:09:57551If `load_native_on_get_impl` is set to `true` then Chrome automatically loads
552Foo DFM's native libraries and PAK file resources when `FooModule.getImpl()` is
553called for the first time. The loading requires Chrome's main native libraries
554to be loaded. If you wish to call `FooModule.getImpl()` earlier than that, then
555you'd need to set `load_native_on_get_impl` to `false`, and manage native
556libraries / resources loading yourself (potentially, on start-up and on install,
557or on use).
558
Christopher Grantf649d282020-01-09 22:56:08559#### Calling feature module native code from base the module
560
561If planning to use direct native-native calls into DFM code, then the module
562should have a purely virtual interface available. The main module can obtain a
563pointer to a DFM-created object or factory (implemented by the feature), and
564call its virtual methods.
565
566Ideally, the interface to the feature will avoid feature-specific types. If a
567feature defines complex data types, and uses them in its own interface, then its
568likely the main library will utilize the code backing these types. That code,
569and anything it references, will in turn be pulled back into the main library,
570negating the intent to house code in the DFM.
571
572Therefore, designing the feature interface to use C types, C++ standard types,
573or classes that aren't expected to move out of Chrome's main library is ideal.
574If feature-specific classes are needed, they simply need to avoid referencing
575feature library internals.
Eric Stevenson8c9ab26b2019-08-30 15:44:40576
Christopher Grant8fea5a12019-07-31 19:12:31577### Adding Android resources
Tibor Goldschwendt19364ba2019-04-10 15:59:55578
579In this section we will add the required build targets to add Android resources
580to the Foo DFM.
581
Tibor Goldschwendt68c5f722019-08-01 15:10:15582First, add a resources target to
Henrique Nakashimacfdcce32020-04-24 22:19:36583`//chrome/browser/foo/internal/BUILD.gn` and add it as a dependency on
Tibor Goldschwendt68c5f722019-08-01 15:10:15584Foo's `java` target in the same file:
Tibor Goldschwendt19364ba2019-04-10 15:59:55585
586```gn
587...
588android_resources("java_resources") {
589 # Define like ordinary Android resources target.
590 ...
Henrique Nakashimacfdcce32020-04-24 22:19:36591 custom_package = "org.chromium.chrome.browser.foo"
Tibor Goldschwendt19364ba2019-04-10 15:59:55592}
593...
594android_library("java") {
595 ...
596 deps = [
597 ":java_resources",
598 ]
599}
600```
601
602To add strings follow steps
603[here](http://dev.chromium.org/developers/design-documents/ui-localization) to
604add new Java GRD file. Then create
Henrique Nakashimacfdcce32020-04-24 22:19:36605`//chrome/browser/foo/internal/android/resources/strings/android_foo_strings.grd` as
Tibor Goldschwendt68c5f722019-08-01 15:10:15606follows:
Tibor Goldschwendt19364ba2019-04-10 15:59:55607
608```xml
609<?xml version="1.0" encoding="UTF-8"?>
610<grit
611 current_release="1"
612 latest_public_release="0"
613 output_all_resource_defines="false">
614 <outputs>
615 <output
616 filename="values-am/android_foo_strings.xml"
617 lang="am"
618 type="android" />
619 <!-- List output file for all other supported languages. See
Henrique Nakashimaf5439f82021-01-29 23:52:24620 //chrome/browser/ui/android/strings/android_chrome_strings.grd for the
621 full list. -->
Tibor Goldschwendt19364ba2019-04-10 15:59:55622 ...
623 </outputs>
624 <translations>
625 <file lang="am" path="vr_translations/android_foo_strings_am.xtb" />
626 <!-- Here, too, list XTB files for all other supported languages. -->
627 ...
628 </translations>
Matt Stark1debb5de2021-02-15 16:08:24629 <release seq="1">
Tibor Goldschwendt19364ba2019-04-10 15:59:55630 <messages fallback_to_english="true">
631 <message name="IDS_BAR_IMPL_TEXT" desc="Magical string.">
632 impl
633 </message>
634 </messages>
635 </release>
636</grit>
637```
638
639Then, create a new GRD target and add it as a dependency on `java_resources` in
Henrique Nakashimacfdcce32020-04-24 22:19:36640`//chrome/browser/foo/internal/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55641
642```gn
643...
644java_strings_grd("java_strings_grd") {
645 defines = chrome_grit_defines
Henrique Nakashimacfdcce32020-04-24 22:19:36646 grd_file = "android/resources/strings/android_foo_strings.grd"
Tibor Goldschwendt19364ba2019-04-10 15:59:55647 outputs = [
648 "values-am/android_foo_strings.xml",
649 # Here, too, list output files for other supported languages.
650 ...
651 ]
652}
653...
654android_resources("java_resources") {
655 ...
656 deps = [":java_strings_grd"]
Henrique Nakashimacfdcce32020-04-24 22:19:36657 custom_package = "org.chromium.chrome.browser.foo"
Tibor Goldschwendt19364ba2019-04-10 15:59:55658}
659...
660```
661
662You can then access Foo's resources using the
Henrique Nakashimacfdcce32020-04-24 22:19:36663`org.chromium.chrome.browser.foo.R` class. To do this change
664`//chrome/browser/foo/internal/android/java/src/org/chromium/chrome/browser/foo/FooImpl.java`
Tibor Goldschwendt19364ba2019-04-10 15:59:55665to:
666
667```java
Henrique Nakashimacfdcce32020-04-24 22:19:36668package org.chromium.chrome.browser.foo;
Tibor Goldschwendt19364ba2019-04-10 15:59:55669
670import org.chromium.base.ContextUtils;
671import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30672import org.chromium.base.annotations.UsedByReflection;
Henrique Nakashimacfdcce32020-04-24 22:19:36673import org.chromium.chrome.browser.foo.R;
Tibor Goldschwendt19364ba2019-04-10 15:59:55674
Tibor Goldschwendt573cf3022019-05-10 17:23:30675@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55676public class FooImpl implements Foo {
677 @Override
678 public void bar() {
679 Log.i("FOO", ContextUtils.getApplicationContext().getString(
680 R.string.bar_impl_text));
681 }
682}
683```
684
Samuel Huang3dc9fce82020-02-26 18:09:57685### Adding non-string native resources
686
687This section describes how to add non-string native resources to Foo DFM.
688Key ideas:
689
690* The compiled resource file shipped with the DFM is `foo_resourcess.pak`.
691* At run time, native resources need to be loaded before use. Also, DFM native
692 resources can only be used from the Browser process.
693
694#### Creating PAK file
695
696Two ways to create `foo_resourcess.pak` (using GRIT) are:
697
6981. (Preferred) Use `foo_resourcess.grd` to refer to individual files (e.g.,
699 images, HTML, JS, or CSS) and assigns resource text IDs. `foo_resourcess.pak`
700 must have an entry in `/tools/gritsettings/resource_ids.spec`.
7011. Combine existing .pak files via `repack` rules in GN build files. This is
702 done by the DevUI DFM, which aggregates resources from many DevUI pages.
703
704#### Loading PAK file
705
706At runtime, `foo_resources.pak` needs to be loaded (memory-mapped) before any of
707its resource gets used. Alternatives to do this are:
708
7091. (Simplest) Specify native resources (with native libraries if any exist) to
710 be automatically loaded on first call to `FooModule.getImpl()`. This behavior
711 is specified via `load_native_on_get_impl = true` in `foo_module_desc`.
7121. In Java code, call `FooModule.ensureNativeLoaded()`.
7131. In C++ code, use JNI to call `FooModule.ensureNativeLoaded()`. The code to do
714 this can be placed in a helper class, which can also have JNI calls to
715 `FooModule.isInstalled()` and `FooModule.installModule()`.
716
717#### Cautionary notes
718
719Compiling `foo_resources.pak` auto-generates `foo_resources.h`, which defines
720textual resource IDs, e.g., `IDR_FOO_HTML`. C++ code then uses these IDs to get
721resource bytes. Unfortunately, this behavior is fragile: If `IDR_FOO_HTML` is
722accessed before the Foo DFM is (a) installed, or (b) loaded, then runtime error
723ensues! Some mitigation strategies are as follows:
724
725* (Ideal) Access Foo DFM's native resources only from code in Foo DFM's native
726 libraries. So by the time that `IDR_FOO_HTML` is accessed, everything is
727 already in place! This isn't always possible; henceforth we assume that
728 `IDR_FOO_HTML` is accessed by code in the base DFM.
729* Before accessing IDR_FOO_HTML, ensure Foo DFM is installed and loaded. The
730 latter can use `FooModule.ensureNativeLoaded()` (needs to be called from
731 Browser thread).
732* Use inclusion of `foo_resources.h` to restrict availability of `IDR_FOO_HTML`.
733 Only C++ files dedicated to "DFM-gated code" (code that runs only when its DFM
734 is installed and loaded) should include `foo_resources.h`.
735
736#### Associating native resources with DFM
737
738Here are the main GN changes to specify PAK files and default loading behavior
739for a DFM's native resources:
740
741```gn
742foo_module_desc = {
743 ...
Henrique Nakashimacfdcce32020-04-24 22:19:36744 paks = [ "$root_gen_dir/chrome/browser/foo/internal/foo_resourcess.pak" ]
745 pak_deps = [ "//chrome/browser/foo/internal:foo_paks" ]
Samuel Huang3dc9fce82020-02-26 18:09:57746 load_native_on_get_impl = true
747}
748```
749
750Note that `load_native_on_get_impl` specifies both native libraries and native
751resources.
752
Tibor Goldschwendt19364ba2019-04-10 15:59:55753
754### Module install
755
756So far, we have installed the Foo DFM as a true split (`-m foo` option on the
757install script). In production, however, we have to explicitly install the Foo
Tibor Goldschwendt68c5f722019-08-01 15:10:15758DFM for users to get it. There are three install options: _on-demand_,
759_deferred_ and _conditional_.
Tibor Goldschwendt19364ba2019-04-10 15:59:55760
Tibor Goldschwendt19364ba2019-04-10 15:59:55761#### On-demand install
762
763On-demand requesting a module will try to download and install the
764module as soon as possible regardless of whether the user is on a metered
765connection or whether they have turned updates off in the Play Store app.
766
Tibor Goldschwendt573cf3022019-05-10 17:23:30767You can use the autogenerated module class to on-demand install the module like
768so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55769
770```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30771FooModule.install((success) -> {
772 if (success) {
773 FooModule.getImpl().bar();
774 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55775});
776```
777
778**Optionally**, you can show UI telling the user about the install flow. For
Tibor Goldschwendt573cf3022019-05-10 17:23:30779this, add a function like the one below. Note, it is possible
Tibor Goldschwendt19364ba2019-04-10 15:59:55780to only show either one of the install, failure and success UI or any
781combination of the three.
782
783```java
784public static void installModuleWithUi(
785 Tab tab, OnModuleInstallFinishedListener onFinishedListener) {
786 ModuleInstallUi ui =
787 new ModuleInstallUi(
788 tab,
789 R.string.foo_module_title,
790 new ModuleInstallUi.FailureUiListener() {
791 @Override
Samuel Huangfebcccd2019-08-21 20:48:47792 public void onFailureUiResponse(retry) {
793 if (retry) {
794 installModuleWithUi(tab, onFinishedListener);
795 } else {
796 onFinishedListener.onFinished(false);
797 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55798 }
799 });
800 // At the time of writing, shows toast informing user about install start.
801 ui.showInstallStartUi();
Tibor Goldschwendt573cf3022019-05-10 17:23:30802 FooModule.install(
Tibor Goldschwendt19364ba2019-04-10 15:59:55803 (success) -> {
804 if (!success) {
805 // At the time of writing, shows infobar allowing user
806 // to retry install.
807 ui.showInstallFailureUi();
808 return;
809 }
810 // At the time of writing, shows toast informing user about
811 // install success.
812 ui.showInstallSuccessUi();
813 onFinishedListener.onFinished(true);
814 });
815}
816```
817
818To test on-demand install, "fake-install" the DFM. It's fake because
819the DFM is not installed as a true split. Instead it will be emulated by Chrome.
820Fake-install and launch Chrome with the following command:
821
822```shell
823$ $OUTDIR/bin/monochrome_public_bundle install -m base -f foo
Samuel Huang39c7db632019-05-15 14:57:18824$ $OUTDIR/bin/monochrome_public_bundle launch --args="--fake-feature-module-install"
Tibor Goldschwendt19364ba2019-04-10 15:59:55825```
826
827When running the install code, the Foo DFM module will be emulated.
828This will be the case in production right after installing the module. Emulation
829will last until Play Store has a chance to install your module as a true split.
Peter Wen577a6fe52019-12-11 22:02:05830This usually takes about a day. After it has been installed, it will be updated
831atomically alongside Chrome. Always check that it is installed and available
832before invoking code within the DFM.
Tibor Goldschwendt19364ba2019-04-10 15:59:55833
834*** note
835**Warning:** There are subtle differences between emulating a module and
836installing it as a true split. We therefore recommend that you always test both
837install methods.
838***
839
Samuel Huang6f5c7ddb82020-05-14 17:10:52840*** note
841To simplify development, the DevUI DFM (dev_ui) is installed by default, i.e.,
842`-m dev_ui` is implied by default. This is overridden by:
843* `--no-module dev_ui`, to test error from missing DevUI,
844* `-f dev_ui`, for fake module install.
845***
Tibor Goldschwendt19364ba2019-04-10 15:59:55846
847#### Deferred install
848
849Deferred install means that the DFM is installed in the background when the
850device is on an unmetered connection and charging. The DFM will only be
851available after Chrome restarts. When deferred installing a module it will
852not be faked installed.
853
854To defer install Foo do the following:
855
856```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30857FooModule.installDeferred();
Tibor Goldschwendt19364ba2019-04-10 15:59:55858```
859
Tibor Goldschwendt68c5f722019-08-01 15:10:15860#### Conditional install
861
862Conditional install means the DFM will be installed automatically upon first
863installing or updating Chrome if the device supports a particular feature.
864Conditional install is configured in the module's manifest. To install your
865module on all Daydream-ready devices for instance, your
Henrique Nakashimacfdcce32020-04-24 22:19:36866`//chrome/android/modules/foo/internal/java/AndroidManifest.xml` should look
Tibor Goldschwendt68c5f722019-08-01 15:10:15867like this:
868
869```xml
870<?xml version="1.0" encoding="utf-8"?>
871<manifest xmlns:android="http://schemas.android.com/apk/res/android"
872 xmlns:dist="http://schemas.android.com/apk/distribution"
873 featureSplit="foo">
874
875 <dist:module
876 dist:instant="false"
877 dist:title="@string/foo_module_title">
Ben Masone571ea5a2019-09-06 18:29:37878 <dist:fusing dist:include="true" />
Tibor Goldschwendt68c5f722019-08-01 15:10:15879 <dist:delivery>
880 <dist:install-time>
881 <dist:conditions>
882 <dist:device-feature
883 dist:name="android.hardware.vr.high_performance" />
884 </dist:conditions>
885 </dist:install-time>
886 <!-- Allows on-demand or deferred install on non-Daydream-ready
887 devices. -->
888 <dist:on-demand />
889 </dist:delivery>
890 </dist:module>
891
892 <application />
893</manifest>
894```
895
Andrew Grievef6069feb2021-04-29 18:29:17896You can also specify no conditions to have your module always installed.
897You might want to do this in order to delay the performance implications
898of loading your module until its first use (true only on Android O+ where
899[android:isolatedSplits](https://developer.android.com/reference/android/R.attr#isolatedSplits)
900is supported. See [go/isolated-splits-dev-guide](http://go/isolated-splits-dev-guide)
901(googlers only).
902
Tibor Goldschwendtf430b272019-11-25 19:19:41903### Metrics
904
905After adding your module to `AndroidFeatureModuleName` (see
906[above](#create-dfm-target)) we will collect, among others, the following
907metrics:
908
909* `Android.FeatureModules.AvailabilityStatus.Foo`: Measures your module's
910 install penetration. That is, the share of users who eventually installed
911 the module after requesting it (once or multiple times).
912
913* `Android.FeatureModules.InstallStatus.Foo`: The result of an on-demand
914 install request. Can be success or one of several error conditions.
915
916* `Android.FeatureModules.UncachedAwakeInstallDuration.Foo`: The duration to
917 install your module successfully after on-demand requesting it.
918
Tibor Goldschwendt19364ba2019-04-10 15:59:55919
Andrew Grievef6069feb2021-04-29 18:29:17920### chrome_public_apk and Integration Tests
Tibor Goldschwendt19364ba2019-04-10 15:59:55921
Andrew Grievef6069feb2021-04-29 18:29:17922To make the Foo feature available in the non-bundle `chrome_public_apk`
923target, add the `java` target to the `chrome_public_common_apk_or_module_tmpl`
924in `//chrome/android/chrome_public_apk_tmpl.gni` like so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55925
926```gn
927template("chrome_public_common_apk_or_module_tmpl") {
928 ...
929 target(_target_type, target_name) {
930 ...
931 if (_target_type != "android_app_bundle_module") {
932 deps += [
Henrique Nakashimacfdcce32020-04-24 22:19:36933 "//chrome/browser/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55934 ]
935 }
936 }
937}
938```
939
Andrew Grievef6069feb2021-04-29 18:29:17940You may also have to add `java` as a dependency of `chrome_test_java` if you want
941to call into Foo from test code.