blob: 004a89ec4dbf416cc637df8c8d6fcbd362c9d208 [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 android.content.Context;
import android.os.SystemClock;
import androidx.collection.SimpleArrayMap;
import org.chromium.base.BundleUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.AsyncTask;
import org.chromium.base.task.TaskTraits;
/**
* Handles preloading split Contexts on a background thread. Loading a new isolated split
* Context can be expensive since the ClassLoader may need to be created. See crbug.com/1150600 for
* more info.
*/
public class SplitPreloader {
private final SimpleArrayMap<String, PreloadTask> mPreloadTasks = new SimpleArrayMap<>();
private final Context mContext;
/** Interface to run code after preload completion. */
public interface OnComplete {
void run(Context context);
}
private class PreloadTask extends AsyncTask<Void> {
private final String mName;
private OnComplete mOnComplete;
public PreloadTask(String name, OnComplete onComplete) {
mName = name;
mOnComplete = onComplete;
}
@Override
protected Void doInBackground() {
Context context = createSplitContext();
return null;
}
@Override
protected void onPostExecute(Void result) {
finish();
}
/**
* Waits for the preload to finish and calls the onComplete function if needed. onComplete
* is expected to be called before {@link SplitPreloader#wait(String)} returns, so this
* method is called there since onPostExecute does not run before get() returns.
*/
public void finish() {
try {
get();
} catch (Exception e) {
// Ignore exception, not a problem if preload fails.
}
if (mOnComplete != null) {
// Recreate the context here to make sure we have the latest version, in case there
// was a race to update the class loader cache, see b/172602571.
mOnComplete.run(createSplitContext());
mOnComplete = null;
}
}
private Context createSplitContext() {
if (BundleUtils.isIsolatedSplitInstalled(mContext, mName)) {
return BundleUtils.createIsolatedSplitContext(mContext, mName);
}
return mContext;
}
}
public SplitPreloader(Context context) {
mContext = context;
}
/** Starts preloading a split context on a background thread. */
public void preload(String name, OnComplete onComplete) {
if (!BundleUtils.isIsolatedSplitInstalled(mContext, name)) {
if (onComplete != null) {
onComplete.run(mContext);
}
return;
}
assert !mPreloadTasks.containsKey(name);
PreloadTask task = new PreloadTask(name, onComplete);
task.executeWithTaskTraits(TaskTraits.USER_BLOCKING_MAY_BLOCK);
mPreloadTasks.put(name, task);
}
/** Waits for the specified split to be finished loading. */
public void wait(String name) {
try (TraceEvent te = TraceEvent.scoped("SplitPreloader.wait")) {
PreloadTask task = mPreloadTasks.remove(name);
if (task != null) {
long startTime = SystemClock.uptimeMillis();
// Make sure the task is finished and onComplete has run.
task.finish();
RecordHistogram.recordTimesHistogram(
"Android.IsolatedSplits.PreloadWaitTime." + name,
SystemClock.uptimeMillis() - startTime);
}
}
}
}