blob: 494a2b05fc3e68c4ecbe7ebf31255886f32b46be [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
13Currently (March 2019), DFMs have the following limitations:
14
15* **WebView:** We don't support DFMs for WebView. If your feature is used by
16 WebView you cannot put it into a DFM. See
17 [crbug/949717](https://bugs.chromium.org/p/chromium/issues/detail?id=949717)
18 for progress.
19* **Android K:** DFMs are based on split APKs, a feature introduced in Android
20 L. Therefore, we don't support DFMs on Android K. As a workaround
21 you can add your feature to the Android K APK build. See
22 [crbug/881354](https://bugs.chromium.org/p/chromium/issues/detail?id=881354)
23 for progress.
Tibor Goldschwendt19364ba2019-04-10 15:59:5524
25## Getting started
26
27This guide walks you through the steps to create a DFM called _Foo_ and add it
Tibor Goldschwendtaef8e392019-07-19 16:39:1028to the Chrome bundles.
Tibor Goldschwendt19364ba2019-04-10 15:59:5529
30*** note
31**Note:** To make your own module you'll essentially have to replace every
32instance of `foo`/`Foo`/`FOO` with `your_feature_name`/`YourFeatureName`/
33`YOUR_FEATURE_NAME`.
34***
35
36
37### Create DFM target
38
39DFMs are APKs. They have a manifest and can contain Java and native code as well
40as resources. This section walks you through creating the module target in our
41build system.
42
43First, create the file `//chrome/android/features/foo/java/AndroidManifest.xml`
44and add:
45
46```xml
47<manifest xmlns:android="http://schemas.android.com/apk/res/android"
48 xmlns:dist="http://schemas.android.com/apk/distribution"
Tibor Goldschwendt5172118f2019-06-24 21:57:4749 featureSplit="foo">
Tibor Goldschwendt19364ba2019-04-10 15:59:5550
Tibor Goldschwendt19364ba2019-04-10 15:59:5551 <!-- dist:onDemand="true" makes this a separately installed module.
52 dist:onDemand="false" would always install the module alongside the
53 rest of Chrome. -->
54 <dist:module
55 dist:onDemand="true"
56 dist:title="@string/foo_module_title">
57 <!-- This will prevent the module to become part of the Android K
58 build in case we ever want to use bundles on Android K. -->
59 <dist:fusing dist:include="false" />
60 </dist:module>
61
Samuel Huang39c7db632019-05-15 14:57:1862 <!-- Remove android:hasCode="false" when adding Java code. -->
63 <application android:hasCode="false" />
Tibor Goldschwendt19364ba2019-04-10 15:59:5564</manifest>
65```
66
67Then, add a package ID for Foo so that Foo's resources have unique identifiers.
68For this, add a new ID to
Tibor Goldschwendtaef8e392019-07-19 16:39:1069`//chrome/android/modules/chrome_feature_modules.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:5570
71```gn
72resource_packages_id_mapping = [
73 ...,
74 "foo=0x{XX}", # Set {XX} to next lower hex number.
75]
76```
77
Tibor Goldschwendtaef8e392019-07-19 16:39:1078Next, create a descriptor configuring the Foo module. To do this, create
79`//chrome/android/features/foo/foo_module.gni` and add the following:
Tibor Goldschwendt19364ba2019-04-10 15:59:5580
81```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:1082foo_module_desc = {
83 name = "foo"
84 manifest = "//chrome/android/features/foo/java/AndroidManifest.xml"
Tibor Goldschwendt19364ba2019-04-10 15:59:5585}
86```
87
Tibor Goldschwendtaef8e392019-07-19 16:39:1088Then, add the module descriptor to the appropriate descriptor list in
89//chrome/android/modules/chrome_feature_modules.gni, e.g. the Chrome Modern
90list:
Tibor Goldschwendt19364ba2019-04-10 15:59:5591
92```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:1093import("//chrome/android/features/foo/foo_module.gni")
Tibor Goldschwendt19364ba2019-04-10 15:59:5594...
Tibor Goldschwendtaef8e392019-07-19 16:39:1095chrome_modern_module_descs += [ foo_module_desc ]
Tibor Goldschwendt19364ba2019-04-10 15:59:5596```
97
98The next step is to add Foo to the list of feature modules for UMA recording.
99For this, add `foo` to the `AndroidFeatureModuleName` in
100`//tools/metrics/histograms/histograms.xml`:
101
102```xml
103<histogram_suffixes name="AndroidFeatureModuleName" ...>
104 ...
105 <suffix name="foo" label="Super Duper Foo Module" />
106 ...
107</histogram_suffixes>
108```
109
110<!--- TODO(tiborg): Add info about install UI. -->
111Lastly, give your module a title that Chrome and Play can use for the install
112UI. To do this, add a string to
113`//chrome/android/java/strings/android_chrome_strings.grd`:
114
115```xml
116...
117<message name="IDS_FOO_MODULE_TITLE"
118 desc="Text shown when the Foo module is referenced in install start, success,
119 failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to
120 'Installing Foo for Chrome').">
121 Foo
122</message>
123...
124```
125
Samuel Huang7f2b53752019-05-23 15:10:05126*** note
127**Note:** This is for module title only. Other strings specific to the module
128should go in the module, not here (in the base module).
129***
130
Tibor Goldschwendt19364ba2019-04-10 15:59:55131Congrats! You added the DFM Foo to Monochrome. That is a big step but not very
132useful so far. In the next sections you'll learn how to add code and resources
133to it.
134
135
136### Building and installing modules
137
138Before we are going to jump into adding content to Foo, let's take a look on how
139to build and deploy the Monochrome bundle with the Foo DFM. The remainder of
140this guide assumes the environment variable `OUTDIR` is set to a properly
141configured GN build directory (e.g. `out/Debug`).
142
143To build and install the Monochrome bundle to your connected device, run:
144
145```shell
146$ autoninja -C $OUTDIR monochrome_public_bundle
147$ $OUTDIR/bin/monochrome_public_bundle install -m base -m foo
148```
149
150This will install Foo alongside the rest of Chrome. The rest of Chrome is called
151_base_ module in the bundle world. The Base module will always be put on the
152device when initially installing Chrome.
153
154*** note
155**Note:** You have to specify `-m base` here to make it explicit which modules
156will be installed. If you only specify `-m foo` the command will fail. It is
157also possible to specify no modules. In that case, the script will install the
158set of modules that the Play Store would install when first installing Chrome.
159That may be different than just specifying `-m base` if we have non-on-demand
160modules.
161***
162
163You can then check that the install worked with:
164
165```shell
166$ adb shell dumpsys package org.chromium.chrome | grep splits
167> splits=[base, config.en, foo]
168```
169
170Then try installing the Monochrome bundle without your module and print the
171installed modules:
172
173```shell
174$ $OUTDIR/bin/monochrome_public_bundle install -m base
175$ adb shell dumpsys package org.chromium.chrome | grep splits
176> splits=[base, config.en]
177```
178
179
180### Adding java code
181
182To make Foo useful, let's add some Java code to it. This section will walk you
183through the required steps.
184
Tibor Goldschwendt573cf3022019-05-10 17:23:30185First, define a module interface for Foo. This is accomplished by adding the
186`@ModuleInterface` annotation to the Foo interface. This annotation
187automatically creates a `FooModule` class that can be used later to install and
188access the module. To do this, add the following in the new file
Tibor Goldschwendt19364ba2019-04-10 15:59:55189`//chrome/android/features/foo/public/java/src/org/chromium/chrome/features/foo/Foo.java`:
190
191```java
192package org.chromium.chrome.features.foo;
193
Tibor Goldschwendt573cf3022019-05-10 17:23:30194import org.chromium.components.module_installer.ModuleInterface;
195
Tibor Goldschwendt19364ba2019-04-10 15:59:55196/** Interface to call into Foo feature. */
Tibor Goldschwendt573cf3022019-05-10 17:23:30197@ModuleInterface(module = "foo", impl = "org.chromium.chrome.features.FooImpl")
Tibor Goldschwendt19364ba2019-04-10 15:59:55198public interface Foo {
199 /** Magical function. */
200 void bar();
201}
202```
203
204*** note
205**Note:** To reflect the separation from "Chrome browser" code, features should
206be defined in their own package name, distinct from the chrome package - i.e.
207`org.chromium.chrome.features.<feature-name>`.
208***
209
210Next, define an implementation that goes into the module in the new file
211`//chrome/android/features/foo/java/src/org/chromium/chrome/features/foo/FooImpl.java`:
212
213```java
214package org.chromium.chrome.features.foo;
215
216import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30217import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55218
Tibor Goldschwendt573cf3022019-05-10 17:23:30219@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55220public class FooImpl implements Foo {
221 @Override
222 public void bar() {
223 Log.i("FOO", "bar in module");
224 }
225}
226```
227
Tibor Goldschwendt19364ba2019-04-10 15:59:55228You can then use this provider to access the module if it is installed. To test
229that, instantiate Foo and call `bar()` somewhere in Chrome:
230
231```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30232if (FooModule.isInstalled()) {
233 FooModule.getImpl().bar();
Tibor Goldschwendt19364ba2019-04-10 15:59:55234} else {
235 Log.i("FOO", "module not installed");
236}
237```
238
Tibor Goldschwendt573cf3022019-05-10 17:23:30239The interface has to be available regardless of whether the Foo DFM is present.
240Therefore, put those classes into the base module. For this create a list of
241those Java files in
Tibor Goldschwendt19364ba2019-04-10 15:59:55242`//chrome/android/features/foo/public/foo_public_java_sources.gni`:
243
244```gn
245foo_public_java_sources = [
246 "//chrome/android/features/foo/public/java/src/org/chromium/chrome/features/foo/Foo.java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55247]
248```
249
250Then add this list to `chrome_java in //chrome/android/BUILD.gn`:
251
252```gn
253...
254import("modules/foo/public/foo_public_java_sources.gni")
255...
256android_library("chrome_java") {
257 ...
258 java_files += foo_public_java_sources
259}
260...
261```
262
263The actual implementation, however, should go into the Foo DFM. For this
264purpose, create a new file `//chrome/android/features/foo/BUILD.gn` and make a
265library with the module Java code in it:
266
267```gn
268import("//build/config/android/rules.gni")
269
270android_library("java") {
271 # Define like ordinary Java Android library.
272 java_files = [
273 "java/src/org/chromium/chrome/features/foo/FooImpl.java",
274 # Add other Java classes that should go into the Foo DFM here.
275 ]
276 # Put other Chrome libs into the classpath so that you can call into the rest
277 # of Chrome from the Foo DFM.
Fred Mellob32b3022019-06-21 18:10:11278 deps = [
Tibor Goldschwendt19364ba2019-04-10 15:59:55279 "//base:base_java",
280 "//chrome/android:chrome_java",
281 # etc.
282 # Also, you'll need to depend on any //third_party or //components code you
283 # are using in the module code.
284 ]
285}
286```
287
Tibor Goldschwendtaef8e392019-07-19 16:39:10288Then, add this new library as a dependency of the Foo module descriptor in
289`//chrome/android/features/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55290
291```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10292foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55293 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10294 java_deps = [
295 "//chrome/android/features/foo:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55296 ]
297}
298```
299
300Finally, tell Android that your module is now containing code. Do that by
Samuel Huang39c7db632019-05-15 14:57:18301removing the `android:hasCode="false"` attribute from the `<application>` tag in
Tibor Goldschwendt19364ba2019-04-10 15:59:55302`//chrome/android/features/foo/java/AndroidManifest.xml`. You should be left
303with an empty tag like so:
304
305```xml
306...
307 <application />
308...
309```
310
311Rebuild and install `monochrome_public_bundle`. Start Chrome and run through a
312flow that tries to executes `bar()`. Depending on whether you installed your
313module (`-m foo`) "`bar in module`" or "`module not installed`" is printed to
314logcat. Yay!
315
Christopher Grant8fea5a12019-07-31 19:12:31316### Adding third-party native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55317
Christopher Grant8fea5a12019-07-31 19:12:31318You can add a third-party native library (or any standalone library that doesn't
319depend on Chrome code) by adding it as a loadable module to the module descriptor in
320`//chrome/android/features/foo/foo_module.gni`:
Tibor Goldschwendt19364ba2019-04-10 15:59:55321
322```gn
Tibor Goldschwendtaef8e392019-07-19 16:39:10323foo_module_desc = {
Tibor Goldschwendt19364ba2019-04-10 15:59:55324 ...
Tibor Goldschwendtaef8e392019-07-19 16:39:10325 loadable_modules_32_bit = [ "//path/to/32/bit/lib.so" ]
326 loadable_modules_64_bit = [ "//path/to/64/bit/lib.so" ]
Tibor Goldschwendt19364ba2019-04-10 15:59:55327}
328```
329
Christopher Grant8fea5a12019-07-31 19:12:31330### Adding Chrome native code
Tibor Goldschwendt19364ba2019-04-10 15:59:55331
Christopher Grant8fea5a12019-07-31 19:12:31332Chrome native code may be placed in a DFM.
333
334A linker-assisted partitioning system automates the placement of code into
335either the main Chrome library or feature-specific .so libraries. Feature code
336may continue to make use of core Chrome code (eg. base::) without modification,
337but Chrome must call feature code through a virtual interface.
338
339Partitioning is explained in [Android Native
340Libraries](android_native_libraries.md#partitioned-libraries).
341
342#### Creating an interface to feature code
343
344One way of creating an interface to a feature library is through an interface
345definition. Feature Foo could define the following in
346`//chrome/android/features/foo/public/foo_interface.h`:
347
348```c++
349class FooInterface {
350 public:
351 virtual ~FooInterface() = default;
352
353 virtual void ProcessInput(const std::string& input) = 0;
354}
355```
356
357Alongside the interface definition, also in
358`//chrome/android/features/foo/public/foo_interface.h`, it's helpful to define a
359factory function type that can be used to create a Foo instance:
360
361```c++
362typedef FooInterface* CreateFooFunction(bool arg1, bool arg2);
363```
364
365<!--- TODO(cjgrant): Add a full, pastable Foo implementation. -->
366The feature library implements class Foo, hiding its implementation within the
367library. The library may then expose a single entrypoint, a Foo factory
368function. Here, C naming is (optionally) used so that the entrypoint symbol
369isn't mangled. In `//chrome/android/features/foo/internal/foo.cc`:
370
371```c++
372extern "C" {
373// This symbol is retrieved from the Foo feature module library via dlsym(),
374// where it's bare address is type-cast to its actual type and executed.
375// The forward declaration here ensures that CreateFoo()'s signature is correct.
376CreateFooFunction CreateFoo;
377
378__attribute__((visibility("default"))) FooInterface* CreateFoo(
379 bool arg1, bool arg2) {
380 return new Foo(arg1, arg2);
381}
382} // extern "C"
383```
384
385Ideally, the interface to the feature will avoid feature-specific types. If a
386feature defines complex data types, and uses them in its own interface, then its
387likely the main library will utilize the code backing these types. That code,
388and anything it references, will in turn be pulled back into the main library.
389
390Therefore, designing the feature inferface to use C types, C++ standard types,
391or classes that aren't expected to move out of Chrome's main library is ideal.
392If feature-specific classes are needed, they simply need to avoid referencing
393feature library internals.
394
395*** note
396**Note:** To help enforce separation between the feature interface and
397implementation, the interface class is best placed in its own GN target, on
398which the feature and main library code both depend.
399***
400
401#### Marking feature entrypoints
402
403Foo's feature module descriptor needs to pull in the appropriate native GN code
404dependencies, and also indicate the name of the file that lists the entrypoint
405symbols. In `//chrome/android/features/foo/foo_module.gni`:
406
407```gn
408foo_module_desc = {
409 ...
410 native_deps = [ "//chrome/android/features/foo/internal:foo" ]
411 native_entrypoints = "//chrome/android/features/foo/internal/module_entrypoints.lst"
412}
413```
414
415The module entrypoint file is a text file listing symbols. In this example,
416`//chrome/android/features/foo/internal/module_entrypoints.lst` has only a
417single factory function exposed:
418
419```shell
420# This file lists entrypoints exported from the Foo native feature library.
421
422CreateFoo
423```
424
425These symbols will be pulled into a version script for the linker, indicating
426that they should be exported in the dynamic symbol table of the feature library.
427
428*** note
429**Note:** If C++ symbol names are chosen as entrypoints, the full mangled names
430must be listed.
431***
432
433Additionally, it's necessary to map entrypoints to a particular partition. To
434follow compiler/linker convention, this is done at the compiler stage. A cflag
435is applied to source file(s) that may supply entrypoints (it's okay to apply the
436flag to all feature source - the attribute is utilized only on modules that
437export symbols). In `//chrome/android/features/foo/internal/BUILD.gn`:
438
439```gn
440static_library("foo") {
441 sources = [
442 ...
443 ]
444
445 # Mark symbols in this target as belonging to the Foo library partition. Only
446 # exported symbols (entrypoints) are affected, and only if this build supports
447 # native modules.
448 if (use_native_modules) {
449 cflags = [ "-fsymbol-partition=libfoo.so" ]
450 }
451}
452```
453
454Feature code is free to use any existing Chrome code (eg. logging, base::,
455skia::, cc::, etc), as well as other feature targets. From a GN build config
456perspective, the dependencies are defined as they normally would. The
457partitioning operation works independently of GN's dependency tree.
458
459```gn
460static_library("foo") {
461 ...
462
463 # It's fine to depend on base:: and other Chrome code.
464 deps = [
465 "//base",
466 "//cc/animation",
467 ...
468 ]
469
470 # Also fine to depend on other feature sub-targets.
471 deps += [
472 ":some_other_foo_target"
473 ]
474
475 # And fine to depend on the interface.
476 deps += [
477 ":foo_interface"
478 ]
479}
480```
481
482#### Opening the feature library
483
484Now, code in the main library can open the feature library and create an
485instance of feature Foo. Note that in this example, no care is taken to scope
486the lifetime of the opened library. Depending on the feature, it may be
487preferable to open and close the library as a feature is used.
488`//chrome/android/features/foo/factory/foo_factory.cc` may contain this:
489
490```c++
491std::unique_ptr<FooInterface> FooFactory(bool arg1, bool arg2) {
492 // Open the feature library, using the partition library helper to map it into
493 // the correct memory location. Specifying partition name *foo* will open
494 // libfoo.so.
495 void* foo_library_handle =
496 base::android::BundleUtils::DlOpenModuleLibraryPartition("foo");
497 }
498 DCHECK(foo_library_handle != nullptr) << "Could not open foo library:"
499 << dlerror();
500
501 // Pull the Foo factory function out of the library. The function name isn't
502 // mangled because it was extern "C".
503 CreateFooFunction* create_foo = reinterpret_cast<CreateFooFunction*>(
504 dlsym(foo_library_handle, "CreateFoo"));
505 DCHECK(create_foo != nullptr);
506
507 // Make and return a Foo!
508 return base::WrapUnique(create_foo(arg1, arg2));
509}
510
511```
512
513*** note
514**Note:** Component builds do not support partitioned libraries (code splitting
515happens across component boundaries instead). As such, an alternate, simplified
516feature factory implementation must be supplied (either by linking in a
517different factory source file, or using #defines in the factory) that simply
518instantiates a Foo object directly.
519***
520
521Finally, the main library is free to utilize Foo:
522
523```c++
524 auto foo = FooFactory::Create(arg1, arg2);
525 foo->ProcessInput(const std::string& input);
526```
527
528### Adding Android resources
Tibor Goldschwendt19364ba2019-04-10 15:59:55529
530In this section we will add the required build targets to add Android resources
531to the Foo DFM.
532
533First, add a resources target to `//chrome/android/features/foo/BUILD.gn` and
534add it as a dependency on Foo's `java` target in the same file:
535
536```gn
537...
538android_resources("java_resources") {
539 # Define like ordinary Android resources target.
540 ...
541 custom_package = "org.chromium.chrome.features.foo"
542}
543...
544android_library("java") {
545 ...
546 deps = [
547 ":java_resources",
548 ]
549}
550```
551
552To add strings follow steps
553[here](http://dev.chromium.org/developers/design-documents/ui-localization) to
554add new Java GRD file. Then create
555`//chrome/android/features/foo/java/strings/android_foo_strings.grd` as follows:
556
557```xml
558<?xml version="1.0" encoding="UTF-8"?>
559<grit
560 current_release="1"
561 latest_public_release="0"
562 output_all_resource_defines="false">
563 <outputs>
564 <output
565 filename="values-am/android_foo_strings.xml"
566 lang="am"
567 type="android" />
568 <!-- List output file for all other supported languages. See
569 //chrome/android/java/strings/android_chrome_strings.grd for the full
570 list. -->
571 ...
572 </outputs>
573 <translations>
574 <file lang="am" path="vr_translations/android_foo_strings_am.xtb" />
575 <!-- Here, too, list XTB files for all other supported languages. -->
576 ...
577 </translations>
578 <release allow_pseudo="false" seq="1">
579 <messages fallback_to_english="true">
580 <message name="IDS_BAR_IMPL_TEXT" desc="Magical string.">
581 impl
582 </message>
583 </messages>
584 </release>
585</grit>
586```
587
588Then, create a new GRD target and add it as a dependency on `java_resources` in
589`//chrome/android/features/foo/BUILD.gn`:
590
591```gn
592...
593java_strings_grd("java_strings_grd") {
594 defines = chrome_grit_defines
595 grd_file = "java/strings/android_foo_strings.grd"
596 outputs = [
597 "values-am/android_foo_strings.xml",
598 # Here, too, list output files for other supported languages.
599 ...
600 ]
601}
602...
603android_resources("java_resources") {
604 ...
605 deps = [":java_strings_grd"]
606 custom_package = "org.chromium.chrome.features.foo"
607}
608...
609```
610
611You can then access Foo's resources using the
612`org.chromium.chrome.features.foo.R` class. To do this change
613`//chrome/android/features/foo/java/src/org/chromium/chrome/features/foo/FooImpl.java`
614to:
615
616```java
617package org.chromium.chrome.features.foo;
618
619import org.chromium.base.ContextUtils;
620import org.chromium.base.Log;
Tibor Goldschwendt573cf3022019-05-10 17:23:30621import org.chromium.base.annotations.UsedByReflection;
Tibor Goldschwendt19364ba2019-04-10 15:59:55622import org.chromium.chrome.features.foo.R;
623
Tibor Goldschwendt573cf3022019-05-10 17:23:30624@UsedByReflection("FooModule")
Tibor Goldschwendt19364ba2019-04-10 15:59:55625public class FooImpl implements Foo {
626 @Override
627 public void bar() {
628 Log.i("FOO", ContextUtils.getApplicationContext().getString(
629 R.string.bar_impl_text));
630 }
631}
632```
633
634*** note
635**Warning:** While your module is emulated (see [below](#on-demand-install))
636your resources are only available through
637`ContextUtils.getApplicationContext()`. Not through activities, etc. We
638therefore recommend that you only access DFM resources this way. See
639[crbug/949729](https://bugs.chromium.org/p/chromium/issues/detail?id=949729)
640for progress on making this more robust.
641***
642
643
644### Module install
645
646So far, we have installed the Foo DFM as a true split (`-m foo` option on the
647install script). In production, however, we have to explicitly install the Foo
648DFM for users to get it. There are two install options: _on-demand_ and
649_deferred_.
650
651
652#### On-demand install
653
654On-demand requesting a module will try to download and install the
655module as soon as possible regardless of whether the user is on a metered
656connection or whether they have turned updates off in the Play Store app.
657
Tibor Goldschwendt573cf3022019-05-10 17:23:30658You can use the autogenerated module class to on-demand install the module like
659so:
Tibor Goldschwendt19364ba2019-04-10 15:59:55660
661```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30662FooModule.install((success) -> {
663 if (success) {
664 FooModule.getImpl().bar();
665 }
Tibor Goldschwendt19364ba2019-04-10 15:59:55666});
667```
668
669**Optionally**, you can show UI telling the user about the install flow. For
Tibor Goldschwendt573cf3022019-05-10 17:23:30670this, add a function like the one below. Note, it is possible
Tibor Goldschwendt19364ba2019-04-10 15:59:55671to only show either one of the install, failure and success UI or any
672combination of the three.
673
674```java
675public static void installModuleWithUi(
676 Tab tab, OnModuleInstallFinishedListener onFinishedListener) {
677 ModuleInstallUi ui =
678 new ModuleInstallUi(
679 tab,
680 R.string.foo_module_title,
681 new ModuleInstallUi.FailureUiListener() {
682 @Override
683 public void onRetry() {
684 installModuleWithUi(tab, onFinishedListener);
685 }
686
687 @Override
688 public void onCancel() {
689 onFinishedListener.onFinished(false);
690 }
691 });
692 // At the time of writing, shows toast informing user about install start.
693 ui.showInstallStartUi();
Tibor Goldschwendt573cf3022019-05-10 17:23:30694 FooModule.install(
Tibor Goldschwendt19364ba2019-04-10 15:59:55695 (success) -> {
696 if (!success) {
697 // At the time of writing, shows infobar allowing user
698 // to retry install.
699 ui.showInstallFailureUi();
700 return;
701 }
702 // At the time of writing, shows toast informing user about
703 // install success.
704 ui.showInstallSuccessUi();
705 onFinishedListener.onFinished(true);
706 });
707}
708```
709
710To test on-demand install, "fake-install" the DFM. It's fake because
711the DFM is not installed as a true split. Instead it will be emulated by Chrome.
712Fake-install and launch Chrome with the following command:
713
714```shell
715$ $OUTDIR/bin/monochrome_public_bundle install -m base -f foo
Samuel Huang39c7db632019-05-15 14:57:18716$ $OUTDIR/bin/monochrome_public_bundle launch --args="--fake-feature-module-install"
Tibor Goldschwendt19364ba2019-04-10 15:59:55717```
718
719When running the install code, the Foo DFM module will be emulated.
720This will be the case in production right after installing the module. Emulation
721will last until Play Store has a chance to install your module as a true split.
722This usually takes about a day.
723
724*** note
725**Warning:** There are subtle differences between emulating a module and
726installing it as a true split. We therefore recommend that you always test both
727install methods.
728***
729
730
731#### Deferred install
732
733Deferred install means that the DFM is installed in the background when the
734device is on an unmetered connection and charging. The DFM will only be
735available after Chrome restarts. When deferred installing a module it will
736not be faked installed.
737
738To defer install Foo do the following:
739
740```java
Tibor Goldschwendt573cf3022019-05-10 17:23:30741FooModule.installDeferred();
Tibor Goldschwendt19364ba2019-04-10 15:59:55742```
743
744
745### Integration test APK and Android K support
746
747On Android K we still ship an APK. To make the Foo feature available on Android
748K add its code to the APK build. For this, add the `java` target to
749the `chrome_public_common_apk_or_module_tmpl` in
750`//chrome/android/chrome_public_apk_tmpl.gni` like so:
751
752```gn
753template("chrome_public_common_apk_or_module_tmpl") {
754 ...
755 target(_target_type, target_name) {
756 ...
757 if (_target_type != "android_app_bundle_module") {
758 deps += [
Tibor Goldschwendtaef8e392019-07-19 16:39:10759 "//chrome/android/features/foo:java",
Tibor Goldschwendt19364ba2019-04-10 15:59:55760 ]
761 }
762 }
763}
764```
765
766This will also add Foo's Java to the integration test APK. You may also have to
767add `java` as a dependency of `chrome_test_java` if you want to call into Foo
768from test code.