blob: bdd9d9aa1819b93429d6ffad3d034e0283145b07 [file] [log] [blame] [view]
Andrew Grieve520e0902022-04-27 13:19:251# Isolated Splits
2
3This doc aims to explain the ins and outs of using Isolated Splits on Android.
4
5For an overview of apk splits and how to use them in Chrome, see
6[android_dynamic_feature_modules.md].
7
8[TOC]
9
10## About
11
12### What are Isolated Splits?
13
14Isolated Splits is an opt-in feature (via [android:isolatedSplits] manifest
15entry) that cause all feature splits in an application to have separate
16`Context` objects, rather than being merged together into a single Application
17`Context`. The `Context` objects have distict `ClassLoader` and `Resources`
18instances. They are loaded on-demand instead of eagerly on launch.
19
20With Isolated Splits, each feature split is loaded in its own ClassLoader, with
21the parent split set as the parent ClassLoader.
22
23[android:isolatedSplits]: https://developer.android.com/reference/android/R.attr#isolatedSplits
24
25### Why use Isolated Splits?
26
27The more DEX that is loaded on start-up, the more RAM and time it takes for
28application code to start running. Loading less code on start-up is particularly
29helpful for Chrome, since Chrome tends to spawn a lot of processes, and because
30renderer processes require almost no DEX.
31
32### What Splits Exist in Chrome?
33
34Chrome's splits look like:
35
36```
Clemens Arbesser46cb4f72022-12-07 15:08:1737base.apk <-- chrome.apk <-- image_editor.apk
Andrew Grieve520e0902022-04-27 13:19:2538 <-- feedv2.apk
39 <-- ...
40```
41
42* The browser process loads the `chrome` split on start-up, and other splits are
43 loaded on-demand.
44* Renderer and GPU processes do not load any feature splits.
45 * The `chrome` split exists to minimize the amount of DEX loaded by renderer
46 processes. However, it also enables faster browser process start-up by
47 allowing DEX to be loaded concurrently with other start-up tasks.
48
49### How are Isolated Splits Loaded?
50
51There are two ways:
521) They can be loaded by Android Framework when handling an intent.
53 * E.g.: If a feature split defines an Activity in its manifest, Android
54 will create the split's Context and associate the Activity with it.
552) They can be loaded explicitly via [BundleUtils.createIsolatedSplitContext()].
56 * The most common way to load in this way is through declaring a
57 `ModuleInterface`, as described in [android_dynamic_feature_modules.md].
58
59[BundleUtils.createIsolatedSplitContext()]: https://source.chromium.org/search?q=func:createIsolatedSplitContext&ss=chromium
60[android_dynamic_feature_modules.md]: android_dynamic_feature_modules.md
61
62## OS Support for Isolated Splits
63
64Initial support was added in Android O. On earlier Android versions, all
65feature splits are loaded during process start-up and merged into the
66Application Context.
67
68## OS Bugs
69
70### Base ClassLoader used for Services in Splits (Android Pre-S)
71
72Service Contexts are created with the base split's ClassLoader rather than the
73split's ClassLoader.
74
75Fixed in Android S. Bug: [b/169196314] (Googler only).
76
77**Work-around:**
78
79We use [SplitCompatService] (and siblings) to put a minimal service class in the
80base split. They forward all calls to an implementation class, which can live
81in the `chrome` split (or other splits). We also have a [compile-time check] to
82enforce that no Service subclasses exist outside of the base split.
83
84[b/169196314]: https://issuetracker.google.com/169196314
85[SplitCompatService]: https://source.chromium.org/search?q=symbol:SplitCompatService&ss=chromium
Sam Maier18745fa2024-12-03 16:26:0486[compile-time check]: https://source.chromium.org/chromium/chromium/src/+/main:build/android/gyp/create_app_bundle.py;l=446;drc=c4dd266492ad1e242161b415ac5a1d9fccd7a041
Andrew Grieve520e0902022-04-27 13:19:2587
88### Corrupted .odex (Android O MR1)
89
90Android O MR1 has a bug where `bg-dexopt-job` (runs during maintenance windows)
91breaks optimized dex files for Isolated Splits. The corrupt `.odex` files cause
92extremely slow startup times.
93
94**Work-around:**
95
96We [preemptively run] `dexopt` so that `bg-dexopt-job` decides there is no work
97to do. We trigger this from [PackageReplacedBroadcastReceiver] so that it
98happens whenever Chrome is updated rather than when the user launches Chrome.
99
100[preemptively run]: https://source.chromium.org/search?q=symbol:DexFixer.needsDexCompile&ss=chromium
101[PackageReplacedBroadcastReceiver]: https://source.chromium.org/search?q=symbol:PackageReplacedBroadcastReceiver&ss=chromium
102
Andrew Grieve10c6c562024-08-01 18:36:14103### Conflicting ClassLoaders #1
Andrew Grieve520e0902022-04-27 13:19:25104
Andrew Grievefdc83022023-03-24 16:49:57105Tracked by [b/172602571], sometimes a split's parent ClassLoader is different
106from the Application's ClassLoader. This manifests as odd-looking
Andrew Grieve520e0902022-04-27 13:19:25107`ClassCastExceptions` where `"TypeA cannot be cast to TypeA"` (since the two
108`TypeAs` are from different ClassLoaders).
109
Andrew Grieveb93843f2022-06-21 15:08:37110Tracked by UMA `Android.IsolatedSplits.ClassLoaderReplaced`. Occurs < 0.05% of
111the time.
Andrew Grieve520e0902022-04-27 13:19:25112
113**Work-around:**
114
115On Android O, there is no work-around. We just [detect and crash early].
116
117Android P added [AppComponentFactory], which offers a hook that we use to
118[detect and fix] ClassLoader mixups. The ClassLoader mixup also needs to be
119corrected for `ContextImpl` instances, which we do via
120[ChromeBaseAppCompatActivity.attachBaseContext()].
121
122[b/172602571]: https://issuetracker.google.com/172602571
123[detect and crash early]: https://source.chromium.org/search?q=crbug.com%2F1146745&ss=chromium
124[AppComponentFactory]: https://developer.android.com/reference/android/app/AppComponentFactory
125[detect and fix]: https://source.chromium.org/search?q=f:splitcompatappcomponentfactory&ss=chromium
126[ChromeBaseAppCompatActivity.attachBaseContext()]: https://source.chromium.org/search?q=BundleUtils\.checkContextClassLoader&ss=chromium
127
Andrew Grieve10c6c562024-08-01 18:36:14128### Conflicting ClassLoaders #2
129
130Tracked by [b/172602571], when a new split language split or feature split is
131installed, the ClassLoaders for non-base splits are recreated. Any reference to
132a class from the previous ClassLoader (e.g. due to native code holding
133references to them) will result in `ClassCastExceptions` where
134`"TypeA cannot be cast to TypeA"`.
135
136**Work-around:**
137
138There is no work-around. This is a source of crashes. We could potentially
139mitigate by restarting chrome when a split is installed.
140
Andrew Grieve520e0902022-04-27 13:19:25141### System.loadLibrary() Broken for Libraries in Splits
142
143Tracked by [b/171269960], Android is not adding the apk split to the associated
144ClassLoader's `nativeSearchPath`. This means that `libfoo.so` within an
145isolated split is not found by a call to `System.loadLibrary("foo")`.
146
147**Work-around:**
148
149Load libraries via `System.load()` instead.
150
151```java
152System.load(BundleUtils.getNativeLibraryPath("foo", "mysplitsname"));
153```
154
155[b/171269960]: https://issuetracker.google.com/171269960
156
Andrew Grieve89a28b12022-12-02 19:57:22157### System.loadLibrary() Unusable from Split if Library depends on Another Loaded by Base Split
158
159Also tracked by [b/171269960], maybe related to linker namespaces. If a split
160tries to load `libfeature.so`, and `libfeature.so` has a `DT_NEEDED` entry for
161`libbase.so`, and `libbase.so` is loaded by the base split, then the load will
162fail.
163
164**Work-around:**
165
166Have base split load libraries from within splits. Proxy all JNI calls through
167a class that exists in the base split.
168
Andrew Grievee7a8cdf2022-05-16 17:46:11169### System.loadLibrary() Broken for Libraries in Splits on System Image
170
171Also tracked by [b/171269960], Android's linker config (`ld.config.txt`) sets
172`permitted_paths="/data:/mnt/expand"`, and then adds the app's `.apk` to an
173allowlist. This allowlist does not contain apk splits, so library loading is
174blocked by `permitted_paths` when the splits live on the `/system` partition.
175
176**Work-around:**
177
178Use compressed system image stubs (`.apk.gz` and `-Stub.apk`) so that Chrome is
179extracted to the `/data` partition upon boot.
180
Andrew Grieve520e0902022-04-27 13:19:25181### Too Many Splits Break App Zygote
182
183Starting with Android Q / TriChrome, Chrome uses an [Application Zygote]. As
184part of initialization, Chrome's `ApplicationInfo` object is serialized into a
185fixed size buffer. Each installed split increases the size of the
186`ApplicationInfo` object, and can push it over the buffer's limit.
187
188**Work-around:**
189
190Do not add too many splits, and monitor the size of our `ApplicationInfo` object
191([crbug/1298496]).
192
193[crbug/1298496]: https://bugs.chromium.org/p/chromium/issues/detail?id=1298496
194[Application Zygote]: https://developer.android.com/reference/android/app/ZygotePreload
195
Andrew Grievefdc83022023-03-24 16:49:57196### AppComponentFactory does not Hook Split ClassLoaders
197
198`AppComponentFactory#instantiateClassLoader()` is meant to allow apps to hook
199`ClassLoader` creation. The hook is called for the base split, but not for other
200isolated splits. Tracked by [b/265583114]. There is no work-around.
201
202[b/265583114]: https://issuetracker.google.com/265583114
203
204### Incorrect Handling of Shared Libraries
205
206Tracked by [b/265589431]. If an APK split has `<uses-library>` in its manifest,
207the classloader for the split is meant to have that library added to it by the
208framework. However, Android does not add the library to the classpath when a
209split is dynamically installed, but instead adds it to the classpath of the base
210split's classloader upon subsequent app launches.
211
212**Work-around:**
213
Andrew Grieve6d44e66e2024-07-26 14:24:12214 * Always add `<uses-library>` to the base split.
215
216[b/265589431]: https://issuetracker.google.com/265589431
Andrew Grievefdc83022023-03-24 16:49:57217
Andrew Grieve520e0902022-04-27 13:19:25218## Other Quirks & Subtleties
219
220### System Image APKs
221
222When distributing Chrome on Android system images, we generate a single `.apk`
223file that contains all splits merged together (or rather, all splits whose
Andrew Grieve5f71ed622022-04-28 13:57:38224`AndroidManifest.xml` contain `<dist:fusing dist:include="true" />`). We do this
225for simplicity; Android supports apk splits on the system image.
Andrew Grieve520e0902022-04-27 13:19:25226
Andrew Grieve5f71ed622022-04-28 13:57:38227You can build Chrome's system `.apk` via:
Andrew Grieve520e0902022-04-27 13:19:25228```sh
Andrew Grieve5f71ed622022-04-28 13:57:38229out/Release/bin/trichrome_chrome_bundle build-bundle-apks --output-apks SystemChrome.apks --build-mode system
230unzip SystemChrome.apks system/system.apk
Andrew Grieve520e0902022-04-27 13:19:25231```
232
233Shipping a single `.apk` file simplifies distribution, but eliminates all the
234benefits of Isolated Splits.
235
236### Chrome's Application ClassLoader
237
238A lot of Chrome's code uses the `ContextUtils.getApplicationContext()` as a
239Context object. Rather than auditing all usages and replacing applicable ones
240with the `chrome` split's Context, we [use reflection] to change the
241Application instance's ClassLoader to point to the `chrome` split's ClassLoader.
242
243[use reflection]: https://source.chromium.org/search?q=f:SplitChromeApplication%20replaceClassLoader&ss=chromium
244
245### ContentProviders
246
247Unlike other application components, ContentProviders are created on start-up
248even when they are not the reason the process is being created. If a
249ContentProvider were to be declared in a split, its split's Context would need
250to be loaded during process creation, eliminating any benefit.
251
252**Work-around:**
253
254We declare all ContentProviders in the base split's `AndroidManifest.xml` and
255enforce this with a [compile-time check]. ContentProviders that would pull in
256significant amounts of code use [SplitCompatContentProvider] to delegate to a
257helper class living within a split.
258
259[compile-time check]: https://source.chromium.org/search?q=symbol:_MaybeCheckServicesAndProvidersPresentInBase&ss=chromium
260[SplitCompatContentProvider]: https://source.chromium.org/search?q=symbol:SplitCompatContentProvider&ss=chromium
261
262### JNI and ClassLoaders
263
264When you call from native-&gt;Java (via `@CalledByNative`), there are two APIs
265that Chrome could use to resolve the target class:
266
2671) JNI API: [JNIEnv::FindClass()]
2682) Java Reflection API:`ClassLoader.loadClass())`
269
270Chrome uses #2. For methods within feature splits, `generate_jni()` targets
271use `split_name = "foo"` to make the generated JNI code use the split's
272ClassLoader.
273
274[JNIEnv::FindClass()]: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html#wp16027
275
276### Accessing Android Resources
277
278When resources live in a split, they must be accessed through a Context object
279associated with that split. However:
280
281* Bug: Chrome's build system [improperly handles ID conflicts] between splits.
282* Bug: Splash screens [fail to load] for activities in Isolated Splits (unless
283 associated resources are defined in the base split).
284* Quirk: `RemoteViews`, notification icons, and other Android features that
285 access resources by Package ID require resources to be in the base split when
286 Isolated Splits are enabled.
287
288**Work-around:**
289
290Chrome [stores all Android resources in the base split]. There is [a crbug] to
291track moving resources into splits, but it may prove too challenging.
292
293[stores all Android resources in the base split]: https://source.chromium.org/search?q=recursive_resource_deps%5C%20%3D%5C%20true
294[improperly handles ID conflicts]: https://crbug.com/1133898
295[fail to load]: https://issuetracker.google.com/171743801
296[a crbug]: https://crbug.com/1165782
297
298### Inflating Layouts
299
300Layouts should be inflated with an Activity Context so that
301configuration-specific resources and themes are used. If layouts contain
302references to View classes from different feature splits than the Activity's,
303then the views' split ClassLoaders must be used.
304
305**Work-around:**
306
307Use the `ContextWrapper` created via: [BundleUtils.createContextForInflation()]
308
309[BundleUtils.createContextForInflation()]: https://source.chromium.org/search?q=symbol:BundleUtils.createContextForInflation&ss=chromium
310
Andrew Grieve9439213f2022-11-23 16:30:32311### onRestoreInstanceState with Classes From Splits
Andrew Grieve520e0902022-04-27 13:19:25312
313When Android kills an app, it normally calls `onSaveInstanceState()` to allow
314the app to first save state. The saved state includes the class names of active
Andrew Grieve9439213f2022-11-23 16:30:32315Fragments, RecyclerViews, and potentially other classes from splits. Upon
316re-launch, these class names are used to reflectively instantiate instances.
317`FragmentManager` uses the ClassLoader of the Activity to instantiate them,
318and `RecyclerView` uses the ClassLoader associated with the `Bundle` object.
319The reflection fails if the active Activity resides in a different spilt from
320the reflectively instantiated classes.
Andrew Grieve520e0902022-04-27 13:19:25321
322**Work-around:**
323
324Chrome stores the list of all splits that have been used for inflation during
Andrew Grieve9439213f2022-11-23 16:30:32325[`onSaveInstanceState`] and then uses [a custom ClassLoader] to look within them
326for classes that do not exist in the application's ClassLoader. The custom
327ClassLoader is passed to `Bundle` instances in
328`ChromeBaseAppCompatActivity.onRestoreInstanceState()`.
Andrew Grieve520e0902022-04-27 13:19:25329
Andrew Grieve87c12e02022-11-28 15:19:01330Having Android Framework call `Bundle.setClassLoader()` is tracked in
331[b/260574161].
332
Andrew Grieve9439213f2022-11-23 16:30:32333[`onSaveInstanceState`]: https://source.chromium.org/search?q=symbol:ChromeBaseAppCompatActivity.onSaveInstanceState&ss=chromium
Andrew Grieve520e0902022-04-27 13:19:25334[a custom ClassLoader]: https://source.chromium.org/search?q=symbol:ChromeBaseAppCompatActivity.getClassLoader&ss=chromium
Andrew Grieve87c12e02022-11-28 15:19:01335[b/260574161]: https://issuetracker.google.com/260574161
Andrew Grieve520e0902022-04-27 13:19:25336
Andrew Grieve638a46a2025-03-03 20:58:20337### Package Private Methods
Sam Maier05e341ad2022-04-28 15:52:08338
339Due to having different ClassLoaders, package-private methods don't work across
Andrew Grieve638a46a2025-03-03 20:58:20340the boundary, even though they will compile. Release builds will fail during
341the R8 step, which has a check for cross-split package-private access.
Sam Maier05e341ad2022-04-28 15:52:08342
343**Work around:**
344
345Make any method public that you wish to call in another module, even if it's in
346the same package.
347
Andrew Grieve520e0902022-04-27 13:19:25348### Proguarding Splits
349
350"Proguarding" is the build step that performs whole-program optimization of Java
351code, and "R8" is the program Chrome uses to do this. R8 currently supports
352mapping input `.jar` files to output feature splits. If two feature splits share
353a common GN `dep`, then its associated `.jar` will be promoted to the parent
354split (or to the base split) by our [proguard.py] wrapper script.
355
356This scheme means that if a single class from a large library is needed by, or
357promoted to, the base split, then every class needed from that library by
358feature splits will also remain in the base split. The feature request to have
359R8 move code into deeper splits on a per-class basis is [b/225876019] (Googler
360only).
361
362[proguard.py]: https://source.chromium.org/search?q=symbol:_DeDupeInputJars%20f:proguard.py&ss=chromium
363[b/225876019]: https://issuetracker.google.com/225876019
364
Andrew Grievefdc83022023-03-24 16:49:57365### Metadata in Splits
366
367Metadata is queried on a per-app basis (not a per-split basis). E.g.:
368
369```java
370ApplicationInfo ai = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
371Bundle b = ai.metaData;
372```
373
374This bundle contains merged values from all fully-installed apk splits.
375
Andrew Grieve520e0902022-04-27 13:19:25376## Other Resources
377
378 * [go/isolated-splits-dev-guide] (Googlers only).
379 * [go/clank-isolated-splits-architecture] (Googlers only).
380
381[go/isolated-splits-dev-guide]: http://go/isolated-splits-dev-guide
382[go/clank-isolated-splits-architecture]: http://go/clank-isolated-splits-architecture