Commit 171f31cb authored by Anna Malova's avatar Anna Malova Committed by Commit Bot

Dynamic Module: Separate creating of ClassLoader and module loading.

When loading a dynamic module from dex we need to create ClassLoader
in advance as part of warmup without loading a module.

See also: http://crrev.com/c/1479331

Bug: 924118
Change-Id: Ib7e7250eec8fe9c7f2ce27abe1c031f6dec8d270
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1506513
Commit-Queue: Anna Malova <amalova@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Reviewed-by: default avatarMichael van Ouwerkerk <mvanouwerkerk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638579}
parent 91640e56
......@@ -49,12 +49,21 @@ public class ModuleLoader {
/** Specifies the module package name and entry point class name. */
private final ComponentName mComponentName;
@Nullable
private final String mDexAssetName;
private final DexInputStreamProvider mDexInputStreamProvider;
private final DexClassLoaderProvider mDexClassLoaderProvider;
private final long mModuleLastUpdateTime;
private final String mModuleId;
/** @param moduleContext The context for the package to load the class from. */
private Context mModuleContext;
@Nullable
private ClassLoader mClassLoader;
private boolean mIsClassLoaderCreating;
private boolean mNeedsToLoadModule;
/**
* Tracks the number of usages of the module. If it is no longer used, it may be destroyed, but
* the time of destruction depends on the caching policy.
......@@ -122,6 +131,9 @@ public class ModuleLoader {
}
mModuleLastUpdateTime = lastUpdateTime;
mModuleId = String.format("%s v%s (%s)", packageName, versionCode, versionName);
mModuleContext = createModuleContext(
mComponentName.getPackageName(), /* resourcesOnly = */ mDexAssetName != null);
}
public ComponentName getComponentName() {
......@@ -137,6 +149,12 @@ public class ModuleLoader {
* If the module is not loaded yet, dynamically loads the module entry point class.
*/
public void loadModule() {
if (mClassLoader == null) {
mNeedsToLoadModule = true;
if (!mIsClassLoaderCreating) createClassLoader();
return;
}
if (mIsModuleLoading) return;
// If module has been already loaded all callbacks must be notified synchronously.
......@@ -146,9 +164,7 @@ public class ModuleLoader {
return;
}
Context moduleContext = createModuleContext(
mComponentName.getPackageName(), /* resourcesOnly = */ mDexAssetName != null);
if (moduleContext == null) {
if (mModuleContext == null) {
runAndClearCallbacks();
return;
}
......@@ -156,9 +172,16 @@ public class ModuleLoader {
ModuleMetrics.registerLifecycleState(ModuleMetrics.LifecycleState.NOT_LOADED);
mIsModuleLoading = true;
new LoadClassTask(moduleContext)
.executeWithTaskTraits(
new TaskTraits().taskPriority(TaskPriority.USER_VISIBLE).mayBlock(true));
new LoadClassTask().executeWithTaskTraits(
new TaskTraits().taskPriority(TaskPriority.USER_VISIBLE).mayBlock(true));
}
public void createClassLoader() {
if (mClassLoader != null) return;
mIsClassLoaderCreating = true;
new ClassLoaderTask().executeWithTaskTraits(
new TaskTraits().taskPriority(TaskPriority.USER_VISIBLE).mayBlock(true));
}
/**
......@@ -311,35 +334,19 @@ public class ModuleLoader {
}
/**
* A task for loading the module entry point class on a background thread.
* A task for creating module {@link ClassLoader}.
*/
private class LoadClassTask extends AsyncTask<Class<?>> {
private class ClassLoaderTask extends AsyncTask<ClassLoader> {
/** Buffer size to use while copying an input stream into the disk. */
private static final int BUFFER_SIZE = 16 * 1024;
private final Context mModuleContext;
/**
* Constructs the task.
* @param moduleContext The context for the package to load the class from.
*/
LoadClassTask(Context moduleContext) {
mModuleContext = moduleContext;
}
@Override
@Nullable
protected Class<?> doInBackground() {
protected ClassLoader doInBackground() {
try {
boolean loadFromDex = updateModuleDexInDiskIfNeeded();
long entryPointLoadClassStartTime = ModuleMetrics.now();
Class<?> clazz =
getModuleClassLoader(loadFromDex).loadClass(mComponentName.getClassName());
ModuleMetrics.recordLoadClassTime(entryPointLoadClassStartTime);
return clazz;
} catch (ClassNotFoundException e) {
Log.e(TAG, "Could not find class %s", mComponentName.getClassName(), e);
ModuleMetrics.recordLoadResult(ModuleMetrics.LoadResult.CLASS_NOT_FOUND_EXCEPTION);
mClassLoader = getModuleClassLoader(loadFromDex);
return mClassLoader;
} catch (IOException e) {
Log.e(TAG, "Could not copy dex to local storage", e);
ModuleMetrics.recordLoadResult(
......@@ -348,6 +355,15 @@ public class ModuleLoader {
return null;
}
@Override
protected void onPostExecute(ClassLoader classLoader) {
mIsClassLoaderCreating = false;
if (mNeedsToLoadModule) {
mNeedsToLoadModule = false;
loadModule();
}
}
/**
* Updates the local copy of the module dex file in disk if necessary.
*
......@@ -400,6 +416,27 @@ public class ModuleLoader {
}
return mDexClassLoaderProvider.createClassLoader(getDexFile());
}
}
/**
* A task for loading the module entry point class on a background thread.
*/
private class LoadClassTask extends AsyncTask<Class<?>> {
@Override
@Nullable
protected Class<?> doInBackground() {
if (mClassLoader == null) return null;
try {
long entryPointLoadClassStartTime = ModuleMetrics.now();
Class<?> clazz = mClassLoader.loadClass(mComponentName.getClassName());
ModuleMetrics.recordLoadClassTime(entryPointLoadClassStartTime);
return clazz;
} catch (ClassNotFoundException e) {
Log.e(TAG, "Could not find class %s", mComponentName.getClassName(), e);
ModuleMetrics.recordLoadResult(ModuleMetrics.LoadResult.CLASS_NOT_FOUND_EXCEPTION);
}
return null;
}
@Override
protected void onPostExecute(@Nullable Class<?> clazz) {
......
......@@ -13,7 +13,7 @@ import static org.junit.Assert.assertTrue;
import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
import static org.chromium.chrome.browser.customtabs.dynamicmodule.CustomTabsDynamicModuleTestUtils.FAKE_CLASS_LOADER_PROVIDER;
import static org.chromium.chrome.browser.customtabs.dynamicmodule.CustomTabsDynamicModuleTestUtils.FAKE_MODULE_COMPONENT_NAME;
import static org.chromium.chrome.browser.customtabs.dynamicmodule.CustomTabsDynamicModuleTestUtils.FAKE_MODULE_DEX_RESOURCE_ID;
import static org.chromium.chrome.browser.customtabs.dynamicmodule.CustomTabsDynamicModuleTestUtils.FAKE_MODULE_DEX_ASSET_NAME;
import android.support.test.filters.SmallTest;
......@@ -54,12 +54,11 @@ public class CustomTabsDynamicModuleLoaderTest {
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
mDexInputStreamProvider = new FakeDexInputStreamProvider();
mModuleLoaderFromApk = new ModuleLoader(FAKE_MODULE_COMPONENT_NAME,
/* dexAssetName = */ null,
mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
/* dexAssetName = */ null, mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
mModuleLoaderFromDex = new ModuleLoader(FAKE_MODULE_COMPONENT_NAME,
FAKE_MODULE_DEX_RESOURCE_ID, mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
FAKE_MODULE_DEX_ASSET_NAME, mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
mModuleLoaderFromDex2 = new ModuleLoader(FAKE_MODULE_COMPONENT_NAME,
FAKE_MODULE_DEX_RESOURCE_ID, mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
FAKE_MODULE_DEX_ASSET_NAME, mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
}
@After
......
......@@ -50,7 +50,7 @@ public class CustomTabsDynamicModuleTestUtils {
/**
* A asset name used to load {@link #FAKE_MODULE_DEX}.
*/
/* package */ final static String FAKE_MODULE_DEX_RESOURCE_ID = "42";
/* package */ final static String FAKE_MODULE_DEX_ASSET_NAME = "R.strings.forty_two";
/**
* A fake "dex file" that consists of couple of bytes.
......@@ -308,7 +308,7 @@ public class CustomTabsDynamicModuleTestUtils {
@Override
public InputStream createInputStream(@Nullable String dexAssetName, Context moduleContext) {
if (!FAKE_MODULE_DEX_RESOURCE_ID.equals(dexAssetName)) {
if (!FAKE_MODULE_DEX_ASSET_NAME.equals(dexAssetName)) {
throw new RuntimeException("Unknown resource ID: " + dexAssetName);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment