blob: c4d7280f666244dad6eecd9fe1dac1448e681d16 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.base;
import static org.chromium.chrome.browser.base.SplitCompatUtils.CHROME_SPLIT_NAME;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.BundleUtils;
import org.chromium.base.JNIUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.metrics.RecordHistogram;
/**
* Application class to use for Chrome when //chrome code is in an isolated split. This class will
* perform any necessary initialization for non-browser processes without loading code from the
* chrome split. In the browser process, the necessary logic is loaded from the chrome split using
* reflection.
*
* This class will be used when isolated splits are enabled.
*/
public class SplitChromeApplication extends SplitCompatApplication {
private static final String TAG = "SplitChromeApp";
@SuppressLint("StaticFieldLeak")
private static SplitPreloader sSplitPreloader;
private String mChromeApplicationClassName;
public SplitChromeApplication() {
this(SplitCompatUtils.getIdentifierName(
"org.chromium.chrome.browser.ChromeApplication$ChromeApplicationImpl"));
}
public SplitChromeApplication(String chromeApplicationClassName) {
mChromeApplicationClassName = chromeApplicationClassName;
}
@Override
public void onCreate() {
finishPreload(CHROME_SPLIT_NAME);
super.onCreate();
}
@Override
protected void attachBaseContext(Context context) {
super.attachBaseContext(context);
if (isBrowserProcess()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
DexFixer.setHasIsolatedSplits(true);
}
setImplSupplier(() -> {
Context chromeContext = SplitCompatUtils.createChromeContext(this);
return (Impl) SplitCompatUtils.newInstance(
chromeContext, mChromeApplicationClassName);
});
applyActivityClassLoaderWorkaround();
} else {
setImplSupplier(() -> createNonBrowserApplication());
}
}
@Override
public Context createContextForSplit(String name) throws PackageManager.NameNotFoundException {
try (TraceEvent te = TraceEvent.scoped("SplitChromeApplication.createContextForSplit")) {
// Wait for any splits that are preloading so we don't have a race to update the
// class loader cache (b/172602571).
finishPreload(name);
long startTime = SystemClock.uptimeMillis();
Context context = super.createContextForSplit(name);
RecordHistogram.recordTimesHistogram("Android.IsolatedSplits.ContextCreateTime." + name,
SystemClock.uptimeMillis() - startTime);
return context;
}
}
@Override
protected void performBrowserProcessPreloading(Context context) {
// The chrome split has a large amount of code, which can slow down startup. Loading
// this in the background allows us to do this in parallel with startup tasks which do
// not depend on code in the chrome split.
sSplitPreloader = new SplitPreloader(context);
// If the chrome module is not enabled or isolated splits are not supported (e.g. in Android
// N), the onComplete function will run immediately so it must handle the case where the
// base context of the application has not been set yet.
sSplitPreloader.preload(CHROME_SPLIT_NAME, new SplitPreloader.OnComplete() {
@Override
public void runImmediatelyInBackgroundThread(Context chromeContext) {
// A new thread is started here because we do not want to delay returning the chrome
// Context, since that slows down startup. This thread must be a HandlerThread
// because AsyncInitializationActivity (a base class of ChromeTabbedActivity)
// creates a Handler, so needs to have a Looper prepared.
HandlerThread thread = new HandlerThread("ActivityPreload");
thread.start();
new Handler(thread.getLooper()).post(() -> {
try {
// Create a throwaway instance of ChromeTabbedActivity. This will warm up
// the chrome ClassLoader, and perform loading of classes used early in
// startup in the background.
chromeContext.getClassLoader()
.loadClass(
"org.chromium.chrome.browser.ChromeTabbedActivity$Preload")
.newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
thread.quit();
});
}
@Override
public void runInUiThread(Context chromeContext) {
// When installed, the vr module is always loaded on startup, so preload here.
sSplitPreloader.preload("vr", null);
// If the chrome module is not enabled or isolated splits are not supported,
// chromeContext will have the same ClassLoader as the base context, so no need to
// replace the ClassLoaders here.
if (!context.getClassLoader().equals(chromeContext.getClassLoader())) {
// Replace the application Context's ClassLoader with the chrome ClassLoader,
// because the application ClassLoader is expected to be able to access all
// chrome classes.
BundleUtils.replaceClassLoader(
SplitChromeApplication.this, chromeContext.getClassLoader());
JNIUtils.setClassLoader(chromeContext.getClassLoader());
}
}
});
}
protected Impl createNonBrowserApplication() {
return new Impl();
}
/* package */ static void finishPreload(String name) {
if (sSplitPreloader != null) {
sSplitPreloader.wait(name);
}
}
/**
* Fixes Activity ClassLoader if necessary. Isolated splits can cause a ClassLoader mismatch
* between the Application and Activity ClassLoaders. We have a workaround in
* SplitCompatAppComponentFactory which overrides the Activity ClassLoader, but this does not
* change the ClassLoader for the Activity's base context. We override that ClassLoader here, so
* it matches the ClassLoader that was used to load the Activity class. Note that
* ContextUtils.getApplicationContext().getClassLoader() may not give the right ClassLoader here
* because the Activity may be in a DFM which is a child of the chrome DFM. See
* crbug.com/1146745 for more info.
*/
private static void applyActivityClassLoaderWorkaround() {
ApplicationStatus.registerStateListenerForAllActivities(
new ApplicationStatus.ActivityStateListener() {
@Override
public void onActivityStateChange(
Activity activity, @ActivityState int newState) {
if (newState != ActivityState.CREATED) {
return;
}
// ClassLoaders are already the same, no workaround needed.
if (activity.getClassLoader().equals(
activity.getClass().getClassLoader())) {
return;
}
BundleUtils.replaceClassLoader(
activity.getBaseContext(), activity.getClass().getClassLoader());
}
});
}
}