package com.android.server.pm.dex;
-
import android.content.pm.ApplicationInfo;
import android.util.Slog;
import android.util.SparseArray;
import java.io.File;
+import java.util.List;
public final class DexoptUtils {
private static final String TAG = "DexoptUtils";
* - index 0 contains the context for the base apk
* - index 1 to n contain the context for the splits in the order determined by
* {@code info.getSplitCodePaths()}
+ *
+ * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
+ * and pay attention to the way the classpath is created for the non isolated mode in:
+ * {@link android.app.LoadedApk#makePaths(
+ * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
*/
public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
// The base class loader context contains only the shared library.
String baseApkContextClassLoader = encodeClassLoader(
sharedLibrariesClassPath, "dalvik.system.PathClassLoader");
- String[] splitCodePaths = info.getSplitCodePaths();
-
- if (splitCodePaths == null) {
+ if (info.getSplitCodePaths() == null) {
// The application has no splits.
return new String[] {baseApkContextClassLoader};
}
// The application has splits. Compute their class loader contexts.
+ // First, cache the relative paths of the splits and do some sanity checks
+ String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info);
+
// The splits have an implicit dependency on the base apk.
// This means that we have to add the base apk file in addition to the shared libraries.
String baseApkName = new File(info.getBaseCodePath()).getName();
- String splitDependencyOnBase = encodeClassLoader(
- encodeClasspath(sharedLibrariesClassPath, baseApkName),
- "dalvik.system.PathClassLoader");
+ String sharedLibrariesAndBaseClassPath =
+ encodeClasspath(sharedLibrariesClassPath, baseApkName);
// The result is stored in classLoaderContexts.
// Index 0 is the class loaded context for the base apk.
// Index `i` is the class loader context encoding for split `i`.
- String[] classLoaderContexts = new String[/*base apk*/ 1 + splitCodePaths.length];
+ String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
classLoaderContexts[0] = baseApkContextClassLoader;
- SparseArray<int[]> splitDependencies = info.splitDependencies;
-
- if (splitDependencies == null) {
- // If there are no inter-split dependencies, populate the result with the implicit
- // dependency on the base apk.
+ if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) {
+ // If the app didn't request for the splits to be loaded in isolation or if it does not
+ // declare inter-split dependencies, then all the splits will be loaded in the base
+ // apk class loader (in the order of their definition).
+ String classpath = sharedLibrariesAndBaseClassPath;
for (int i = 1; i < classLoaderContexts.length; i++) {
- classLoaderContexts[i] = splitDependencyOnBase;
+ classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
+ classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
}
} else {
// In case of inter-split dependencies, we need to walk the dependency chain of each
// classLoaderContexts is that the later contains the full chain of class loaders for
// a given split while splitClassLoaderEncodingCache only contains a single class loader
// encoding.
- String baseCodePath = new File(info.getBaseCodePath()).getParent();
- String[] splitClassLoaderEncodingCache = new String[splitCodePaths.length];
- for (int i = 0; i < splitCodePaths.length; i++) {
- File pathFile = new File(splitCodePaths[i]);
- String fileName = pathFile.getName();
- splitClassLoaderEncodingCache[i] = encodeClassLoader(fileName,
+ String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
+ for (int i = 0; i < splitRelativeCodePaths.length; i++) {
+ splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
"dalvik.system.PathClassLoader");
- // Sanity check that the base paths of the splits are all the same.
- String basePath = pathFile.getParent();
- if (!basePath.equals(baseCodePath)) {
- Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
- baseCodePath);
- }
}
+ String splitDependencyOnBase = encodeClassLoader(
+ sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader");
+ SparseArray<int[]> splitDependencies = info.splitDependencies;
for (int i = 1; i < splitDependencies.size(); i++) {
getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache,
splitDependencies, classLoaderContexts, splitDependencyOnBase);
}
- }
- // At this point classLoaderContexts contains only the parent dependencies.
- // We also need to add the class loader of the current split which should
- // come first in the context.
- for (int i = 1; i < classLoaderContexts.length; i++) {
- String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
- classLoaderContexts[i] = encodeClassLoaderChain(
- splitClassLoader, classLoaderContexts[i]);
+ // At this point classLoaderContexts contains only the parent dependencies.
+ // We also need to add the class loader of the current split which should
+ // come first in the context.
+ for (int i = 1; i < classLoaderContexts.length; i++) {
+ String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
+ classLoaderContexts[i] = encodeClassLoaderChain(
+ splitClassLoader, classLoaderContexts[i]);
+ }
}
return classLoaderContexts;
private static String encodeClassLoaderChain(String cl1, String cl2) {
return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2);
}
+
+ /**
+ * Returns the relative paths of the splits declared by the application {@code info}.
+ * Assumes that the application declares a non-null array of splits.
+ */
+ private static String[] getSplitRelativeCodePaths(ApplicationInfo info) {
+ String baseCodePath = new File(info.getBaseCodePath()).getParent();
+ String[] splitCodePaths = info.getSplitCodePaths();
+ String[] splitRelativeCodePaths = new String[splitCodePaths.length];
+ for (int i = 0; i < splitCodePaths.length; i++) {
+ File pathFile = new File(splitCodePaths[i]);
+ splitRelativeCodePaths[i] = pathFile.getName();
+ // Sanity check that the base paths of the splits are all the same.
+ String basePath = pathFile.getParent();
+ if (!basePath.equals(baseCodePath)) {
+ Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
+ baseCodePath);
+ }
+ }
+ return splitRelativeCodePaths;
+ }
}
ApplicationInfo ai = new ApplicationInfo();
String codeDir = "/data/app/mock.android.com";
ai.setBaseCodePath(codeDir + "/base.dex");
+ ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING;
if (addSplits) {
ai.setSplitCodePaths(new String[]{
assertEquals(7, contexts.length);
assertEquals("PCL[a.dex:b.dex]", contexts[0]);
- assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
+ assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]",
+ contexts[1]);
assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]);
assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
assertEquals(7, contexts.length);
assertEquals("PCL[a.dex:b.dex]", contexts[0]);
- assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[1]);
- assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[2]);
- assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[3]);
- assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]);
- assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]);
- assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[6]);
+ assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]);
+ assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]);
+ assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex]", contexts[3]);
+ assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
+ assertEquals(
+ "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]",
+ contexts[5]);
+ assertEquals(
+ "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
+ contexts[6]);
}
@Test
+ public void testSplitChainNoIsolationNoSharedLibrary() {
+ ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true);
+ ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING);
+ String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null);
+
+ assertEquals(7, contexts.length);
+ assertEquals("PCL[]", contexts[0]);
+ assertEquals("PCL[base.dex]", contexts[1]);
+ assertEquals("PCL[base.dex:base-1.dex]", contexts[2]);
+ assertEquals("PCL[base.dex:base-1.dex:base-2.dex]", contexts[3]);
+ assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
+ assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]);
+ assertEquals(
+ "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
+ contexts[6]);
+ }
+ @Test
public void testSplitChainNoSharedLibraries() {
ApplicationInfo ai = createMockApplicationInfo(
DELEGATE_LAST_CLASS_LOADER_NAME, true, true);