| // 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); |
| } |
| } |
| } |
| } |