blob: 3e5dd23d7321d446cec323b8889f3defd48c22c7 [file] [log] [blame] [view]
Tibor Goldschwendt19364ba2019-04-10 15:59:551# Dynamic Feature Modules (DFMs)
2
3[Android App bundles and Dynamic Feature Modules (DFMs)](https://developer.android.com/guide/app-bundle)
4is a Play Store feature that allows delivering pieces of an app when they are
5needed rather than at install time. We use DFMs to modularize Chrome and make
6Chrome's install size smaller.
7
8[TOC]
9
10
11## Limitations
12
Tibor Goldschwendt68c5f722019-08-01 15:10:1513DFMs have the following limitations:
Tibor Goldschwendt19364ba2019-04-10 15:59:5514
15* **WebView:** We don't support DFMs for WebView. If your feature is used by
Tibor Goldschwendt68c5f722019-08-01 15:10:1516 WebView you cannot put it into a DFM.
Tibor Goldschwendt19364ba2019-04-10 15:59:5517* **Android K:** DFMs are based on split APKs, a feature introduced in Android
18 L. Therefore, we don't support DFMs on Android K. As a workaround
Tibor Goldschwendt68c5f722019-08-01 15:10:1519 you can add your feature to the Android K APK build. See below for details.
Tibor Goldschwendt19364ba2019-04-10 15:59:5520
21## Getting started
22
23This guide walks you through the steps to create a DFM called _Foo_ and add it
Tibor Goldschwendtaef8e392019-07-19 16:39:1024to the Chrome bundles.
Tibor Goldschwendt19364ba2019-04-10 15:59:5525
26*** note
27**Note:** To make your own module you'll essentially have to replace every
28instance of `foo`/`Foo`/`FOO` with `your_feature_name`/`YourFeatureName`/
29`YOUR_FEATURE_NAME`.
30***
31
Christopher Grantf649d282020-01-09 22:56:0832### Reference DFM
33
34In addition to this guide, the
35[Test Dummy](https://cs.chromium.org/chromium/src/chrome/android/modules/test_dummy/test_dummy_module.gni)
36module serves as an actively-maintained reference DFM. Test Dummy is used in
37automated bundle testing, and covers both Java and native code and resource
38usage.
Tibor Goldschwendt19364ba2019-04-10 15:59:5539
40### Create DFM target
41
42DFMs are APKs. They have a manifest and can contain Java and native code as well
43as resources. This section walks you through creating the module target in our
44build system.
45
Tibor Goldschwendt68c5f722019-08-01 15:10:1546First, create the file
Henrique Nakashimacfdcce32020-04-24 22:19:3647`//chrome/android/modules/foo/internal/java/AndroidManifest.xml` and add:
Tibor Goldschwendt19364ba2019-04-10 15:59:5548
49```xml
Tibor Goldschwendt68c5f722019-08-01 15:10:1550<?xml version="1.0" encoding="utf-8"?>
Tibor Goldschwendt19364ba2019-04-10 15:59:5551<manifest xmlns:android="http://schemas.android.com/apk/res/android"
52 xmlns:dist="http://schemas.android.com/apk/distribution"
Tibor Goldschwendt5172118f2019-06-24 21:57:4753 featureSplit="foo">
Tibor Goldschwendt19364ba2019-04-10 15:59:5554
Tibor Goldschwendt19364ba2019-04-10 15:59:5555 <!-- dist:onDemand="true" makes this a separately installed module.
56 dist:onDemand="false" would always install the module alongside the
57 rest of Chrome. -->
58 <dist:module
59 dist:onDemand="true"
60 dist:title="@string/foo_module_title">
Ben Masone571ea5a2019-09-06 18:29:3761 <!-- This will fuse the module into the base APK if a system image
62 APK is built from this bundle. -->
63 <dist:fusing dist:include="true" />
Tibor Goldschwendt19364ba2019-04-10 15:59:5564 </dist:module>
65
Samuel Huang39c7db632019-05-15 14:57:1866 <!-- Remove android:hasCode="false" when adding Java code. -->
67 <application android:hasCode="false" />
Tibor Goldschwendt19364ba2019-04-10 15:59:5568</manifest>
69```
70
Tibor Goldschwendtaef8e392019-07-19 16:39:1071Next, create a descriptor configuring the Foo module. To do this, create
Henrique Nakashimacfdcce32020-04-24 22:19:3672`//chrome/android/modules/foo/foo_module.gni` and add the following:
Tibor Goldschwendt19364ba2019-04-10 15:59:5573
74```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:1075foo_module_desc = {
76 name = "foo"
Tibor Goldschwendt68c5f722019-08-01 15:10:1577 android_manifest =
Henrique Nakashimacfdcce32020-04-24 22:19:3678 "//chrome/android/modules/foo/internal/java/AndroidManifest.xml"
Tibor Goldschwendt19364ba2019-04-10 15:59:5579}
80```
81
Tibor Goldschwendtaef8e392019-07-19 16:39:1082Then, add the module descriptor to the appropriate descriptor list in
83//chrome/android/modules/chrome_feature_modules.gni, e.g. the Chrome Modern
84list:
Tibor Goldschwendt19364ba2019-04-10 15:59:5585
86```gn
Henrique Nakashimacfdcce32020-04-24 22:19:3687import("//chrome/android/modules/foo/foo_module.gni")
Tibor Goldschwendt19364ba2019-04-10 15:59:5588...
Tibor Goldschwendtaef8e392019-07-19 16:39:1089chrome_modern_module_descs += [ foo_module_desc ]
Tibor Goldschwendt19364ba2019-04-10 15:59:5590```
91
92The next step is to add Foo to the list of feature modules for UMA recording.
93For this, add `foo` to the `AndroidFeatureModuleName` in
94`//tools/metrics/histograms/histograms.xml`:
95
96```xml
97<histogram_suffixes name="AndroidFeatureModuleName" ...>
98 ...
99 <suffix name="foo" label="Super Duper Foo Module" />
100 ...
101</histogram_suffixes>
102```
103
Tibor Goldschwendtf430b272019-11-25 19:19:41104See [below](#metrics) for what metrics will be automatically collected after
105this step.
106
Tibor Goldschwendt19364ba2019-04-10 15:59:55107<!--- TODO(tiborg): Add info about install UI. -->
108Lastly, give your module a title that Chrome and Play can use for the install
109UI. To do this, add a string to
Adam Langley891ea2b2020-04-10 17:11:18110`//chrome/browser/ui/android/strings/android_chrome_strings.grd`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55111
112```xml
113...
114<message name="IDS_FOO_MODULE_TITLE"
115 desc="Text shown when the Foo module is referenced in install start, success,
116 failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to
117 'Installing Foo for Chrome…').">
118 Foo
119</message>
120...
121```
122
Samuel Huang7f2b53752019-05-23 15:10:05123*** note
124**Note:** This is for module title only. Other strings specific to the module
125should go in the module, not here (in the base module).
126***
127
Tibor Goldschwendt19364ba2019-04-10 15:59:55128Congrats! You added the DFM Foo to Monochrome. That is a big step but not very
129useful so far. In the next sections you'll learn how to add code and resources
130to it.
131
132
133### Building and installing modules
134
135Before we are going to jump into adding content to Foo, let's take a look on how
136to build and deploy the Monochrome bundle with the Foo DFM. The remainder of
137this guide assumes the environment variable `OUTDIR` is set to a properly
138configured GN build directory (e.g. `out/Debug`).
139
140To build and install the Monochrome bundle to your connected device, run:
141
142```shell
143$ autoninja -C $OUTDIR monochrome_public_bundle
144$ $OUTDIR/bin/monochrome_public_bundle install -m base -m foo
145```
146
147This will install Foo alongside the rest of Chrome. The rest of Chrome is called
Tibor Goldschwendtf430b272019-11-25 19:19:41148_base_ module in the bundle world. The base module will always be put on the
Tibor Goldschwendt19364ba2019-04-10 15:59:55149device when initially installing Chrome.
150
151*** note
Tibor Goldschwendtf430b272019-11-25 19:19:41152**Note:** The install script may install more modules than you specify, e.g.
153when there are default or conditionally installed modules (see
154[below](#conditional-install) for details).
Tibor Goldschwendt19364ba2019-04-10 15:59:55155***
156
157You can then check that the install worked with:
158
159```shell
160$ adb shell dumpsys package org.chromium.chrome | grep splits
161> splits=[base, config.en, foo]
162```
163
164Then try installing the Monochrome bundle without your module and print the
165installed modules:
166
167```shell
168$ $OUTDIR/bin/monochrome_public_bundle install -m base
169$ adb shell dumpsys package org.chromium.chrome | grep splits
170> splits=[base, config.en]
171```
172
173
Samuel Huang3dc9fce82020-02-26 18:09:57174### Adding Java code
Tibor Goldschwendt19364ba2019-04-10 15:59:55175
176To make Foo useful, let's add some Java code to it. This section will walk you
177through the required steps.
178
Tibor Goldschwendt573cf3022019-05-10 17:23:30179First, define a module interface for Foo. This is accomplished by adding the
180`@ModuleInterface` annotation to the Foo interface. This annotation
181automatically creates a `FooModule` class that can be used later to install and
182access the module. To do this, add the following in the new file
Henrique Nakashimacfdcce32020-04-24 22:19:36183`//chrome/browser/foo/android/java/src/org/chromium/chrome/browser/foo/Foo.java`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55184
185```java
Henrique Nakashimacfdcce32020-04-24 22:19:36186package org.chromium.chrome.browser.foo;
Tibor Goldschwendt19364ba2019-04-10 15:59:55187
Fred Mello2623e052019-10-02 20:18:04188import org.chromium.components.module_installer.builder.ModuleInterface;
Tibor Goldschwendt573cf3022019-05-10 17:23:30189
Tibor Goldschwendt19364ba2019-04-10 15:59:55190/** Interface to call into Foo feature. */
Henrique Nakashimacfdcce32020-04-24 22:19:36191@ModuleInterface(module = "foo", impl = "org.chromium.chrome.browser.FooImpl")
Tibor Goldschwendt19364ba2019-04-10 15:59:55192public interface Foo {
193 /** Magical function. */
194 void bar();
195}
196```
197
Tibor Goldschwendt19364ba2019-04-10 15:59:55198Next, define an implementation that goes into the module in the new file
Henrique Nakashimacfdcce32020-04-24 22:19:36199`//chrome/browser/foo/internal/android/java/src/org/chromium/chrome/browser/foo/FooImpl.java`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55200
201```java
Henrique Nakashimacfdcce32020-04-24 22:19:36202package org.chromium.chrome.browser.foo;
Tibor Goldschwendt19364ba2019-04-10 15:59:55203
204import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30205import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55206
Tibor Goldschwendt573cf3022019-05-10 17:23:30207@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55208public class FooImpl implements Foo {
209 @Override
210 public void bar() {
211 Log.i("FOO", "bar in module");
212 }
213}
214```
215
Tibor Goldschwendt19364ba2019-04-10 15:59:55216You can then use this provider to access the module if it is installed. To test
217that, instantiate Foo and call `bar()` somewhere in Chrome:
218
219```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30220if (FooModule.isInstalled()) {
221 FooModule.getImpl().bar();
Tibor Goldschwendt19364ba2019-04-10 15:59:55222} else {
223 Log.i("FOO", "module not installed");
224}
225```
226
Tibor Goldschwendt573cf3022019-05-10 17:23:30227The interface has to be available regardless of whether the Foo DFM is present.
Henrique Nakashimacfdcce32020-04-24 22:19:36228Therefore, put those classes into the base module, creating a new public
229build target in: `//chrome/browser/foo/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55230
231```gn
Henrique Nakashimacfdcce32020-04-24 22:19:36232import("//build/config/android/rules.gni")
233
234android_library("java") {
235 sources = [
236 "android/java/src/org/chromium/chrome/browser/foo/Foo.java",
237 ]
238}
Tibor Goldschwendt19364ba2019-04-10 15:59:55239```
240
Henrique Nakashimacfdcce32020-04-24 22:19:36241Then, depend on this target from where it is used as usual. For example, if the
242caller is in `chrome_java in //chrome/android/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55243
244```gn
245...
Tibor Goldschwendt19364ba2019-04-10 15:59:55246android_library("chrome_java") {
Henrique Nakashimacfdcce32020-04-24 22:19:36247 deps =[
248 ...
249 "//chrome/browser/foo:java",
250 ...
251 ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55252}
253...
254```
255
256The actual implementation, however, should go into the Foo DFM. For this
Henrique Nakashimacfdcce32020-04-24 22:19:36257purpose, create a new file `//chrome/browser/foo/internal/BUILD.gn` and
Tibor Goldschwendt68c5f722019-08-01 15:10:15258make a library with the module Java code in it:
Tibor Goldschwendt19364ba2019-04-10 15:59:55259
260```gn
261import("//build/config/android/rules.gni")
262
263android_library("java") {
264 # Define like ordinary Java Android library.
Natalie Chouinardcbdc6dc2019-12-24 00:02:35265 sources = [
Henrique Nakashimacfdcce32020-04-24 22:19:36266 "android/java/src/org/chromium/chrome/browser/foo/FooImpl.java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55267 # Add other Java classes that should go into the Foo DFM here.
268 ]
Fred Mellob32b3022019-06-21 18:10:11269 deps = [
Tibor Goldschwendt19364ba2019-04-10 15:59:55270 "//base:base_java",
Henrique Nakashimacfdcce32020-04-24 22:19:36271 # Put other Chrome libs into the classpath so that you can call into them
272 # from the Foo DFM.
273 "//chrome/browser/bar:java",
274 # The module can depend even on `chrome_java` due to factory magic, but this
275 # is discouraged. Consider passing a delegate interface in instead.
Tibor Goldschwendt19364ba2019-04-10 15:59:55276 "//chrome/android:chrome_java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55277 # Also, you'll need to depend on any //third_party or //components code you
278 # are using in the module code.
279 ]
280}
281```
282
Tibor Goldschwendtaef8e392019-07-19 16:39:10283Then, add this new library as a dependency of the Foo module descriptor in
Henrique Nakashimacfdcce32020-04-24 22:19:36284`//chrome/android/modules/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55285
286```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10287foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55288 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10289 java_deps = [
Henrique Nakashimacfdcce32020-04-24 22:19:36290 "//chrome/browser/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55291 ]
292}
293```
294
295Finally, tell Android that your module is now containing code. Do that by
Samuel Huang39c7db632019-05-15 14:57:18296removing the `android:hasCode="false"` attribute from the `<application>` tag in
Henrique Nakashimacfdcce32020-04-24 22:19:36297`//chrome/android/modules/foo/internal/java/AndroidManifest.xml`. You should be
Tibor Goldschwendt68c5f722019-08-01 15:10:15298left with an empty tag like so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55299
300```xml
301...
302 <application />
303...
304```
305
306Rebuild and install `monochrome_public_bundle`. Start Chrome and run through a
307flow that tries to executes `bar()`. Depending on whether you installed your
308module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
309logcat. Yay!
310
Christopher Grantf649d282020-01-09 22:56:08311### Adding pre-built native libraries
Tibor Goldschwendt19364ba2019-04-10 15:59:55312
Christopher Grant8fea5a12019-07-31 19:12:31313You can add a third-party native library (or any standalone library that doesn't
314depend on Chrome code) by adding it as a loadable module to the module descriptor in
Henrique Nakashimacfdcce32020-04-24 22:19:36315`//chrome/android/moduiles/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55316
317```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10318foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55319 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10320 loadable_modules_32_bit = [ "//path/to/32/bit/lib.so" ]
321 loadable_modules_64_bit = [ "//path/to/64/bit/lib.so" ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55322}
323```
324
Christopher Grant8fea5a12019-07-31 19:12:31325### Adding Chrome native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55326
Christopher Grantf649d282020-01-09 22:56:08327Chrome native code may be placed in a DFM. The easiest way to access native
328feature code is by calling it from Java via JNI. When a module is first
329accessed, its native library (or potentially libraries, if using a component
330build), are automatically opened by the DFM framework, and a feature-specific
331JNI method (supplied by the feature's implementation) is invoked. Hence, a
332module's Java code may freely use JNI to call module native code.
Christopher Grant8fea5a12019-07-31 19:12:31333
Christopher Grantf649d282020-01-09 22:56:08334Using the module framework and JNI to access the native code eliminates concerns
335with DFM library file names (which vary across build variants),
336`android_dlopen_ext()` (needed to open feature libraries), and use of dlsym().
Christopher Grant8fea5a12019-07-31 19:12:31337
Christopher Grantf649d282020-01-09 22:56:08338This mechanism can be extended if necessary by DFM implementers to facilitate
339subsequent native-native calls, by having a JNI-called initialization method
340create instance of a object or factory, and register it through a call to the
341base module's native code (DFM native code can call base module code directly).
Christopher Grant8fea5a12019-07-31 19:12:31342
Eric Stevenson8c9ab26b2019-08-30 15:44:40343#### JNI
344
345Read the `jni_generator` [docs](../base/android/jni_generator/README.md) before
346reading this section.
347
348There are some subtleties to how JNI registration works with DFMs:
349
350* Generated wrapper `ClassNameJni` classes are packaged into the DFM's dex file
351* The class containing the actual native definitions, `GEN_JNI.java`, is always
352 stored in the base module
Christopher Grantf649d282020-01-09 22:56:08353* If the DFM is only included in bundles that use [implicit JNI
354 registration](android_native_libraries.md#JNI-Native-Methods-Resolution) (i.e.
355 Monochrome and newer), then no extra consideration is necessary
Eric Stevenson8c9ab26b2019-08-30 15:44:40356* Otherwise, the DFM will need to provide a `generate_jni_registration` target
357 that will generate all of the native registration functions
358
Christopher Grantf649d282020-01-09 22:56:08359#### Calling DFM native code via JNI
360
361A linker-assisted partitioning system automates the placement of code into
362either the main Chrome library or feature-specific .so libraries. Feature code
363may continue to make use of core Chrome code (eg. base::) without modification,
364but Chrome must call feature code through a virtual interface (any "direct"
365calls to the feature code from the main library will cause the feature code to
366be pulled back into the main library).
367
368Partitioning is explained in [Android Native
369Libraries](android_native_libraries.md#partitioned-libraries).
370
371First, build a module native interface. Supply a JNI method named
372`JNI_OnLoad_foo` for the module framework to call, in
373`//chrome/android/modules/foo/internal/entrypoints.cc`. This method is invoked
374on all Chrome build variants, including Monochrome (unlike base module JNI).
375
376```c++
377#include "base/android/jni_generator/jni_generator_helper.h"
378#include "base/android/jni_utils.h"
379#include "chrome/android/modules/foo/internal/jni_registration.h"
380
381extern "C" {
382// This JNI registration method is found and called by module framework code.
383JNI_GENERATOR_EXPORT bool JNI_OnLoad_foo(JNIEnv* env) {
384 if (!base::android::IsSelectiveJniRegistrationEnabled(env) &&
385 !foo::RegisterNonMainDexNatives(env)) {
386 return false;
387 }
388 if (!foo::RegisterMainDexNatives(env)) {
389 return false;
390 }
391 return true;
392}
393} // extern "C"
394```
395
396Next, include the module entrypoint and related pieces in the build config at
397`//chrome/android/modules/foo/internal/BUILD.gn`:
398
399```gn
400import("//build/config/android/rules.gni")
401import("//chrome/android/modules/buildflags.gni")
402...
403
404# Put the JNI entrypoint in a component, so that the component build has a
405# library to include in the foo module. This makes things feel consistent with
406# a release build.
407component("foo") {
408 sources = [
409 "entrypoints.cc",
410 ]
411 deps = [
412 ":jni_registration",
Christopher Grantf649d282020-01-09 22:56:08413 "//base",
Henrique Nakashimacfdcce32020-04-24 22:19:36414 "//chrome/browser/foo/internal:native",
Christopher Grantf649d282020-01-09 22:56:08415 ]
416
417 # Instruct the compiler to flag exported entrypoint function as belonging in
418 # foo's library. The linker will use this information when creating the
419 # native libraries. The partition name must be <feature>_partition.
420 if (use_native_partitions) {
421 cflags = [ "-fsymbol-partition=foo_partition" ]
422 }
423}
424
425# Generate JNI registration for the methods called by the Java side. Note the
426# no_transitive_deps argument, which ensures that JNI is generated for only the
427# specified Java target, and not all its transitive deps (which could include
428# the base module).
429generate_jni_registration("jni_registration") {
Henrique Nakashimacfdcce32020-04-24 22:19:36430 targets = [ "//chrome/browser/foo/internal:java" ]
Christopher Grantf649d282020-01-09 22:56:08431 header_output = "$target_gen_dir/jni_registration.h"
432 namespace = "foo"
433 no_transitive_deps = true
434}
435
436# This group is a convenience alias representing the module's native code,
437# allowing it to be named "native" for clarity in module descriptors.
438group("native") {
439 deps = [
440 ":foo",
441 ]
442}
443```
444
445Now, over to the implementation of the module. These are the parts that
446shouldn't know or care whether they're living in a module or not.
447
448Add a stub implementation in
Henrique Nakashimacfdcce32020-04-24 22:19:36449`//chrome/browser/foo/internal/android/foo_impl.cc`:
Christopher Grantf649d282020-01-09 22:56:08450
451```c++
452#include "base/logging.h"
Henrique Nakashimacfdcce32020-04-24 22:19:36453#include "chrome/browser/foo/internal/jni_headers/FooImpl_jni.h"
Christopher Grantf649d282020-01-09 22:56:08454
455static int JNI_FooImpl_Execute(JNIEnv* env) {
456 LOG(INFO) << "Running foo feature code!";
457 return 123;
458}
459```
460
461And, the associated build config in
Henrique Nakashimacfdcce32020-04-24 22:19:36462`//chrome/browser/foo/internal/BUILD.gn`:
Christopher Grantf649d282020-01-09 22:56:08463
464```gn
465import("//build/config/android/rules.gni")
466
467...
468
469source_set("native") {
470 sources = [
Henrique Nakashimacfdcce32020-04-24 22:19:36471 "android/foo_impl.cc",
Christopher Grantf649d282020-01-09 22:56:08472 ]
473
474 deps = [
475 ":jni_headers",
476 "//base",
477 ]
478}
479
480generate_jni("jni_headers") {
481 sources = [
Henrique Nakashimacfdcce32020-04-24 22:19:36482 "android/java/src/org/chromium/chrome/browser/foo/FooImpl.java",
Christopher Grantf649d282020-01-09 22:56:08483 ]
484}
485```
486
487With a declaration of the native method on the Java side:
488
489```java
490public class FooImpl implements Foo {
491 ...
492
493 @NativeMethods
494 interface Natives {
495 int execute();
496 }
497}
498```
499
500Finally, augment the module descriptor in
501`//chrome/android/modules/foo/foo_module.gni` with the native dependencies:
502
503```gn
504foo_module_desc = {
505 ...
506 native_deps = [
Christopher Grantf649d282020-01-09 22:56:08507 "//chrome/android/modules/foo/internal:native",
Henrique Nakashimacfdcce32020-04-24 22:19:36508 "//chrome/browser/foo/internal:native",
Christopher Grantf649d282020-01-09 22:56:08509 ]
Samuel Huang3dc9fce82020-02-26 18:09:57510 load_native_on_get_impl = true
Christopher Grantf649d282020-01-09 22:56:08511}
512```
513
Samuel Huang3dc9fce82020-02-26 18:09:57514If `load_native_on_get_impl` is set to `true` then Chrome automatically loads
515Foo DFM's native libraries and PAK file resources when `FooModule.getImpl()` is
516called for the first time. The loading requires Chrome's main native libraries
517to be loaded. If you wish to call `FooModule.getImpl()` earlier than that, then
518you'd need to set `load_native_on_get_impl` to `false`, and manage native
519libraries / resources loading yourself (potentially, on start-up and on install,
520or on use).
521
Christopher Grantf649d282020-01-09 22:56:08522#### Calling feature module native code from base the module
523
524If planning to use direct native-native calls into DFM code, then the module
525should have a purely virtual interface available. The main module can obtain a
526pointer to a DFM-created object or factory (implemented by the feature), and
527call its virtual methods.
528
529Ideally, the interface to the feature will avoid feature-specific types. If a
530feature defines complex data types, and uses them in its own interface, then its
531likely the main library will utilize the code backing these types. That code,
532and anything it references, will in turn be pulled back into the main library,
533negating the intent to house code in the DFM.
534
535Therefore, designing the feature interface to use C types, C++ standard types,
536or classes that aren't expected to move out of Chrome's main library is ideal.
537If feature-specific classes are needed, they simply need to avoid referencing
538feature library internals.
Eric Stevenson8c9ab26b2019-08-30 15:44:40539
Christopher Grant8fea5a12019-07-31 19:12:31540### Adding Android resources
Tibor Goldschwendt19364ba2019-04-10 15:59:55541
542In this section we will add the required build targets to add Android resources
543to the Foo DFM.
544
Tibor Goldschwendt68c5f722019-08-01 15:10:15545First, add a resources target to
Henrique Nakashimacfdcce32020-04-24 22:19:36546`//chrome/browser/foo/internal/BUILD.gn` and add it as a dependency on
Tibor Goldschwendt68c5f722019-08-01 15:10:15547Foo's `java` target in the same file:
Tibor Goldschwendt19364ba2019-04-10 15:59:55548
549```gn
550...
551android_resources("java_resources") {
552 # Define like ordinary Android resources target.
553 ...
Henrique Nakashimacfdcce32020-04-24 22:19:36554 custom_package = "org.chromium.chrome.browser.foo"
Tibor Goldschwendt19364ba2019-04-10 15:59:55555}
556...
557android_library("java") {
558 ...
559 deps = [
560 ":java_resources",
561 ]
562}
563```
564
565To add strings follow steps
566[here](http://dev.chromium.org/developers/design-documents/ui-localization) to
567add new Java GRD file. Then create
Henrique Nakashimacfdcce32020-04-24 22:19:36568`//chrome/browser/foo/internal/android/resources/strings/android_foo_strings.grd` as
Tibor Goldschwendt68c5f722019-08-01 15:10:15569follows:
Tibor Goldschwendt19364ba2019-04-10 15:59:55570
571```xml
572<?xml version="1.0" encoding="UTF-8"?>
573<grit
574 current_release="1"
575 latest_public_release="0"
576 output_all_resource_defines="false">
577 <outputs>
578 <output
579 filename="values-am/android_foo_strings.xml"
580 lang="am"
581 type="android" />
582 <!-- List output file for all other supported languages. See
583 //chrome/android/java/strings/android_chrome_strings.grd for the full
584 list. -->
585 ...
586 </outputs>
587 <translations>
588 <file lang="am" path="vr_translations/android_foo_strings_am.xtb" />
589 <!-- Here, too, list XTB files for all other supported languages. -->
590 ...
591 </translations>
592 <release allow_pseudo="false" seq="1">
593 <messages fallback_to_english="true">
594 <message name="IDS_BAR_IMPL_TEXT" desc="Magical string.">
595 impl
596 </message>
597 </messages>
598 </release>
599</grit>
600```
601
602Then, create a new GRD target and add it as a dependency on `java_resources` in
Henrique Nakashimacfdcce32020-04-24 22:19:36603`//chrome/browser/foo/internal/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55604
605```gn
606...
607java_strings_grd("java_strings_grd") {
608 defines = chrome_grit_defines
Henrique Nakashimacfdcce32020-04-24 22:19:36609 grd_file = "android/resources/strings/android_foo_strings.grd"
Tibor Goldschwendt19364ba2019-04-10 15:59:55610 outputs = [
611 "values-am/android_foo_strings.xml",
612 # Here, too, list output files for other supported languages.
613 ...
614 ]
615}
616...
617android_resources("java_resources") {
618 ...
619 deps = [":java_strings_grd"]
Henrique Nakashimacfdcce32020-04-24 22:19:36620 custom_package = "org.chromium.chrome.browser.foo"
Tibor Goldschwendt19364ba2019-04-10 15:59:55621}
622...
623```
624
625You can then access Foo's resources using the
Henrique Nakashimacfdcce32020-04-24 22:19:36626`org.chromium.chrome.browser.foo.R` class. To do this change
627`//chrome/browser/foo/internal/android/java/src/org/chromium/chrome/browser/foo/FooImpl.java`
Tibor Goldschwendt19364ba2019-04-10 15:59:55628to:
629
630```java
Henrique Nakashimacfdcce32020-04-24 22:19:36631package org.chromium.chrome.browser.foo;
Tibor Goldschwendt19364ba2019-04-10 15:59:55632
633import org.chromium.base.ContextUtils;
634import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30635import org.chromium.base.annotations.UsedByReflection;
Henrique Nakashimacfdcce32020-04-24 22:19:36636import org.chromium.chrome.browser.foo.R;
Tibor Goldschwendt19364ba2019-04-10 15:59:55637
Tibor Goldschwendt573cf3022019-05-10 17:23:30638@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55639public class FooImpl implements Foo {
640 @Override
641 public void bar() {
642 Log.i("FOO", ContextUtils.getApplicationContext().getString(
643 R.string.bar_impl_text));
644 }
645}
646```
647
Samuel Huang3dc9fce82020-02-26 18:09:57648### Adding non-string native resources
649
650This section describes how to add non-string native resources to Foo DFM.
651Key ideas:
652
653* The compiled resource file shipped with the DFM is `foo_resourcess.pak`.
654* At run time, native resources need to be loaded before use. Also, DFM native
655 resources can only be used from the Browser process.
656
657#### Creating PAK file
658
659Two ways to create `foo_resourcess.pak` (using GRIT) are:
660
6611. (Preferred) Use `foo_resourcess.grd` to refer to individual files (e.g.,
662 images, HTML, JS, or CSS) and assigns resource text IDs. `foo_resourcess.pak`
663 must have an entry in `/tools/gritsettings/resource_ids.spec`.
6641. Combine existing .pak files via `repack` rules in GN build files. This is
665 done by the DevUI DFM, which aggregates resources from many DevUI pages.
666
667#### Loading PAK file
668
669At runtime, `foo_resources.pak` needs to be loaded (memory-mapped) before any of
670its resource gets used. Alternatives to do this are:
671
6721. (Simplest) Specify native resources (with native libraries if any exist) to
673 be automatically loaded on first call to `FooModule.getImpl()`. This behavior
674 is specified via `load_native_on_get_impl = true` in `foo_module_desc`.
6751. In Java code, call `FooModule.ensureNativeLoaded()`.
6761. In C++ code, use JNI to call `FooModule.ensureNativeLoaded()`. The code to do
677 this can be placed in a helper class, which can also have JNI calls to
678 `FooModule.isInstalled()` and `FooModule.installModule()`.
679
680#### Cautionary notes
681
682Compiling `foo_resources.pak` auto-generates `foo_resources.h`, which defines
683textual resource IDs, e.g., `IDR_FOO_HTML`. C++ code then uses these IDs to get
684resource bytes. Unfortunately, this behavior is fragile: If `IDR_FOO_HTML` is
685accessed before the Foo DFM is (a) installed, or (b) loaded, then runtime error
686ensues! Some mitigation strategies are as follows:
687
688* (Ideal) Access Foo DFM's native resources only from code in Foo DFM's native
689 libraries. So by the time that `IDR_FOO_HTML` is accessed, everything is
690 already in place! This isn't always possible; henceforth we assume that
691 `IDR_FOO_HTML` is accessed by code in the base DFM.
692* Before accessing IDR_FOO_HTML, ensure Foo DFM is installed and loaded. The
693 latter can use `FooModule.ensureNativeLoaded()` (needs to be called from
694 Browser thread).
695* Use inclusion of `foo_resources.h` to restrict availability of `IDR_FOO_HTML`.
696 Only C++ files dedicated to "DFM-gated code" (code that runs only when its DFM
697 is installed and loaded) should include `foo_resources.h`.
698
699#### Associating native resources with DFM
700
701Here are the main GN changes to specify PAK files and default loading behavior
702for a DFM's native resources:
703
704```gn
705foo_module_desc = {
706 ...
Henrique Nakashimacfdcce32020-04-24 22:19:36707 paks = [ "$root_gen_dir/chrome/browser/foo/internal/foo_resourcess.pak" ]
708 pak_deps = [ "//chrome/browser/foo/internal:foo_paks" ]
Samuel Huang3dc9fce82020-02-26 18:09:57709 load_native_on_get_impl = true
710}
711```
712
713Note that `load_native_on_get_impl` specifies both native libraries and native
714resources.
715
Tibor Goldschwendt19364ba2019-04-10 15:59:55716
717### Module install
718
719So far, we have installed the Foo DFM as a true split (`-m foo` option on the
720install script). In production, however, we have to explicitly install the Foo
Tibor Goldschwendt68c5f722019-08-01 15:10:15721DFM for users to get it. There are three install options: _on-demand_,
722_deferred_ and _conditional_.
Tibor Goldschwendt19364ba2019-04-10 15:59:55723
724
725#### On-demand install
726
727On-demand requesting a module will try to download and install the
728module as soon as possible regardless of whether the user is on a metered
729connection or whether they have turned updates off in the Play Store app.
730
Tibor Goldschwendt573cf3022019-05-10 17:23:30731You can use the autogenerated module class to on-demand install the module like
732so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55733
734```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30735FooModule.install((success) -> {
736 if (success) {
737 FooModule.getImpl().bar();
738 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55739});
740```
741
742**Optionally**, you can show UI telling the user about the install flow. For
Tibor Goldschwendt573cf3022019-05-10 17:23:30743this, add a function like the one below. Note, it is possible
Tibor Goldschwendt19364ba2019-04-10 15:59:55744to only show either one of the install, failure and success UI or any
745combination of the three.
746
747```java
748public static void installModuleWithUi(
749 Tab tab, OnModuleInstallFinishedListener onFinishedListener) {
750 ModuleInstallUi ui =
751 new ModuleInstallUi(
752 tab,
753 R.string.foo_module_title,
754 new ModuleInstallUi.FailureUiListener() {
755 @Override
Samuel Huangfebcccd2019-08-21 20:48:47756 public void onFailureUiResponse(retry) {
757 if (retry) {
758 installModuleWithUi(tab, onFinishedListener);
759 } else {
760 onFinishedListener.onFinished(false);
761 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55762 }
763 });
764 // At the time of writing, shows toast informing user about install start.
765 ui.showInstallStartUi();
Tibor Goldschwendt573cf3022019-05-10 17:23:30766 FooModule.install(
Tibor Goldschwendt19364ba2019-04-10 15:59:55767 (success) -> {
768 if (!success) {
769 // At the time of writing, shows infobar allowing user
770 // to retry install.
771 ui.showInstallFailureUi();
772 return;
773 }
774 // At the time of writing, shows toast informing user about
775 // install success.
776 ui.showInstallSuccessUi();
777 onFinishedListener.onFinished(true);
778 });
779}
780```
781
782To test on-demand install, "fake-install" the DFM. It's fake because
783the DFM is not installed as a true split. Instead it will be emulated by Chrome.
784Fake-install and launch Chrome with the following command:
785
786```shell
787$ $OUTDIR/bin/monochrome_public_bundle install -m base -f foo
Samuel Huang39c7db632019-05-15 14:57:18788$ $OUTDIR/bin/monochrome_public_bundle launch --args="--fake-feature-module-install"
Tibor Goldschwendt19364ba2019-04-10 15:59:55789```
790
791When running the install code, the Foo DFM module will be emulated.
792This will be the case in production right after installing the module. Emulation
793will last until Play Store has a chance to install your module as a true split.
Peter Wen577a6fe52019-12-11 22:02:05794This usually takes about a day. After it has been installed, it will be updated
795atomically alongside Chrome. Always check that it is installed and available
796before invoking code within the DFM.
Tibor Goldschwendt19364ba2019-04-10 15:59:55797
798*** note
799**Warning:** There are subtle differences between emulating a module and
800installing it as a true split. We therefore recommend that you always test both
801install methods.
802***
803
Samuel Huang6f5c7ddb82020-05-14 17:10:52804*** note
805To simplify development, the DevUI DFM (dev_ui) is installed by default, i.e.,
806`-m dev_ui` is implied by default. This is overridden by:
807* `--no-module dev_ui`, to test error from missing DevUI,
808* `-f dev_ui`, for fake module install.
809***
Tibor Goldschwendt19364ba2019-04-10 15:59:55810
811#### Deferred install
812
813Deferred install means that the DFM is installed in the background when the
814device is on an unmetered connection and charging. The DFM will only be
815available after Chrome restarts. When deferred installing a module it will
816not be faked installed.
817
818To defer install Foo do the following:
819
820```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30821FooModule.installDeferred();
Tibor Goldschwendt19364ba2019-04-10 15:59:55822```
823
Tibor Goldschwendt68c5f722019-08-01 15:10:15824#### Conditional install
825
826Conditional install means the DFM will be installed automatically upon first
827installing or updating Chrome if the device supports a particular feature.
828Conditional install is configured in the module's manifest. To install your
829module on all Daydream-ready devices for instance, your
Henrique Nakashimacfdcce32020-04-24 22:19:36830`//chrome/android/modules/foo/internal/java/AndroidManifest.xml` should look
Tibor Goldschwendt68c5f722019-08-01 15:10:15831like this:
832
833```xml
834<?xml version="1.0" encoding="utf-8"?>
835<manifest xmlns:android="http://schemas.android.com/apk/res/android"
836 xmlns:dist="http://schemas.android.com/apk/distribution"
837 featureSplit="foo">
838
839 <dist:module
840 dist:instant="false"
841 dist:title="@string/foo_module_title">
Ben Masone571ea5a2019-09-06 18:29:37842 <dist:fusing dist:include="true" />
Tibor Goldschwendt68c5f722019-08-01 15:10:15843 <dist:delivery>
844 <dist:install-time>
845 <dist:conditions>
846 <dist:device-feature
847 dist:name="android.hardware.vr.high_performance" />
848 </dist:conditions>
849 </dist:install-time>
850 <!-- Allows on-demand or deferred install on non-Daydream-ready
851 devices. -->
852 <dist:on-demand />
853 </dist:delivery>
854 </dist:module>
855
856 <application />
857</manifest>
858```
859
Tibor Goldschwendtf430b272019-11-25 19:19:41860### Metrics
861
862After adding your module to `AndroidFeatureModuleName` (see
863[above](#create-dfm-target)) we will collect, among others, the following
864metrics:
865
866* `Android.FeatureModules.AvailabilityStatus.Foo`: Measures your module's
867 install penetration. That is, the share of users who eventually installed
868 the module after requesting it (once or multiple times).
869
870* `Android.FeatureModules.InstallStatus.Foo`: The result of an on-demand
871 install request. Can be success or one of several error conditions.
872
873* `Android.FeatureModules.UncachedAwakeInstallDuration.Foo`: The duration to
874 install your module successfully after on-demand requesting it.
875
Tibor Goldschwendt19364ba2019-04-10 15:59:55876
877### Integration test APK and Android K support
878
879On Android K we still ship an APK. To make the Foo feature available on Android
880K add its code to the APK build. For this, add the `java` target to
881the `chrome_public_common_apk_or_module_tmpl` in
882`//chrome/android/chrome_public_apk_tmpl.gni` like so:
883
884```gn
885template("chrome_public_common_apk_or_module_tmpl") {
886 ...
887 target(_target_type, target_name) {
888 ...
889 if (_target_type != "android_app_bundle_module") {
890 deps += [
Henrique Nakashimacfdcce32020-04-24 22:19:36891 "//chrome/browser/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55892 ]
893 }
894 }
895}
896```
897
898This will also add Foo's Java to the integration test APK. You may also have to
899add `java` as a dependency of `chrome_test_java` if you want to call into Foo
900from test code.