blob: bf8eb05977ad58c7280f30d796f358286da82fd3 [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
47`//chrome/android/features/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
72`//chrome/android/features/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 =
78 "//chrome/android/features/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
Tibor Goldschwendtaef8e392019-07-19 16:39:1087import("//chrome/android/features/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
Tibor Goldschwendt19364ba2019-04-10 15:59:55183`//chrome/android/features/foo/public/java/src/org/chromium/chrome/features/foo/Foo.java`:
184
185```java
186package org.chromium.chrome.features.foo;
187
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. */
Tibor Goldschwendt573cf3022019-05-10 17:23:30191@ModuleInterface(module = "foo", impl = "org.chromium.chrome.features.FooImpl")
Tibor Goldschwendt19364ba2019-04-10 15:59:55192public interface Foo {
193 /** Magical function. */
194 void bar();
195}
196```
197
198*** note
199**Note:** To reflect the separation from "Chrome browser" code, features should
200be defined in their own package name, distinct from the chrome package - i.e.
201`org.chromium.chrome.features.<feature-name>`.
202***
203
204Next, define an implementation that goes into the module in the new file
Tibor Goldschwendt68c5f722019-08-01 15:10:15205`//chrome/android/features/foo/internal/java/src/org/chromium/chrome/features/foo/FooImpl.java`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55206
207```java
208package org.chromium.chrome.features.foo;
209
210import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30211import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55212
Tibor Goldschwendt573cf3022019-05-10 17:23:30213@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55214public class FooImpl implements Foo {
215 @Override
216 public void bar() {
217 Log.i("FOO", "bar in module");
218 }
219}
220```
221
Tibor Goldschwendt19364ba2019-04-10 15:59:55222You can then use this provider to access the module if it is installed. To test
223that, instantiate Foo and call `bar()` somewhere in Chrome:
224
225```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30226if (FooModule.isInstalled()) {
227 FooModule.getImpl().bar();
Tibor Goldschwendt19364ba2019-04-10 15:59:55228} else {
229 Log.i("FOO", "module not installed");
230}
231```
232
Tibor Goldschwendt573cf3022019-05-10 17:23:30233The interface has to be available regardless of whether the Foo DFM is present.
234Therefore, put those classes into the base module. For this create a list of
235those Java files in
Tibor Goldschwendt19364ba2019-04-10 15:59:55236`//chrome/android/features/foo/public/foo_public_java_sources.gni`:
237
238```gn
239foo_public_java_sources = [
240 "//chrome/android/features/foo/public/java/src/org/chromium/chrome/features/foo/Foo.java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55241]
242```
243
244Then add this list to `chrome_java in //chrome/android/BUILD.gn`:
245
246```gn
247...
Tibor Goldschwendt68c5f722019-08-01 15:10:15248import("//chrome/android/features/foo/public/foo_public_java_sources.gni")
Tibor Goldschwendt19364ba2019-04-10 15:59:55249...
250android_library("chrome_java") {
251 ...
Natalie Chouinardcbdc6dc2019-12-24 00:02:35252 sources += foo_public_java_sources
Tibor Goldschwendt19364ba2019-04-10 15:59:55253}
254...
255```
256
257The actual implementation, however, should go into the Foo DFM. For this
Tibor Goldschwendt68c5f722019-08-01 15:10:15258purpose, create a new file `//chrome/android/features/foo/internal/BUILD.gn` and
259make a library with the module Java code in it:
Tibor Goldschwendt19364ba2019-04-10 15:59:55260
261```gn
262import("//build/config/android/rules.gni")
263
264android_library("java") {
265 # Define like ordinary Java Android library.
Natalie Chouinardcbdc6dc2019-12-24 00:02:35266 sources = [
Tibor Goldschwendt19364ba2019-04-10 15:59:55267 "java/src/org/chromium/chrome/features/foo/FooImpl.java",
268 # Add other Java classes that should go into the Foo DFM here.
269 ]
270 # Put other Chrome libs into the classpath so that you can call into the rest
271 # of Chrome from the Foo DFM.
Fred Mellob32b3022019-06-21 18:10:11272 deps = [
Tibor Goldschwendt19364ba2019-04-10 15:59:55273 "//base:base_java",
274 "//chrome/android:chrome_java",
275 # etc.
276 # Also, you'll need to depend on any //third_party or //components code you
277 # are using in the module code.
278 ]
279}
280```
281
Tibor Goldschwendtaef8e392019-07-19 16:39:10282Then, add this new library as a dependency of the Foo module descriptor in
283`//chrome/android/features/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55284
285```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10286foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55287 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10288 java_deps = [
Tibor Goldschwendt68c5f722019-08-01 15:10:15289 "//chrome/android/features/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55290 ]
291}
292```
293
294Finally, tell Android that your module is now containing code. Do that by
Samuel Huang39c7db632019-05-15 14:57:18295removing the `android:hasCode="false"` attribute from the `<application>` tag in
Tibor Goldschwendt68c5f722019-08-01 15:10:15296`//chrome/android/features/foo/internal/java/AndroidManifest.xml`. You should be
297left with an empty tag like so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55298
299```xml
300...
301 <application />
302...
303```
304
305Rebuild and install `monochrome_public_bundle`. Start Chrome and run through a
306flow that tries to executes `bar()`. Depending on whether you installed your
307module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
308logcat. Yay!
309
Christopher Grantf649d282020-01-09 22:56:08310### Adding pre-built native libraries
Tibor Goldschwendt19364ba2019-04-10 15:59:55311
Christopher Grant8fea5a12019-07-31 19:12:31312You can add a third-party native library (or any standalone library that doesn't
313depend on Chrome code) by adding it as a loadable module to the module descriptor in
314`//chrome/android/features/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55315
316```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10317foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55318 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10319 loadable_modules_32_bit = [ "//path/to/32/bit/lib.so" ]
320 loadable_modules_64_bit = [ "//path/to/64/bit/lib.so" ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55321}
322```
323
Christopher Grant8fea5a12019-07-31 19:12:31324### Adding Chrome native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55325
Christopher Grantf649d282020-01-09 22:56:08326Chrome native code may be placed in a DFM. The easiest way to access native
327feature code is by calling it from Java via JNI. When a module is first
328accessed, its native library (or potentially libraries, if using a component
329build), are automatically opened by the DFM framework, and a feature-specific
330JNI method (supplied by the feature's implementation) is invoked. Hence, a
331module's Java code may freely use JNI to call module native code.
Christopher Grant8fea5a12019-07-31 19:12:31332
Christopher Grantf649d282020-01-09 22:56:08333Using the module framework and JNI to access the native code eliminates concerns
334with DFM library file names (which vary across build variants),
335`android_dlopen_ext()` (needed to open feature libraries), and use of dlsym().
Christopher Grant8fea5a12019-07-31 19:12:31336
Christopher Grantf649d282020-01-09 22:56:08337This mechanism can be extended if necessary by DFM implementers to facilitate
338subsequent native-native calls, by having a JNI-called initialization method
339create instance of a object or factory, and register it through a call to the
340base module's native code (DFM native code can call base module code directly).
Christopher Grant8fea5a12019-07-31 19:12:31341
Eric Stevenson8c9ab26b2019-08-30 15:44:40342#### JNI
343
344Read the `jni_generator` [docs](../base/android/jni_generator/README.md) before
345reading this section.
346
347There are some subtleties to how JNI registration works with DFMs:
348
349* Generated wrapper `ClassNameJni` classes are packaged into the DFM's dex file
350* The class containing the actual native definitions, `GEN_JNI.java`, is always
351 stored in the base module
Christopher Grantf649d282020-01-09 22:56:08352* If the DFM is only included in bundles that use [implicit JNI
353 registration](android_native_libraries.md#JNI-Native-Methods-Resolution) (i.e.
354 Monochrome and newer), then no extra consideration is necessary
Eric Stevenson8c9ab26b2019-08-30 15:44:40355* Otherwise, the DFM will need to provide a `generate_jni_registration` target
356 that will generate all of the native registration functions
357
Christopher Grantf649d282020-01-09 22:56:08358#### Calling DFM native code via JNI
359
360A linker-assisted partitioning system automates the placement of code into
361either the main Chrome library or feature-specific .so libraries. Feature code
362may continue to make use of core Chrome code (eg. base::) without modification,
363but Chrome must call feature code through a virtual interface (any "direct"
364calls to the feature code from the main library will cause the feature code to
365be pulled back into the main library).
366
367Partitioning is explained in [Android Native
368Libraries](android_native_libraries.md#partitioned-libraries).
369
370First, build a module native interface. Supply a JNI method named
371`JNI_OnLoad_foo` for the module framework to call, in
372`//chrome/android/modules/foo/internal/entrypoints.cc`. This method is invoked
373on all Chrome build variants, including Monochrome (unlike base module JNI).
374
375```c++
376#include "base/android/jni_generator/jni_generator_helper.h"
377#include "base/android/jni_utils.h"
378#include "chrome/android/modules/foo/internal/jni_registration.h"
379
380extern "C" {
381// This JNI registration method is found and called by module framework code.
382JNI_GENERATOR_EXPORT bool JNI_OnLoad_foo(JNIEnv* env) {
383 if (!base::android::IsSelectiveJniRegistrationEnabled(env) &&
384 !foo::RegisterNonMainDexNatives(env)) {
385 return false;
386 }
387 if (!foo::RegisterMainDexNatives(env)) {
388 return false;
389 }
390 return true;
391}
392} // extern "C"
393```
394
395Next, include the module entrypoint and related pieces in the build config at
396`//chrome/android/modules/foo/internal/BUILD.gn`:
397
398```gn
399import("//build/config/android/rules.gni")
400import("//chrome/android/modules/buildflags.gni")
401...
402
403# Put the JNI entrypoint in a component, so that the component build has a
404# library to include in the foo module. This makes things feel consistent with
405# a release build.
406component("foo") {
407 sources = [
408 "entrypoints.cc",
409 ]
410 deps = [
411 ":jni_registration",
412 "//chrome/android/features/foo/internal:native",
413 "//base",
414 ]
415
416 # Instruct the compiler to flag exported entrypoint function as belonging in
417 # foo's library. The linker will use this information when creating the
418 # native libraries. The partition name must be <feature>_partition.
419 if (use_native_partitions) {
420 cflags = [ "-fsymbol-partition=foo_partition" ]
421 }
422}
423
424# Generate JNI registration for the methods called by the Java side. Note the
425# no_transitive_deps argument, which ensures that JNI is generated for only the
426# specified Java target, and not all its transitive deps (which could include
427# the base module).
428generate_jni_registration("jni_registration") {
429 targets = [ "//chrome/android/features/foo/internal:java" ]
430 header_output = "$target_gen_dir/jni_registration.h"
431 namespace = "foo"
432 no_transitive_deps = true
433}
434
435# This group is a convenience alias representing the module's native code,
436# allowing it to be named "native" for clarity in module descriptors.
437group("native") {
438 deps = [
439 ":foo",
440 ]
441}
442```
443
444Now, over to the implementation of the module. These are the parts that
445shouldn't know or care whether they're living in a module or not.
446
447Add a stub implementation in
448`//chrome/android/features/foo/internal/foo_impl.cc`:
449
450```c++
451#include "base/logging.h"
452#include "chrome/android/features/foo/internal/jni_headers/FooImpl_jni.h"
453
454static int JNI_FooImpl_Execute(JNIEnv* env) {
455 LOG(INFO) << "Running foo feature code!";
456 return 123;
457}
458```
459
460And, the associated build config in
461`//chrome/android/features/foo/internal/BUILD.gn`:
462
463```gn
464import("//build/config/android/rules.gni")
465
466...
467
468source_set("native") {
469 sources = [
470 "foo_impl.cc",
471 ]
472
473 deps = [
474 ":jni_headers",
475 "//base",
476 ]
477}
478
479generate_jni("jni_headers") {
480 sources = [
481 "java/src/org/chromium/chrome/features/foo/FooImpl.java",
482 ]
483}
484```
485
486With a declaration of the native method on the Java side:
487
488```java
489public class FooImpl implements Foo {
490 ...
491
492 @NativeMethods
493 interface Natives {
494 int execute();
495 }
496}
497```
498
499Finally, augment the module descriptor in
500`//chrome/android/modules/foo/foo_module.gni` with the native dependencies:
501
502```gn
503foo_module_desc = {
504 ...
505 native_deps = [
506 "//chrome/android/features/foo/internal:native",
507 "//chrome/android/modules/foo/internal:native",
508 ]
Samuel Huang3dc9fce82020-02-26 18:09:57509 load_native_on_get_impl = true
Christopher Grantf649d282020-01-09 22:56:08510}
511```
512
Samuel Huang3dc9fce82020-02-26 18:09:57513If `load_native_on_get_impl` is set to `true` then Chrome automatically loads
514Foo DFM's native libraries and PAK file resources when `FooModule.getImpl()` is
515called for the first time. The loading requires Chrome's main native libraries
516to be loaded. If you wish to call `FooModule.getImpl()` earlier than that, then
517you'd need to set `load_native_on_get_impl` to `false`, and manage native
518libraries / resources loading yourself (potentially, on start-up and on install,
519or on use).
520
Christopher Grantf649d282020-01-09 22:56:08521#### Calling feature module native code from base the module
522
523If planning to use direct native-native calls into DFM code, then the module
524should have a purely virtual interface available. The main module can obtain a
525pointer to a DFM-created object or factory (implemented by the feature), and
526call its virtual methods.
527
528Ideally, the interface to the feature will avoid feature-specific types. If a
529feature defines complex data types, and uses them in its own interface, then its
530likely the main library will utilize the code backing these types. That code,
531and anything it references, will in turn be pulled back into the main library,
532negating the intent to house code in the DFM.
533
534Therefore, designing the feature interface to use C types, C++ standard types,
535or classes that aren't expected to move out of Chrome's main library is ideal.
536If feature-specific classes are needed, they simply need to avoid referencing
537feature library internals.
Eric Stevenson8c9ab26b2019-08-30 15:44:40538
Christopher Grant8fea5a12019-07-31 19:12:31539### Adding Android resources
Tibor Goldschwendt19364ba2019-04-10 15:59:55540
541In this section we will add the required build targets to add Android resources
542to the Foo DFM.
543
Tibor Goldschwendt68c5f722019-08-01 15:10:15544First, add a resources target to
545`//chrome/android/features/foo/internal/BUILD.gn` and add it as a dependency on
546Foo's `java` target in the same file:
Tibor Goldschwendt19364ba2019-04-10 15:59:55547
548```gn
549...
550android_resources("java_resources") {
551 # Define like ordinary Android resources target.
552 ...
553 custom_package = "org.chromium.chrome.features.foo"
554}
555...
556android_library("java") {
557 ...
558 deps = [
559 ":java_resources",
560 ]
561}
562```
563
564To add strings follow steps
565[here](http://dev.chromium.org/developers/design-documents/ui-localization) to
566add new Java GRD file. Then create
Tibor Goldschwendt68c5f722019-08-01 15:10:15567`//chrome/android/features/foo/internal/java/strings/android_foo_strings.grd` as
568follows:
Tibor Goldschwendt19364ba2019-04-10 15:59:55569
570```xml
571<?xml version="1.0" encoding="UTF-8"?>
572<grit
573 current_release="1"
574 latest_public_release="0"
575 output_all_resource_defines="false">
576 <outputs>
577 <output
578 filename="values-am/android_foo_strings.xml"
579 lang="am"
580 type="android" />
581 <!-- List output file for all other supported languages. See
582 //chrome/android/java/strings/android_chrome_strings.grd for the full
583 list. -->
584 ...
585 </outputs>
586 <translations>
587 <file lang="am" path="vr_translations/android_foo_strings_am.xtb" />
588 <!-- Here, too, list XTB files for all other supported languages. -->
589 ...
590 </translations>
591 <release allow_pseudo="false" seq="1">
592 <messages fallback_to_english="true">
593 <message name="IDS_BAR_IMPL_TEXT" desc="Magical string.">
594 impl
595 </message>
596 </messages>
597 </release>
598</grit>
599```
600
601Then, create a new GRD target and add it as a dependency on `java_resources` in
Tibor Goldschwendt68c5f722019-08-01 15:10:15602`//chrome/android/features/foo/internal/BUILD.gn`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55603
604```gn
605...
606java_strings_grd("java_strings_grd") {
607 defines = chrome_grit_defines
608 grd_file = "java/strings/android_foo_strings.grd"
609 outputs = [
610 "values-am/android_foo_strings.xml",
611 # Here, too, list output files for other supported languages.
612 ...
613 ]
614}
615...
616android_resources("java_resources") {
617 ...
618 deps = [":java_strings_grd"]
619 custom_package = "org.chromium.chrome.features.foo"
620}
621...
622```
623
624You can then access Foo's resources using the
625`org.chromium.chrome.features.foo.R` class. To do this change
Tibor Goldschwendt68c5f722019-08-01 15:10:15626`//chrome/android/features/foo/internal/java/src/org/chromium/chrome/features/foo/FooImpl.java`
Tibor Goldschwendt19364ba2019-04-10 15:59:55627to:
628
629```java
630package org.chromium.chrome.features.foo;
631
632import org.chromium.base.ContextUtils;
633import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30634import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55635import org.chromium.chrome.features.foo.R;
636
Tibor Goldschwendt573cf3022019-05-10 17:23:30637@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55638public class FooImpl implements Foo {
639 @Override
640 public void bar() {
641 Log.i("FOO", ContextUtils.getApplicationContext().getString(
642 R.string.bar_impl_text));
643 }
644}
645```
646
Samuel Huang3dc9fce82020-02-26 18:09:57647### Adding non-string native resources
648
649This section describes how to add non-string native resources to Foo DFM.
650Key ideas:
651
652* The compiled resource file shipped with the DFM is `foo_resourcess.pak`.
653* At run time, native resources need to be loaded before use. Also, DFM native
654 resources can only be used from the Browser process.
655
656#### Creating PAK file
657
658Two ways to create `foo_resourcess.pak` (using GRIT) are:
659
6601. (Preferred) Use `foo_resourcess.grd` to refer to individual files (e.g.,
661 images, HTML, JS, or CSS) and assigns resource text IDs. `foo_resourcess.pak`
662 must have an entry in `/tools/gritsettings/resource_ids.spec`.
6631. Combine existing .pak files via `repack` rules in GN build files. This is
664 done by the DevUI DFM, which aggregates resources from many DevUI pages.
665
666#### Loading PAK file
667
668At runtime, `foo_resources.pak` needs to be loaded (memory-mapped) before any of
669its resource gets used. Alternatives to do this are:
670
6711. (Simplest) Specify native resources (with native libraries if any exist) to
672 be automatically loaded on first call to `FooModule.getImpl()`. This behavior
673 is specified via `load_native_on_get_impl = true` in `foo_module_desc`.
6741. In Java code, call `FooModule.ensureNativeLoaded()`.
6751. In C++ code, use JNI to call `FooModule.ensureNativeLoaded()`. The code to do
676 this can be placed in a helper class, which can also have JNI calls to
677 `FooModule.isInstalled()` and `FooModule.installModule()`.
678
679#### Cautionary notes
680
681Compiling `foo_resources.pak` auto-generates `foo_resources.h`, which defines
682textual resource IDs, e.g., `IDR_FOO_HTML`. C++ code then uses these IDs to get
683resource bytes. Unfortunately, this behavior is fragile: If `IDR_FOO_HTML` is
684accessed before the Foo DFM is (a) installed, or (b) loaded, then runtime error
685ensues! Some mitigation strategies are as follows:
686
687* (Ideal) Access Foo DFM's native resources only from code in Foo DFM's native
688 libraries. So by the time that `IDR_FOO_HTML` is accessed, everything is
689 already in place! This isn't always possible; henceforth we assume that
690 `IDR_FOO_HTML` is accessed by code in the base DFM.
691* Before accessing IDR_FOO_HTML, ensure Foo DFM is installed and loaded. The
692 latter can use `FooModule.ensureNativeLoaded()` (needs to be called from
693 Browser thread).
694* Use inclusion of `foo_resources.h` to restrict availability of `IDR_FOO_HTML`.
695 Only C++ files dedicated to "DFM-gated code" (code that runs only when its DFM
696 is installed and loaded) should include `foo_resources.h`.
697
698#### Associating native resources with DFM
699
700Here are the main GN changes to specify PAK files and default loading behavior
701for a DFM's native resources:
702
703```gn
704foo_module_desc = {
705 ...
706 paks = [ "$root_gen_dir/chrome/android/features/foo/internal/foo_resourcess.pak" ]
707 pak_deps = [ "//chrome/android/features/foo/internal:foo_paks" ]
708 load_native_on_get_impl = true
709}
710```
711
712Note that `load_native_on_get_impl` specifies both native libraries and native
713resources.
714
Tibor Goldschwendt19364ba2019-04-10 15:59:55715
716### Module install
717
718So far, we have installed the Foo DFM as a true split (`-m foo` option on the
719install script). In production, however, we have to explicitly install the Foo
Tibor Goldschwendt68c5f722019-08-01 15:10:15720DFM for users to get it. There are three install options: _on-demand_,
721_deferred_ and _conditional_.
Tibor Goldschwendt19364ba2019-04-10 15:59:55722
723
724#### On-demand install
725
726On-demand requesting a module will try to download and install the
727module as soon as possible regardless of whether the user is on a metered
728connection or whether they have turned updates off in the Play Store app.
729
Tibor Goldschwendt573cf3022019-05-10 17:23:30730You can use the autogenerated module class to on-demand install the module like
731so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55732
733```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30734FooModule.install((success) -> {
735 if (success) {
736 FooModule.getImpl().bar();
737 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55738});
739```
740
741**Optionally**, you can show UI telling the user about the install flow. For
Tibor Goldschwendt573cf3022019-05-10 17:23:30742this, add a function like the one below. Note, it is possible
Tibor Goldschwendt19364ba2019-04-10 15:59:55743to only show either one of the install, failure and success UI or any
744combination of the three.
745
746```java
747public static void installModuleWithUi(
748 Tab tab, OnModuleInstallFinishedListener onFinishedListener) {
749 ModuleInstallUi ui =
750 new ModuleInstallUi(
751 tab,
752 R.string.foo_module_title,
753 new ModuleInstallUi.FailureUiListener() {
754 @Override
Samuel Huangfebcccd2019-08-21 20:48:47755 public void onFailureUiResponse(retry) {
756 if (retry) {
757 installModuleWithUi(tab, onFinishedListener);
758 } else {
759 onFinishedListener.onFinished(false);
760 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55761 }
762 });
763 // At the time of writing, shows toast informing user about install start.
764 ui.showInstallStartUi();
Tibor Goldschwendt573cf3022019-05-10 17:23:30765 FooModule.install(
Tibor Goldschwendt19364ba2019-04-10 15:59:55766 (success) -> {
767 if (!success) {
768 // At the time of writing, shows infobar allowing user
769 // to retry install.
770 ui.showInstallFailureUi();
771 return;
772 }
773 // At the time of writing, shows toast informing user about
774 // install success.
775 ui.showInstallSuccessUi();
776 onFinishedListener.onFinished(true);
777 });
778}
779```
780
781To test on-demand install, "fake-install" the DFM. It's fake because
782the DFM is not installed as a true split. Instead it will be emulated by Chrome.
783Fake-install and launch Chrome with the following command:
784
785```shell
786$ $OUTDIR/bin/monochrome_public_bundle install -m base -f foo
Samuel Huang39c7db632019-05-15 14:57:18787$ $OUTDIR/bin/monochrome_public_bundle launch --args="--fake-feature-module-install"
Tibor Goldschwendt19364ba2019-04-10 15:59:55788```
789
790When running the install code, the Foo DFM module will be emulated.
791This will be the case in production right after installing the module. Emulation
792will last until Play Store has a chance to install your module as a true split.
Peter Wen577a6fe52019-12-11 22:02:05793This usually takes about a day. After it has been installed, it will be updated
794atomically alongside Chrome. Always check that it is installed and available
795before invoking code within the DFM.
Tibor Goldschwendt19364ba2019-04-10 15:59:55796
797*** note
798**Warning:** There are subtle differences between emulating a module and
799installing it as a true split. We therefore recommend that you always test both
800install methods.
801***
802
803
804#### Deferred install
805
806Deferred install means that the DFM is installed in the background when the
807device is on an unmetered connection and charging. The DFM will only be
808available after Chrome restarts. When deferred installing a module it will
809not be faked installed.
810
811To defer install Foo do the following:
812
813```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30814FooModule.installDeferred();
Tibor Goldschwendt19364ba2019-04-10 15:59:55815```
816
Tibor Goldschwendt68c5f722019-08-01 15:10:15817#### Conditional install
818
819Conditional install means the DFM will be installed automatically upon first
820installing or updating Chrome if the device supports a particular feature.
821Conditional install is configured in the module's manifest. To install your
822module on all Daydream-ready devices for instance, your
823`//chrome/android/features/foo/internal/java/AndroidManifest.xml` should look
824like this:
825
826```xml
827<?xml version="1.0" encoding="utf-8"?>
828<manifest xmlns:android="http://schemas.android.com/apk/res/android"
829 xmlns:dist="http://schemas.android.com/apk/distribution"
830 featureSplit="foo">
831
832 <dist:module
833 dist:instant="false"
834 dist:title="@string/foo_module_title">
Ben Masone571ea5a2019-09-06 18:29:37835 <dist:fusing dist:include="true" />
Tibor Goldschwendt68c5f722019-08-01 15:10:15836 <dist:delivery>
837 <dist:install-time>
838 <dist:conditions>
839 <dist:device-feature
840 dist:name="android.hardware.vr.high_performance" />
841 </dist:conditions>
842 </dist:install-time>
843 <!-- Allows on-demand or deferred install on non-Daydream-ready
844 devices. -->
845 <dist:on-demand />
846 </dist:delivery>
847 </dist:module>
848
849 <application />
850</manifest>
851```
852
Tibor Goldschwendtf430b272019-11-25 19:19:41853### Metrics
854
855After adding your module to `AndroidFeatureModuleName` (see
856[above](#create-dfm-target)) we will collect, among others, the following
857metrics:
858
859* `Android.FeatureModules.AvailabilityStatus.Foo`: Measures your module's
860 install penetration. That is, the share of users who eventually installed
861 the module after requesting it (once or multiple times).
862
863* `Android.FeatureModules.InstallStatus.Foo`: The result of an on-demand
864 install request. Can be success or one of several error conditions.
865
866* `Android.FeatureModules.UncachedAwakeInstallDuration.Foo`: The duration to
867 install your module successfully after on-demand requesting it.
868
Tibor Goldschwendt19364ba2019-04-10 15:59:55869
870### Integration test APK and Android K support
871
872On Android K we still ship an APK. To make the Foo feature available on Android
873K add its code to the APK build. For this, add the `java` target to
874the `chrome_public_common_apk_or_module_tmpl` in
875`//chrome/android/chrome_public_apk_tmpl.gni` like so:
876
877```gn
878template("chrome_public_common_apk_or_module_tmpl") {
879 ...
880 target(_target_type, target_name) {
881 ...
882 if (_target_type != "android_app_bundle_module") {
883 deps += [
Tibor Goldschwendt68c5f722019-08-01 15:10:15884 "//chrome/android/features/foo/internal:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55885 ]
886 }
887 }
888}
889```
890
891This will also add Foo's Java to the integration test APK. You may also have to
892add `java` as a dependency of `chrome_test_java` if you want to call into Foo
893from test code.