Extend the multidex logic to only kick in for the main browser application.
For each Application instances that falls into the Multidex path,
there is a regression in startup time as it must bootstrap the
classloader to be able to access the additional sources.
During a normal run of Chrome on Android, there are three Application
instances created (Main Browser, Renderer, and GPU).
The renderer process is an isolated process, which was being skipped
with the existing check in this file.
The problem is with the GPU process. We had the assumption that all
non-isolated processes would only be hit once by the Multidex startup
regression, but it turns out that any Application in a different
process is hit by the same slowdown. This means that the first time
the page attempts to paint anything, you see a hang as the GPU process
is attempting to initialize.
This results in a bunch of tests (mainly on K builds on slow devices)
to start flaking as they timeout trying to load the first non
about:blank page.
This change extends the existing check for isolated process to also
skip initializing multi dex for anything running in a separate
process. As a result, anything running in a different process will
need to ensure all code is included in the main dex or force the
additional dex files to be loaded (not as of yet needed), but we
no longer see the slow down on basic operations in Chrome.
BUG=560600
Review URL: https://codereview.chromium.org/1469803007
Cr-Commit-Position: refs/heads/master@{#361738}
CrOS-Libchrome-Original-Commit: ae7c11660f7b39f2d5c4dc28baf404a45aa0fc2d
diff --git a/base/android/java/templates/ChromiumMultiDex.template b/base/android/java/templates/ChromiumMultiDex.template
index 5761a45..441294c 100644
--- a/base/android/java/templates/ChromiumMultiDex.template
+++ b/base/android/java/templates/ChromiumMultiDex.template
@@ -4,7 +4,11 @@
package org.chromium.base.multidex;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Process;
import android.support.multidex.MultiDex;
@@ -13,6 +17,7 @@
import org.chromium.base.VisibleForTesting;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
/**
* Performs multidex installation for non-isolated processes.
@@ -22,6 +27,12 @@
private static final String TAG = "base_multidex";
/**
+ * Suffix for the meta-data tag in the AndroidManifext.xml that determines whether loading
+ * secondary dexes should be skipped for a given process name.
+ */
+ private static final String IGNORE_MULTIDEX_KEY = ".ignore_multidex";
+
+ /**
* Installs secondary dexes if possible/necessary.
*
* Isolated processes (e.g. renderer processes) can't load secondary dex files on
@@ -38,28 +49,65 @@
@VisibleForTesting
#if defined(MULTIDEX_CONFIGURATION_Debug)
public static void install(Context context) {
- try {
- // TODO(jbudorick): Back out this version check once support for K & below works.
- // http://crbug.com/512357
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && processIsIsolated()) {
- Log.i(TAG, "Skipping multidex installation: inside isolated process.");
- } else {
- MultiDex.install(context);
- Log.i(TAG, "Completed multidex installation.");
- }
- } catch (NoSuchMethodException e) {
- Log.wtf(TAG, "Failed multidex installation", e);
- } catch (IllegalAccessException e) {
- Log.wtf(TAG, "Failed multidex installation", e);
- } catch (InvocationTargetException e) {
- Log.wtf(TAG, "Failed multidex installation", e);
+ // TODO(jbudorick): Back out this version check once support for K & below works.
+ // http://crbug.com/512357
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP
+ && !shouldInstallMultiDex(context)) {
+ Log.i(TAG, "Skipping multidex installation: not needed for process.");
+ } else {
+ MultiDex.install(context);
+ Log.i(TAG, "Completed multidex installation.");
}
}
- // Calls Process.isIsolated, a private Android API.
- private static boolean processIsIsolated()
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- return (boolean) Process.class.getMethod("isIsolated").invoke(null);
+ private static String getProcessName(Context context) {
+ try {
+ String currentProcessName = null;
+ int pid = android.os.Process.myPid();
+
+ ActivityManager manager =
+ (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
+ if (processInfo.pid == pid) {
+ currentProcessName = processInfo.processName;
+ break;
+ }
+ }
+
+ return currentProcessName;
+ } catch (SecurityException ex) {
+ return null;
+ }
+ }
+
+ // Determines whether MultiDex should be installed for the current process. Isolated
+ // Processes should skip MultiDex as they can not actually access the files on disk.
+ // Privileged processes need ot have all of their dependencies in the MainDex for
+ // performance reasons.
+ private static boolean shouldInstallMultiDex(Context context) {
+ try {
+ Method isIsolatedMethod =
+ android.os.Process.class.getMethod("isIsolated");
+ Object retVal = isIsolatedMethod.invoke(null);
+ if (retVal != null && retVal instanceof Boolean && ((Boolean) retVal)) {
+ return false;
+ }
+ } catch (IllegalAccessException | IllegalArgumentException
+ | InvocationTargetException | NoSuchMethodException e) {
+ // Ignore and fall back to checking the app processes.
+ }
+
+ String currentProcessName = getProcessName(context);
+ if (currentProcessName == null) return true;
+
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ ApplicationInfo appInfo = packageManager.getApplicationInfo(context.getPackageName(),
+ PackageManager.GET_META_DATA);
+ return !appInfo.metaData.getBoolean(currentProcessName + IGNORE_MULTIDEX_KEY, false);
+ } catch (PackageManager.NameNotFoundException e) {
+ return true;
+ }
}
#else
public static void install(Context context) {