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 { ...@@ -49,12 +49,21 @@ public class ModuleLoader {
/** Specifies the module package name and entry point class name. */ /** Specifies the module package name and entry point class name. */
private final ComponentName mComponentName; private final ComponentName mComponentName;
@Nullable
private final String mDexAssetName; private final String mDexAssetName;
private final DexInputStreamProvider mDexInputStreamProvider; private final DexInputStreamProvider mDexInputStreamProvider;
private final DexClassLoaderProvider mDexClassLoaderProvider; private final DexClassLoaderProvider mDexClassLoaderProvider;
private final long mModuleLastUpdateTime; private final long mModuleLastUpdateTime;
private final String mModuleId; 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 * 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. * the time of destruction depends on the caching policy.
...@@ -122,6 +131,9 @@ public class ModuleLoader { ...@@ -122,6 +131,9 @@ public class ModuleLoader {
} }
mModuleLastUpdateTime = lastUpdateTime; mModuleLastUpdateTime = lastUpdateTime;
mModuleId = String.format("%s v%s (%s)", packageName, versionCode, versionName); mModuleId = String.format("%s v%s (%s)", packageName, versionCode, versionName);
mModuleContext = createModuleContext(
mComponentName.getPackageName(), /* resourcesOnly = */ mDexAssetName != null);
} }
public ComponentName getComponentName() { public ComponentName getComponentName() {
...@@ -137,6 +149,12 @@ public class ModuleLoader { ...@@ -137,6 +149,12 @@ public class ModuleLoader {
* If the module is not loaded yet, dynamically loads the module entry point class. * If the module is not loaded yet, dynamically loads the module entry point class.
*/ */
public void loadModule() { public void loadModule() {
if (mClassLoader == null) {
mNeedsToLoadModule = true;
if (!mIsClassLoaderCreating) createClassLoader();
return;
}
if (mIsModuleLoading) return; if (mIsModuleLoading) return;
// If module has been already loaded all callbacks must be notified synchronously. // If module has been already loaded all callbacks must be notified synchronously.
...@@ -146,9 +164,7 @@ public class ModuleLoader { ...@@ -146,9 +164,7 @@ public class ModuleLoader {
return; return;
} }
Context moduleContext = createModuleContext( if (mModuleContext == null) {
mComponentName.getPackageName(), /* resourcesOnly = */ mDexAssetName != null);
if (moduleContext == null) {
runAndClearCallbacks(); runAndClearCallbacks();
return; return;
} }
...@@ -156,9 +172,16 @@ public class ModuleLoader { ...@@ -156,9 +172,16 @@ public class ModuleLoader {
ModuleMetrics.registerLifecycleState(ModuleMetrics.LifecycleState.NOT_LOADED); ModuleMetrics.registerLifecycleState(ModuleMetrics.LifecycleState.NOT_LOADED);
mIsModuleLoading = true; mIsModuleLoading = true;
new LoadClassTask(moduleContext) new LoadClassTask().executeWithTaskTraits(
.executeWithTaskTraits( new TaskTraits().taskPriority(TaskPriority.USER_VISIBLE).mayBlock(true));
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 { ...@@ -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. */ /** Buffer size to use while copying an input stream into the disk. */
private static final int BUFFER_SIZE = 16 * 1024; 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 @Override
@Nullable @Nullable
protected Class<?> doInBackground() { protected ClassLoader doInBackground() {
try { try {
boolean loadFromDex = updateModuleDexInDiskIfNeeded(); boolean loadFromDex = updateModuleDexInDiskIfNeeded();
long entryPointLoadClassStartTime = ModuleMetrics.now(); mClassLoader = getModuleClassLoader(loadFromDex);
Class<?> clazz = return mClassLoader;
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);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Could not copy dex to local storage", e); Log.e(TAG, "Could not copy dex to local storage", e);
ModuleMetrics.recordLoadResult( ModuleMetrics.recordLoadResult(
...@@ -348,6 +355,15 @@ public class ModuleLoader { ...@@ -348,6 +355,15 @@ public class ModuleLoader {
return null; 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. * Updates the local copy of the module dex file in disk if necessary.
* *
...@@ -400,6 +416,27 @@ public class ModuleLoader { ...@@ -400,6 +416,27 @@ public class ModuleLoader {
} }
return mDexClassLoaderProvider.createClassLoader(getDexFile()); 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 @Override
protected void onPostExecute(@Nullable Class<?> clazz) { protected void onPostExecute(@Nullable Class<?> clazz) {
......
...@@ -13,7 +13,7 @@ import static org.junit.Assert.assertTrue; ...@@ -13,7 +13,7 @@ import static org.junit.Assert.assertTrue;
import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking; 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_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_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; import android.support.test.filters.SmallTest;
...@@ -54,12 +54,11 @@ public class CustomTabsDynamicModuleLoaderTest { ...@@ -54,12 +54,11 @@ public class CustomTabsDynamicModuleLoaderTest {
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
mDexInputStreamProvider = new FakeDexInputStreamProvider(); mDexInputStreamProvider = new FakeDexInputStreamProvider();
mModuleLoaderFromApk = new ModuleLoader(FAKE_MODULE_COMPONENT_NAME, mModuleLoaderFromApk = new ModuleLoader(FAKE_MODULE_COMPONENT_NAME,
/* dexAssetName = */ null, /* dexAssetName = */ null, mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
mDexInputStreamProvider, FAKE_CLASS_LOADER_PROVIDER);
mModuleLoaderFromDex = new ModuleLoader(FAKE_MODULE_COMPONENT_NAME, 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, 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 @After
......
...@@ -50,7 +50,7 @@ public class CustomTabsDynamicModuleTestUtils { ...@@ -50,7 +50,7 @@ public class CustomTabsDynamicModuleTestUtils {
/** /**
* A asset name used to load {@link #FAKE_MODULE_DEX}. * 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. * A fake "dex file" that consists of couple of bytes.
...@@ -308,7 +308,7 @@ public class CustomTabsDynamicModuleTestUtils { ...@@ -308,7 +308,7 @@ public class CustomTabsDynamicModuleTestUtils {
@Override @Override
public InputStream createInputStream(@Nullable String dexAssetName, Context moduleContext) { 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); 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