From dea3fd8cf1000d786ff40e1036a8bf76a55d5fcd Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Mon, 17 Jul 2017 15:12:01 -0700 Subject: [PATCH 1/4] Refactor the arguments passed to dexopt invocations Wrap the arguments passed to the various performDexopt calls into the DexoptOptions object. This will make adding extra arguments (like compile only a split) much easier and avoid extending quite a few internal methods. (cherry picked from commit 1d0e83d2cee794ba576d573119e826905a4422cd) Bug: 38138251 Test: adb shell cmd package compile .... adb shell cmd package bg-dexopt-job ... install new apps and check that they compiled runtest -x services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java Merged-In: Ia9930edd2dceb7535d6168eceb8e3199c82b6306 Change-Id: Ia9930edd2dceb7535d6168eceb8e3199c82b6306 --- .../android/content/pm/IPackageManager.aidl | 8 - .../server/pm/BackgroundDexOptService.java | 29 ++-- .../android/server/pm/OtaDexoptService.java | 20 +-- .../server/pm/PackageDexOptimizer.java | 26 ++- .../server/pm/PackageManagerService.java | 161 ++++++++---------- .../com/android/server/pm/dex/DexManager.java | 26 +-- .../android/server/pm/dex/DexoptOptions.java | 113 ++++++++++++ .../server/pm/dex/DexoptOptionsTests.java | 116 +++++++++++++ 8 files changed, 346 insertions(+), 153 deletions(-) create mode 100644 services/core/java/com/android/server/pm/dex/DexoptOptions.java create mode 100644 services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index e5e7b779ff6ae..3fc95c6696224 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -507,14 +507,6 @@ interface IPackageManager { oneway void registerDexModule(in String packageName, in String dexModulePath, in boolean isSharedModule, IDexModuleRegisterCallback callback); - /** - * Ask the package manager to perform a dex-opt for the given reason. The package - * manager will map the reason to a compiler filter according to the current system - * configuration. - */ - boolean performDexOpt(String packageName, boolean checkProfiles, - int compileReason, boolean force, boolean bootComplete, boolean downgrade); - /** * Ask the package manager to perform a dex-opt with the given compiler filter. * diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 34092ad6607a5..914e81e64ef69 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -37,6 +37,7 @@ import android.util.Log; import com.android.server.pm.dex.DexManager; import com.android.server.LocalServices; import com.android.server.PinnerService; +import com.android.server.pm.dex.DexoptOptions; import java.io.File; import java.util.Set; @@ -217,12 +218,10 @@ public class BackgroundDexOptService extends JobService { // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a // trade-off worth doing to save boot time work. - int result = pm.performDexOptWithStatus(pkg, - /* checkProfiles */ false, + int result = pm.performDexOptWithStatus(new DexoptOptions( + pkg, PackageManagerService.REASON_BOOT, - /* force */ false, - /* bootComplete */ true, - /* downgrade */ false); + DexoptOptions.DEXOPT_BOOT_COMPLETE)); if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); } @@ -334,22 +333,22 @@ public class BackgroundDexOptService extends JobService { // Optimize package if needed. Note that there can be no race between // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. boolean success; + int dexoptFlags = + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0); if (is_for_primary_dex) { - int result = pm.performDexOptWithStatus(pkg, - /* checkProfiles */ true, - reason, - false /* forceCompile*/, - true /* bootComplete */, - downgrade); + int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + dexoptFlags)); success = result != PackageDexOptimizer.DEX_OPT_FAILED; if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) { updatedPackages.add(pkg); } } else { - success = pm.performDexOptSecondary(pkg, - reason, - false /* force */, - downgrade); + success = pm.performDexOpt(new DexoptOptions(pkg, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX)); } if (success) { // Dexopt succeeded, remove package from the list of failing ones. diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java index 4ff6cbf1e1c88..241d76f8a0ac3 100644 --- a/services/core/java/com/android/server/pm/OtaDexoptService.java +++ b/services/core/java/com/android/server/pm/OtaDexoptService.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; -import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; import android.annotation.Nullable; import android.content.Context; @@ -35,6 +34,7 @@ import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.server.pm.Installer.InstallerException; +import com.android.server.pm.dex.DexoptOptions; import java.io.File; import java.io.FileDescriptor; @@ -314,19 +314,19 @@ public class OtaDexoptService extends IOtaDexopt.Stub { libraryDependencies = NO_LIBRARIES; } + optimizer.performDexOpt(pkg, libraryDependencies, - null /* ISAs */, false /* checkProfiles */, - getCompilerFilterForReason(compilationReason), + null /* ISAs */, null /* CompilerStats.PackageStats */, mPackageManagerService.getDexManager().isUsedByOtherApps(pkg.packageName), - true /* bootComplete */, - false /* downgrade */); + new DexoptOptions(pkg.packageName, compilationReason, + DexoptOptions.DEXOPT_BOOT_COMPLETE)); + + mPackageManagerService.getDexManager().dexoptSecondaryDex( + new DexoptOptions(pkg.packageName, compilationReason, + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_BOOT_COMPLETE)); - mPackageManagerService.getDexManager().dexoptSecondaryDex(pkg.packageName, - getCompilerFilterForReason(compilationReason), - false /* force */, - false /* compileOnlySharedDex */, - false /* downgrade */); return commands; } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 644bab1fc2603..4064665076064 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -19,9 +19,7 @@ package com.android.server.pm; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; import android.content.pm.PackageParser; -import android.os.Environment; import android.os.FileUtils; import android.os.PowerManager; import android.os.SystemClock; @@ -35,6 +33,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.Installer.InstallerException; +import com.android.server.pm.dex.DexoptOptions; import java.io.File; import java.io.IOException; @@ -123,18 +122,16 @@ public class PackageDexOptimizer { * synchronized on {@link #mInstallLock}. */ int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries, - String[] instructionSets, boolean checkProfiles, String targetCompilationFilter, - CompilerStats.PackageStats packageStats, boolean isUsedByOtherApps, - boolean bootComplete, boolean downgrade) { + String[] instructionSets, CompilerStats.PackageStats packageStats, + boolean isUsedByOtherApps, DexoptOptions options) { if (!canOptimizePackage(pkg)) { return DEX_OPT_SKIPPED; } synchronized (mInstallLock) { final long acquireTime = acquireWakeLockLI(pkg.applicationInfo.uid); try { - return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles, - targetCompilationFilter, packageStats, isUsedByOtherApps, bootComplete, - downgrade); + return performDexOptLI(pkg, sharedLibraries, instructionSets, + packageStats, isUsedByOtherApps, options); } finally { releaseWakeLockLI(acquireTime); } @@ -147,9 +144,8 @@ public class PackageDexOptimizer { */ @GuardedBy("mInstallLock") private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries, - String[] targetInstructionSets, boolean checkForProfileUpdates, - String targetCompilerFilter, CompilerStats.PackageStats packageStats, - boolean isUsedByOtherApps, boolean bootComplete, boolean downgrade) { + String[] targetInstructionSets, CompilerStats.PackageStats packageStats, + boolean isUsedByOtherApps, DexoptOptions options) { final String[] instructionSets = targetInstructionSets != null ? targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo); final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets); @@ -157,13 +153,13 @@ public class PackageDexOptimizer { final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, - targetCompilerFilter, isUsedByOtherApps); - final boolean profileUpdated = checkForProfileUpdates && + options.getCompilerFilter(), isUsedByOtherApps); + final boolean profileUpdated = options.isCheckForProfileUpdates() && isProfileUpdated(pkg, sharedGid, compilerFilter); final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. - final int dexoptFlags = getDexFlags(pkg, compilerFilter, bootComplete); + final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete()); // Get the dependencies of each split in the package. For each code path in the package, // this array contains the relative paths of each split it depends on, separated by colons. String[] splitDependencies = getSplitDependencies(pkg); @@ -187,7 +183,7 @@ public class PackageDexOptimizer { for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated, sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats, - downgrade); + options.isDowngrade()); // The end result is: // - FAILED if any path failed, // - PERFORMED if at least one path needed compilation, diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6d501bb94299d..0b126a4d87d99 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -280,6 +280,7 @@ import com.android.server.pm.PermissionsState.PermissionState; import com.android.server.pm.Settings.DatabaseVersion; import com.android.server.pm.Settings.VersionInfo; import com.android.server.pm.dex.DexManager; +import com.android.server.pm.dex.DexoptOptions; import com.android.server.pm.dex.PackageDexUsage; import com.android.server.storage.DeviceStorageMonitorInternal; @@ -9394,22 +9395,23 @@ public class PackageManagerService extends IPackageManager.Stub // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a // trade-off worth doing to save boot time work. - int primaryDexOptStaus = performDexOptTraced(pkg.packageName, - false /* checkProfiles */, + int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0; + int primaryDexOptStaus = performDexOptTraced(new DexoptOptions( + pkg.packageName, compilerFilter, - false /* force */, - bootComplete, - false /* downgrade */); + dexoptFlags)); boolean secondaryDexOptStatus = true; if (pkg.isSystemApp()) { // Only dexopt shared secondary dex files belonging to system apps to not slow down // too much boot after an OTA. - secondaryDexOptStatus = mDexManager.dexoptSecondaryDex(pkg.packageName, + int secondaryDexoptFlags = dexoptFlags | + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_ONLY_SHARED_DEX; + mDexManager.dexoptSecondaryDex(new DexoptOptions( + pkg.packageName, compilerFilter, - false /* force */, - true /* compileOnlySharedDex */, - false /* downgrade */); + secondaryDexoptFlags)); } if (secondaryDexOptStatus) { @@ -9495,19 +9497,52 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * Ask the package manager to perform a dex-opt with the given compiler filter. + * + * Note: exposed only for the shell command to allow moving packages explicitly to a + * definite state. + */ @Override - public boolean performDexOpt(String packageName, - boolean checkProfiles, int compileReason, boolean force, boolean bootComplete, - boolean downgrade) { + public boolean performDexOptMode(String packageName, + boolean checkProfiles, String targetCompilerFilter, boolean force, + boolean bootComplete) { + int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) | + (force ? DexoptOptions.DEXOPT_FORCE : 0) | + (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0); + return performDexOpt(new DexoptOptions(packageName, targetCompilerFilter, flags)); + } + + /** + * Ask the package manager to perform a dex-opt with the given compiler filter on the + * secondary dex files belonging to the given package. + * + * Note: exposed only for the shell command to allow moving packages explicitly to a + * definite state. + */ + @Override + public boolean performDexOptSecondary(String packageName, String compilerFilter, + boolean force) { + int flags = DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + (force ? DexoptOptions.DEXOPT_FORCE : 0); + return performDexOpt(new DexoptOptions(packageName, compilerFilter, flags)); + } + + /*package*/ boolean performDexOpt(DexoptOptions options) { if (getInstantAppPackageName(Binder.getCallingUid()) != null) { return false; - } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { + } else if (isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) { return false; } - int dexoptStatus = performDexOptWithStatus( - packageName, checkProfiles, compileReason, force, bootComplete, - downgrade); - return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED; + + if (options.isDexoptOnlySecondaryDex()) { + return mDexManager.dexoptSecondaryDex(options); + } else { + int dexoptStatus = performDexOptWithStatus(options); + return dexoptStatus != PackageDexOptimizer.DEX_OPT_FAILED; + } } /** @@ -9516,34 +9551,14 @@ public class PackageManagerService extends IPackageManager.Stub * {@link PackageDexOptimizer#DEX_OPT_PERFORMED} * {@link PackageDexOptimizer#DEX_OPT_FAILED} */ - /* package */ int performDexOptWithStatus(String packageName, - boolean checkProfiles, int compileReason, boolean force, boolean bootComplete, - boolean downgrade) { - return performDexOptTraced(packageName, checkProfiles, - getCompilerFilterForReason(compileReason), force, bootComplete, downgrade); + /* package */ int performDexOptWithStatus(DexoptOptions options) { + return performDexOptTraced(options); } - @Override - public boolean performDexOptMode(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return false; - } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { - return false; - } - int dexOptStatus = performDexOptTraced(packageName, checkProfiles, - targetCompilerFilter, force, bootComplete, false /* downgrade */); - return dexOptStatus != PackageDexOptimizer.DEX_OPT_FAILED; - } - - private int performDexOptTraced(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete, boolean downgrade) { + private int performDexOptTraced(DexoptOptions options) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); try { - return performDexOptInternal(packageName, checkProfiles, - targetCompilerFilter, force, bootComplete, downgrade); + return performDexOptInternal(options); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } @@ -9551,12 +9566,10 @@ public class PackageManagerService extends IPackageManager.Stub // Run dexopt on a given package. Returns true if dexopt did not fail, i.e. // if the package can now be considered up to date for the given filter. - private int performDexOptInternal(String packageName, - boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete, boolean downgrade) { + private int performDexOptInternal(DexoptOptions options) { PackageParser.Package p; synchronized (mPackages) { - p = mPackages.get(packageName); + p = mPackages.get(options.getPackageName()); if (p == null) { // Package could not be found. Report failure. return PackageDexOptimizer.DEX_OPT_FAILED; @@ -9567,8 +9580,7 @@ public class PackageManagerService extends IPackageManager.Stub long callingId = Binder.clearCallingIdentity(); try { synchronized (mInstallLock) { - return performDexOptInternalWithDependenciesLI(p, checkProfiles, - targetCompilerFilter, force, bootComplete, downgrade); + return performDexOptInternalWithDependenciesLI(p, options); } } finally { Binder.restoreCallingIdentity(callingId); @@ -9588,12 +9600,11 @@ public class PackageManagerService extends IPackageManager.Stub } private int performDexOptInternalWithDependenciesLI(PackageParser.Package p, - boolean checkProfiles, String targetCompilerFilter, - boolean force, boolean bootComplete, boolean downgrade) { + DexoptOptions options) { // Select the dex optimizer based on the force parameter. // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to // allocate an object here. - PackageDexOptimizer pdo = force + PackageDexOptimizer pdo = options.isForce() ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) : mPackageDexOptimizer; @@ -9610,37 +9621,14 @@ public class PackageManagerService extends IPackageManager.Stub for (PackageParser.Package depPackage : deps) { // TODO: Analyze and investigate if we (should) profile libraries. pdo.performDexOpt(depPackage, null /* sharedLibraries */, instructionSets, - false /* checkProfiles */, - targetCompilerFilter, getOrCreateCompilerPackageStats(depPackage), true /* isUsedByOtherApps */, - bootComplete, - downgrade); + options); } } - return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, checkProfiles, - targetCompilerFilter, getOrCreateCompilerPackageStats(p), - mDexManager.isUsedByOtherApps(p.packageName), bootComplete, downgrade); - } - - // Performs dexopt on the used secondary dex files belonging to the given package. - // Returns true if all dex files were process successfully (which could mean either dexopt or - // skip). Returns false if any of the files caused errors. - @Override - public boolean performDexOptSecondary(String packageName, String compilerFilter, - boolean force) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { - return false; - } else if (isInstantApp(packageName, UserHandle.getCallingUserId())) { - return false; - } - return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force, - false /* compileOnlySharedDex */, false /* downgrade */); - } - - public boolean performDexOptSecondary(String packageName, int compileReason, - boolean force, boolean downgrade) { - return mDexManager.dexoptSecondaryDex(packageName, compileReason, force, downgrade); + return pdo.performDexOpt(p, p.usesLibraryFiles, instructionSets, + getOrCreateCompilerPackageStats(p), + mDexManager.isUsedByOtherApps(p.packageName), options); } /** @@ -9816,11 +9804,11 @@ public class PackageManagerService extends IPackageManager.Stub // Whoever is calling forceDexOpt wants a compiled package. // Don't use profiles since that may cause compilation to be skipped. - final int res = performDexOptInternalWithDependenciesLI(pkg, - false /* checkProfiles */, getDefaultCompilerFilter(), - true /* force */, - true /* bootComplete */, - false /* downgrade */); + final int res = performDexOptInternalWithDependenciesLI( + pkg, + new DexoptOptions(packageName, + getDefaultCompilerFilter(), + DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE)); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) { @@ -18284,13 +18272,14 @@ public class PackageManagerService extends IPackageManager.Stub // method because `pkg` may not be in `mPackages` yet. // // Also, don't fail application installs if the dexopt step fails. + DexoptOptions dexoptOptions = new DexoptOptions(pkg.packageName, + REASON_INSTALL, + DexoptOptions.DEXOPT_BOOT_COMPLETE); mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles, - null /* instructionSets */, false /* checkProfiles */, - getCompilerFilterForReason(REASON_INSTALL), + null /* instructionSets */, getOrCreateCompilerPackageStats(pkg), mDexManager.isUsedByOtherApps(pkg.packageName), - true /* bootComplete */, - false /* downgrade */); + dexoptOptions); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 441994d8c590d..0d4df4d4c9b4a 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -300,33 +300,21 @@ public class DexManager { } /** - * Perform dexopt on the package {@code packageName} secondary dex files. + * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped * because they don't need to be compiled).. */ - public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force, - boolean downgrade) { - return dexoptSecondaryDex(packageName, - PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason), - force, /* compileOnlySharedDex */ false, downgrade); - } - - /** - * Perform dexopt on the package {@code packageName} secondary dex files. - * @return true if all secondary dex files were processed successfully (compiled or skipped - * because they don't need to be compiled).. - */ - public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force, - boolean compileOnlySharedDex, boolean downgrade) { + public boolean dexoptSecondaryDex(DexoptOptions options) { // Select the dex optimizer based on the force parameter. // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust // the necessary dexopt flags to make sure that compilation is not skipped. This avoid // passing the force flag through the multitude of layers. // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to // allocate an object here. - PackageDexOptimizer pdo = force + PackageDexOptimizer pdo = options.isForce() ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) : mPackageDexOptimizer; + String packageName = options.getPackageName(); PackageUseInfo useInfo = getPackageUseInfo(packageName); if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { if (DEBUG) { @@ -339,7 +327,7 @@ public class DexManager { for (Map.Entry entry : useInfo.getDexUseInfoMap().entrySet()) { String dexPath = entry.getKey(); DexUseInfo dexUseInfo = entry.getValue(); - if (compileOnlySharedDex && !dexUseInfo.isUsedByOtherApps()) { + if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) { continue; } PackageInfo pkg = null; @@ -361,8 +349,8 @@ public class DexManager { } int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, - dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps(), - downgrade); + dexUseInfo.getLoaderIsas(), options.getCompilerFilter(), + dexUseInfo.isUsedByOtherApps(), options.isDowngrade()); success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); } return success; diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java new file mode 100644 index 0000000000000..f6f261c36d5fa --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + +import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; + +/** + * Options used for dexopt invocations. + */ +public final class DexoptOptions { + // When set, the profiles will be checked for updates before calling dexopt. If + // the apps profiles didn't update in a meaningful way (decided by the compiler), dexopt + // will be skipped. + // Currently this only affects the optimization of primary apks. Secondary dex files + // will always check the profiles for updates. + public static final int DEXOPT_CHECK_FOR_PROFILES_UPDATES = 1 << 0; + + // When set, dexopt will execute unconditionally (even if not needed). + public static final int DEXOPT_FORCE = 1 << 1; + + // Whether or not the invocation of dexopt is done after the boot is completed. This is used + // in order to adjust the priority of the compilation thread. + public static final int DEXOPT_BOOT_COMPLETE = 1 << 2; + + // When set, the dexopt invocation will optimize only the secondary dex files. If false, dexopt + // will only consider the primary apk. + public static final int DEXOPT_ONLY_SECONDARY_DEX = 1 << 3; + + // When set, dexopt will optimize only dex files that are used by other apps. + // Currently, this flag is ignored for primary apks. + public static final int DEXOPT_ONLY_SHARED_DEX = 1 << 4; + + // When set, dexopt will attempt to scale down the optimizations previously applied in order + // save disk space. + public static final int DEXOPT_DOWNGRADE = 1 << 5; + + // The name of package to optimize. + private final String mPackageName; + + // The intended target compiler filter. Note that dexopt might adjust the filter before the + // execution based on factors like: vmSafeMode and packageUsedByOtherApps. + private final String mCompilerFilter; + + // The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags. + private final int mFlags; + + public DexoptOptions(String packageName, String compilerFilter, int flags) { + int validityMask = + DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DEXOPT_FORCE | + DEXOPT_BOOT_COMPLETE | + DEXOPT_ONLY_SECONDARY_DEX | + DEXOPT_ONLY_SHARED_DEX | + DEXOPT_DOWNGRADE; + if ((flags & (~validityMask)) != 0) { + throw new IllegalArgumentException("Invalid flags : " + Integer.toHexString(flags)); + } + + mPackageName = packageName; + mCompilerFilter = compilerFilter; + mFlags = flags; + } + + public DexoptOptions(String packageName, int compilerReason, int flags) { + this(packageName, getCompilerFilterForReason(compilerReason), flags); + } + + public String getPackageName() { + return mPackageName; + } + + public boolean isCheckForProfileUpdates() { + return (mFlags & DEXOPT_CHECK_FOR_PROFILES_UPDATES) != 0; + } + + public String getCompilerFilter() { + return mCompilerFilter; + } + + public boolean isForce() { + return (mFlags & DEXOPT_FORCE) != 0; + } + + public boolean isBootComplete() { + return (mFlags & DEXOPT_BOOT_COMPLETE) != 0; + } + + public boolean isDexoptOnlySecondaryDex() { + return (mFlags & DEXOPT_ONLY_SECONDARY_DEX) != 0; + } + + public boolean isDexoptOnlySharedDex() { + return (mFlags & DEXOPT_ONLY_SHARED_DEX) != 0; + } + + public boolean isDowngrade() { + return (mFlags & DEXOPT_DOWNGRADE) != 0; + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java new file mode 100644 index 0000000000000..2c684b96cc2a2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + + +import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.PackageManagerServiceCompilerMapping; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DexoptOptionsTests { + private final static String mPackageName = "test.android.com"; + private final static String mCompilerFilter = + PackageManagerServiceCompilerMapping.getDefaultCompilerFilter(); + + @Test + public void testCreateDexoptOptionsEmpty() { + DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, /*flags*/ 0); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertFalse(opt.isBootComplete()); + assertFalse(opt.isCheckForProfileUpdates()); + assertFalse(opt.isDexoptOnlySecondaryDex()); + assertFalse(opt.isDexoptOnlySharedDex()); + assertFalse(opt.isDowngrade()); + assertFalse(opt.isForce()); + } + + @Test + public void testCreateDexoptOptionsFull() { + int flags = + DexoptOptions.DEXOPT_FORCE | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES | + DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX | + DexoptOptions.DEXOPT_ONLY_SHARED_DEX | + DexoptOptions.DEXOPT_DOWNGRADE; + + DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertTrue(opt.isBootComplete()); + assertTrue(opt.isCheckForProfileUpdates()); + assertTrue(opt.isDexoptOnlySecondaryDex()); + assertTrue(opt.isDexoptOnlySharedDex()); + assertTrue(opt.isDowngrade()); + assertTrue(opt.isForce()); + } + + @Test + public void testCreateDexoptOptionsReason() { + int flags = + DexoptOptions.DEXOPT_FORCE | + DexoptOptions.DEXOPT_BOOT_COMPLETE | + DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES; + + int[] reasons = new int[] { + PackageManagerService.REASON_FIRST_BOOT, + PackageManagerService.REASON_BOOT, + PackageManagerService.REASON_INSTALL, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + PackageManagerService.REASON_AB_OTA, + PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE}; + + for (int reason : reasons) { + DexoptOptions opt = new DexoptOptions(mPackageName, reason, flags); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(getCompilerFilterForReason(reason), opt.getCompilerFilter()); + assertTrue(opt.isBootComplete()); + assertTrue(opt.isCheckForProfileUpdates()); + assertFalse(opt.isDexoptOnlySecondaryDex()); + assertFalse(opt.isDexoptOnlySharedDex()); + assertFalse(opt.isDowngrade()); + assertTrue(opt.isForce()); + } + } + + @Test + public void testCreateDexoptInvalid() { + boolean gotException = false; + try { + int invalidFlags = 999; + new DexoptOptions(mPackageName, mCompilerFilter, invalidFlags); + } catch (IllegalArgumentException ignore) { + gotException = true; + } + + assertTrue(gotException); + } +} \ No newline at end of file From caed6000be0ebd32b5eb0ffb09757b891ae5c2ed Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Mon, 17 Jul 2017 15:23:21 -0700 Subject: [PATCH 2/4] Add a command line option to optimize individual splits The new option is "--split SplitName" and applies to "adb shell cmd package compile" command. Usage example: adb shell cmd package compile -m speed --split split_feature_a.apk com.android.cts.classloadersplitapp (cherry picked from commit b6f844d28cc7a7bf2b477a904263fcbe9b26d2c4) Bug: 38138251 Test: adb install-multiple CtsClassloaderSplitApp.apk CtsClassloaderSplitAppFeatureA.apk CtsClassloaderSplitAppFeatureB.apk adb shell cmd package compile -m speed --split split_feature_a.apk com.android.cts.classloadersplitapp check the status of split_feature_a runtest -x services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java Merged-In: I579bb12fa6699f99cd3824f185bd9352fb8007c5 Change-Id: I579bb12fa6699f99cd3824f185bd9352fb8007c5 --- .../android/content/pm/IPackageManager.aidl | 2 +- .../server/pm/PackageDexOptimizer.java | 8 +++++++ .../server/pm/PackageManagerService.java | 5 ++-- .../server/pm/PackageManagerShellCommand.java | 19 +++++++++++++-- .../android/server/pm/dex/DexoptOptions.java | 23 +++++++++++++++---- .../server/pm/dex/DexoptOptionsTests.java | 20 ++++++++++++++++ 6 files changed, 68 insertions(+), 9 deletions(-) diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 3fc95c6696224..64d687e9d3dea 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -514,7 +514,7 @@ interface IPackageManager { * definite state. */ boolean performDexOptMode(String packageName, boolean checkProfiles, - String targetCompilerFilter, boolean force, boolean bootComplete); + String targetCompilerFilter, boolean force, boolean bootComplete, String splitName); /** * Ask the package manager to perform a dex-opt with the given compiler filter on the diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 4064665076064..b00b374fa1b19 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -173,6 +173,14 @@ public class PackageDexOptimizer { } // Append shared libraries with split dependencies for this split. String path = paths.get(i); + if (options.getSplitName() != null) { + // We are asked to compile only a specific split. Check that the current path is + // what we are looking for. + if (!options.getSplitName().equals(new File(path).getName())) { + continue; + } + } + String sharedLibrariesPathWithSplits; if (sharedLibrariesPath != null && splitDependencies[i] != null) { sharedLibrariesPathWithSplits = sharedLibrariesPath + ":" + splitDependencies[i]; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 0b126a4d87d99..6b8a415364871 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9506,11 +9506,12 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean performDexOptMode(String packageName, boolean checkProfiles, String targetCompilerFilter, boolean force, - boolean bootComplete) { + boolean bootComplete, String splitName) { int flags = (checkProfiles ? DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES : 0) | (force ? DexoptOptions.DEXOPT_FORCE : 0) | (bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0); - return performDexOpt(new DexoptOptions(packageName, targetCompilerFilter, flags)); + return performDexOpt(new DexoptOptions(packageName, targetCompilerFilter, + splitName, flags)); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 20d7b28c55e15..10ceba418f224 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -365,6 +365,7 @@ class PackageManagerShellCommand extends ShellCommand { String compilationReason = null; String checkProfilesRaw = null; boolean secondaryDex = false; + String split = null; String opt; while ((opt = getNextOption()) != null) { @@ -395,6 +396,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--secondary-dex": secondaryDex = true; break; + case "--split": + split = getNextArgRequired(); + break; default: pw.println("Error: Unknown option: " + opt); return 1; @@ -423,6 +427,16 @@ class PackageManagerShellCommand extends ShellCommand { return 1; } + if (allPackages && split != null) { + pw.println("-a cannot be specified together with --split"); + return 1; + } + + if (secondaryDex && split != null) { + pw.println("--secondary-dex cannot be specified together with --split"); + return 1; + } + String targetCompilerFilter; if (compilerFilter != null) { if (!DexFile.isValidCompilerFilter(compilerFilter)) { @@ -472,7 +486,7 @@ class PackageManagerShellCommand extends ShellCommand { targetCompilerFilter, forceCompilation) : mInterface.performDexOptMode(packageName, checkProfiles, targetCompilerFilter, forceCompilation, - true /* bootComplete */); + true /* bootComplete */, split); if (!result) { failedPackages.add(packageName); } @@ -1609,7 +1623,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" help"); pw.println(" Print this help text."); pw.println(""); - pw.println(" compile [-m MODE | -r REASON] [-f] [-c]"); + pw.println(" compile [-m MODE | -r REASON] [-f] [-c] [--split SPLIT_NAME]"); pw.println(" [--reset] [--check-prof (true | false)] (-a | TARGET-PACKAGE)"); pw.println(" Trigger compilation of TARGET-PACKAGE or all packages if \"-a\"."); pw.println(" Options:"); @@ -1635,6 +1649,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --reset: restore package to its post-install state"); pw.println(" --check-prof (true | false): look at profiles when doing dexopt?"); pw.println(" --secondary-dex: compile app secondary dex files"); + pw.println(" --split SPLIT: compile only the given split name"); pw.println(" bg-dexopt-job"); pw.println(" Execute the background optimizations immediately."); pw.println(" Note that the command only runs the background optimizer logic. It may"); diff --git a/services/core/java/com/android/server/pm/dex/DexoptOptions.java b/services/core/java/com/android/server/pm/dex/DexoptOptions.java index f6f261c36d5fa..f57cf5e2b9996 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptOptions.java +++ b/services/core/java/com/android/server/pm/dex/DexoptOptions.java @@ -18,6 +18,8 @@ package com.android.server.pm.dex; import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason; +import android.annotation.Nullable; + /** * Options used for dexopt invocations. */ @@ -58,7 +60,19 @@ public final class DexoptOptions { // The set of flags for the dexopt options. It's a mix of the DEXOPT_* flags. private final int mFlags; + // When not null, dexopt will optimize only the split identified by this name. + // It only applies for primary apk and it's always null if mOnlySecondaryDex is true. + private final String mSplitName; + public DexoptOptions(String packageName, String compilerFilter, int flags) { + this(packageName, compilerFilter, /*splitName*/ null, flags); + } + + public DexoptOptions(String packageName, int compilerReason, int flags) { + this(packageName, getCompilerFilterForReason(compilerReason), flags); + } + + public DexoptOptions(String packageName, String compilerFilter, String splitName, int flags) { int validityMask = DEXOPT_CHECK_FOR_PROFILES_UPDATES | DEXOPT_FORCE | @@ -73,10 +87,7 @@ public final class DexoptOptions { mPackageName = packageName; mCompilerFilter = compilerFilter; mFlags = flags; - } - - public DexoptOptions(String packageName, int compilerReason, int flags) { - this(packageName, getCompilerFilterForReason(compilerReason), flags); + mSplitName = splitName; } public String getPackageName() { @@ -110,4 +121,8 @@ public final class DexoptOptions { public boolean isDowngrade() { return (mFlags & DEXOPT_DOWNGRADE) != 0; } + + public String getSplitName() { + return mSplitName; + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java index 2c684b96cc2a2..1eb5552d533f5 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptOptionsTests.java @@ -38,12 +38,14 @@ public class DexoptOptionsTests { private final static String mPackageName = "test.android.com"; private final static String mCompilerFilter = PackageManagerServiceCompilerMapping.getDefaultCompilerFilter(); + private final static String mSplitName = "split-A.apk"; @Test public void testCreateDexoptOptionsEmpty() { DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, /*flags*/ 0); assertEquals(mPackageName, opt.getPackageName()); assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertEquals(null, opt.getSplitName()); assertFalse(opt.isBootComplete()); assertFalse(opt.isCheckForProfileUpdates()); assertFalse(opt.isDexoptOnlySecondaryDex()); @@ -65,6 +67,7 @@ public class DexoptOptionsTests { DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, flags); assertEquals(mPackageName, opt.getPackageName()); assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertEquals(null, opt.getSplitName()); assertTrue(opt.isBootComplete()); assertTrue(opt.isCheckForProfileUpdates()); assertTrue(opt.isDexoptOnlySecondaryDex()); @@ -92,6 +95,7 @@ public class DexoptOptionsTests { DexoptOptions opt = new DexoptOptions(mPackageName, reason, flags); assertEquals(mPackageName, opt.getPackageName()); assertEquals(getCompilerFilterForReason(reason), opt.getCompilerFilter()); + assertEquals(null, opt.getSplitName()); assertTrue(opt.isBootComplete()); assertTrue(opt.isCheckForProfileUpdates()); assertFalse(opt.isDexoptOnlySecondaryDex()); @@ -101,6 +105,22 @@ public class DexoptOptionsTests { } } + @Test + public void testCreateDexoptOptionsSplit() { + int flags = DexoptOptions.DEXOPT_FORCE | DexoptOptions.DEXOPT_BOOT_COMPLETE; + + DexoptOptions opt = new DexoptOptions(mPackageName, mCompilerFilter, mSplitName, flags); + assertEquals(mPackageName, opt.getPackageName()); + assertEquals(mCompilerFilter, opt.getCompilerFilter()); + assertEquals(mSplitName, opt.getSplitName()); + assertTrue(opt.isBootComplete()); + assertFalse(opt.isCheckForProfileUpdates()); + assertFalse(opt.isDexoptOnlySecondaryDex()); + assertFalse(opt.isDexoptOnlySharedDex()); + assertFalse(opt.isDowngrade()); + assertTrue(opt.isForce()); + } + @Test public void testCreateDexoptInvalid() { boolean gotException = false; From 0a267a884ed04d3baf77ed7e5f6467879d035d33 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Wed, 12 Jul 2017 18:52:49 -0700 Subject: [PATCH 3/4] Encode the entire class loader context for dex2oat Until now the split dependencies were passed as a flatten list to dex2oat. In the presence of DelegateLastClassLoaders this is no longer enough to ensure the correctness of the compilation. This CL encodes the split dependencies together with their declared class loader in a format accepted by dex2oat. (partially cherry picked from commit 19da1cf72e8c448743a8c27ac6d73424ae42c7ec) Bug: 38138251 Test: runtest -x services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java Merged-In: Iaabd5d8bd5e6d027a8de5a408777fd517063d9f1 Change-Id: Iaabd5d8bd5e6d027a8de5a408777fd517063d9f1 --- .../server/pm/PackageDexOptimizer.java | 105 +------- .../android/server/pm/dex/DexoptUtils.java | 230 ++++++++++++++++++ .../server/pm/dex/DexoptUtilsTest.java | 182 ++++++++++++++ 3 files changed, 422 insertions(+), 95 deletions(-) create mode 100644 services/core/java/com/android/server/pm/dex/DexoptUtils.java create mode 100644 services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index b00b374fa1b19..e53a08a8f3d4c 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -28,12 +28,12 @@ import android.os.UserHandle; import android.os.WorkSource; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexoptOptions; +import com.android.server.pm.dex.DexoptUtils; import java.io.File; import java.io.IOException; @@ -157,12 +157,14 @@ public class PackageDexOptimizer { final boolean profileUpdated = options.isCheckForProfileUpdates() && isProfileUpdated(pkg, sharedGid, compilerFilter); - final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags. final int dexoptFlags = getDexFlags(pkg, compilerFilter, options.isBootComplete()); - // Get the dependencies of each split in the package. For each code path in the package, - // this array contains the relative paths of each split it depends on, separated by colons. - String[] splitDependencies = getSplitDependencies(pkg); + + // Get the class loader context dependencies. + // For each code path in the package, this array contains the class loader context that + // needs to be passed to dexopt in order to ensure correct optimizations. + String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts( + pkg.applicationInfo, sharedLibraries); int result = DEX_OPT_SKIPPED; for (int i = 0; i < paths.size(); i++) { @@ -181,17 +183,10 @@ public class PackageDexOptimizer { } } - String sharedLibrariesPathWithSplits; - if (sharedLibrariesPath != null && splitDependencies[i] != null) { - sharedLibrariesPathWithSplits = sharedLibrariesPath + ":" + splitDependencies[i]; - } else { - sharedLibrariesPathWithSplits = - splitDependencies[i] != null ? splitDependencies[i] : sharedLibrariesPath; - } for (String dexCodeIsa : dexCodeInstructionSets) { - int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated, - sharedLibrariesPathWithSplits, dexoptFlags, sharedGid, packageStats, - options.isDowngrade()); + int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, + profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid, + packageStats, options.isDowngrade()); // The end result is: // - FAILED if any path failed, // - PERFORMED if at least one path needed compilation, @@ -459,86 +454,6 @@ public class PackageDexOptimizer { return adjustDexoptNeeded(dexoptNeeded); } - /** - * Computes the shared libraries path that should be passed to dexopt. - */ - private String getSharedLibrariesPath(String[] sharedLibraries) { - if (sharedLibraries == null || sharedLibraries.length == 0) { - return null; - } - StringBuilder sb = new StringBuilder(); - for (String lib : sharedLibraries) { - if (sb.length() != 0) { - sb.append(":"); - } - sb.append(lib); - } - return sb.toString(); - } - - /** - * Walks dependency tree and gathers the dependencies for each split in a split apk. - * The split paths are stored as relative paths, separated by colons. - */ - private String[] getSplitDependencies(PackageParser.Package pkg) { - // Convert all the code paths to relative paths. - String baseCodePath = new File(pkg.baseCodePath).getParent(); - List paths = pkg.getAllCodePaths(); - String[] splitDependencies = new String[paths.size()]; - for (int i = 0; i < paths.size(); i++) { - File pathFile = new File(paths.get(i)); - String fileName = pathFile.getName(); - paths.set(i, fileName); - - // 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); - } - } - - // If there are no other dependencies, fill in the implicit dependency on the base apk. - SparseArray dependencies = pkg.applicationInfo.splitDependencies; - if (dependencies == null) { - for (int i = 1; i < paths.size(); i++) { - splitDependencies[i] = paths.get(0); - } - return splitDependencies; - } - - // Fill in the dependencies, skipping the base apk which has no dependencies. - for (int i = 1; i < dependencies.size(); i++) { - getParentDependencies(dependencies.keyAt(i), paths, dependencies, splitDependencies); - } - - return splitDependencies; - } - - /** - * Recursive method to generate dependencies for a particular split. - * The index is a key from the package's splitDependencies. - */ - private String getParentDependencies(int index, List paths, - SparseArray dependencies, String[] splitDependencies) { - // The base apk is always first, and has no dependencies. - if (index == 0) { - return null; - } - // Return the result if we've computed the dependencies for this index already. - if (splitDependencies[index] != null) { - return splitDependencies[index]; - } - // Get the dependencies for the parent of this index and append its path to it. - int parent = dependencies.get(index)[0]; - String parentDependencies = - getParentDependencies(parent, paths, dependencies, splitDependencies); - String path = parentDependencies == null ? paths.get(parent) : - parentDependencies + ":" + paths.get(parent); - splitDependencies[index] = path; - return path; - } - /** * Checks if there is an update on the profile information of the {@code pkg}. * If the compiler filter is not profile guided the method returns false. diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java new file mode 100644 index 0000000000000..c750f6526c7dc --- /dev/null +++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + + +import android.content.pm.ApplicationInfo; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.File; + +public final class DexoptUtils { + private static final String TAG = "DexoptUtils"; + + private DexoptUtils() {} + + /** + * Creates the class loader context dependencies for each of the application code paths. + * The returned array contains the class loader contexts that needs to be passed to dexopt in + * order to ensure correct optimizations. + * + * A class loader context describes how the class loader chain should be built by dex2oat + * in order to ensure that classes are resolved during compilation as they would be resolved + * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is + * loaded in a different context (with a different set of class loaders or a different + * classpath), the compiled code will be rejected. + * + * Note that the class loader context only includes dependencies and not the code path itself. + * The contexts are created based on the application split dependency list and + * the provided shared libraries. + * + * All the code paths encoded in the context will be relative to the base directory. This + * enables stage compilation where compiler artifacts may be moved around. + * + * The result is indexed as follows: + * - 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()} + */ + public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) { + // The base class loader context contains only the shared library. + String sharedLibrariesClassPath = encodeClasspath(sharedLibraries); + String baseApkContextClassLoader = encodeClassLoader( + sharedLibrariesClassPath, "dalvik.system.PathClassLoader"); + + String[] splitCodePaths = info.getSplitCodePaths(); + + if (splitCodePaths == null) { + // The application has no splits. + return new String[] {baseApkContextClassLoader}; + } + + // The application has splits. Compute their class loader contexts. + + // 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"); + + // 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]; + classLoaderContexts[0] = baseApkContextClassLoader; + + SparseArray splitDependencies = info.splitDependencies; + + if (splitDependencies == null) { + // If there are no inter-split dependencies, populate the result with the implicit + // dependency on the base apk. + for (int i = 1; i < classLoaderContexts.length; i++) { + classLoaderContexts[i] = splitDependencyOnBase; + } + } else { + // In case of inter-split dependencies, we need to walk the dependency chain of each + // split. We do this recursively and store intermediate results in classLoaderContexts. + + // First, look at the split class loaders and cache their individual contexts (i.e. + // the class loader + the name of the split). This is an optimization to avoid + // re-computing them during the recursive call. + // The cache is stored in splitClassLoaderEncodingCache. The difference between this and + // 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, + "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); + } + } + 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]); + } + + return classLoaderContexts; + } + + /** + * Recursive method to generate the class loader context dependencies for the split with the + * given index. {@param classLoaderContexts} acts as an accumulator. Upton return + * {@code classLoaderContexts[index]} will contain the split dependency. + * During computation, the method may resolve the dependencies of other splits as it traverses + * the entire parent chain. The result will also be stored in {@param classLoaderContexts}. + * + * Note that {@code index 0} denotes the base apk and it is special handled. When the + * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}. + * {@code classLoaderContexts[0]} is not modified in this method. + * + * @param index the index of the split (Note that index 0 denotes the base apk) + * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits. + * It contains only the split class loader and not the the base. The split + * with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}. + * @param splitDependencies the dependencies for all splits. Note that in this array index 0 + * is the base and splits start from index 1. + * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits + * start at index 1. + * @param splitDependencyOnBase the encoding of the implicit split dependency on base. + */ + private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache, + SparseArray splitDependencies, String[] classLoaderContexts, + String splitDependencyOnBase) { + // If we hit the base apk return its custom dependency list which is + // sharedLibraries + base.apk + if (index == 0) { + return splitDependencyOnBase; + } + // Return the result if we've computed the splitDependencies for this index already. + if (classLoaderContexts[index] != null) { + return classLoaderContexts[index]; + } + // Get the splitDependencies for the parent of this index and append its path to it. + int parent = splitDependencies.get(index)[0]; + String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache, + splitDependencies, classLoaderContexts, splitDependencyOnBase); + + // The split context is: `parent context + parent dependencies context`. + String splitContext = (parent == 0) ? + parentDependencies : + encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies); + classLoaderContexts[index] = splitContext; + return splitContext; + } + + /** + * Encodes the shared libraries classpathElements in a format accepted by dexopt. + * NOTE: Keep this in sync with the dexopt expectations! Right now that is + * a list separated by ':'. + */ + private static String encodeClasspath(String[] classpathElements) { + if (classpathElements == null || classpathElements.length == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (String element : classpathElements) { + if (sb.length() != 0) { + sb.append(":"); + } + sb.append(element); + } + return sb.toString(); + } + + /** + * Adds an element to the encoding of an existing classpath. + * {@see PackageDexOptimizer.encodeClasspath(String[])} + */ + private static String encodeClasspath(String classpath, String newElement) { + return classpath.isEmpty() ? newElement : (classpath + ":" + newElement); + } + + /** + * Encodes a single class loader dependency starting from {@param path} and + * {@param classLoaderName}. + * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]" + * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader. + */ + private static String encodeClassLoader(String classpath, String classLoaderName) { + String classLoaderDexoptEncoding = classLoaderName; + if ("dalvik.system.PathClassLoader".equals(classLoaderName)) { + classLoaderDexoptEncoding = "PCL"; + } else { + Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName); + } + return classLoaderDexoptEncoding + "[" + classpath + "]"; + } + + /** + * Links to dependencies together in a format accepted by dexopt. + * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split + * dependencies {@see encodeClassLoader} separated by ';'. + */ + private static String encodeClassLoaderChain(String cl1, String cl2) { + return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java new file mode 100644 index 0000000000000..f1f176789148c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.pm.dex; + +import static org.junit.Assert.assertEquals; + +import android.content.pm.ApplicationInfo; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.SparseArray; + +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.PathClassLoader; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class DexoptUtilsTest { + private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); + private static final String DELEGATE_LAST_CLASS_LOADER_NAME = + DelegateLastClassLoader.class.getName(); + + private ApplicationInfo createMockApplicationInfo(String baseClassLoader, boolean addSplits, + boolean addSplitDependencies) { + ApplicationInfo ai = new ApplicationInfo(); + String codeDir = "/data/app/mock.android.com"; + ai.setBaseCodePath(codeDir + "/base.dex"); + + if (addSplits) { + ai.setSplitCodePaths(new String[]{ + codeDir + "/base-1.dex", + codeDir + "/base-2.dex", + codeDir + "/base-3.dex", + codeDir + "/base-4.dex", + codeDir + "/base-5.dex", + codeDir + "/base-6.dex"}); + + if (addSplitDependencies) { + ai.splitDependencies = new SparseArray<>(6 + 1); + ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency + ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2 + ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4 + ai.splitDependencies.put(3, new int[] {4}); // split 3 depends on 4 + ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base + ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base + ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 + } + } + return ai; + } + + @Test + public void testSplitChain() { + ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + + 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-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("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + } + + @Test + public void testSplitChainNoSplitDependencies() { + ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + + 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]); + } + + @Test + public void testSplitChainNoSharedLibraries() { + ApplicationInfo ai = createMockApplicationInfo( + DELEGATE_LAST_CLASS_LOADER_NAME, true, true); + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + + assertEquals(7, contexts.length); + assertEquals("PCL[]", contexts[0]); + assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[base.dex]", contexts[1]); + assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[3]); + assertEquals("PCL[];PCL[base.dex]", contexts[4]); + assertEquals("PCL[];PCL[base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[base.dex]", contexts[6]); + } + + @Test + public void testSplitChainWithNullPrimaryClassLoader() { + // A null classLoaderName should mean PathClassLoader. + ApplicationInfo ai = createMockApplicationInfo(null, true, true); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + + 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-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("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + } + + @Test + public void tesNoSplits() { + ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + + assertEquals(1, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + } + + @Test + public void tesNoSplitsNullClassLoaderName() { + ApplicationInfo ai = createMockApplicationInfo(null, false, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + + assertEquals(1, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + } + + @Test + public void tesNoSplitDelegateLast() { + ApplicationInfo ai = createMockApplicationInfo( + DELEGATE_LAST_CLASS_LOADER_NAME, false, false); + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + + assertEquals(1, contexts.length); + assertEquals("PCL[a.dex:b.dex]", contexts[0]); + } + + @Test + public void tesNoSplitsNoSharedLibraries() { + ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + + assertEquals(1, contexts.length); + assertEquals("PCL[]", contexts[0]); + } + + @Test + public void tesNoSplitDelegateLastNoSharedLibraries() { + ApplicationInfo ai = createMockApplicationInfo( + DELEGATE_LAST_CLASS_LOADER_NAME, false, false); + String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + + assertEquals(1, contexts.length); + assertEquals("PCL[]", contexts[0]); + } +} From e534b2c6f439e2d64d5b3a95b640766c56e5995c Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Fri, 14 Jul 2017 16:40:07 -0700 Subject: [PATCH 4/4] Fix splits class loader context for non dependant splits If the app doesn't request for splits to be loaded in isolation or does not declare inter-split dependencies, then all the splits are loaded in the base apk class loader (in the order of they are defined). Fix the class loader context passed to dex2oat to reflect the runtime loading logic. (cherry picked from commit 305aeea38f96f0b94ad4be5cb979dd9cf98cf7df) Bug: 38138251 Test: runtest -x services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java Merged-In: Ia0623d38883ae244fd16c0afb053fef016bf260a Change-Id: Ia0623d38883ae244fd16c0afb053fef016bf260a --- .../android/server/pm/dex/DexoptUtils.java | 89 ++++++++++++------- .../server/pm/dex/DexoptUtilsTest.java | 37 ++++++-- 2 files changed, 85 insertions(+), 41 deletions(-) diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java index c750f6526c7dc..abac52f7a8daa 100644 --- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java +++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java @@ -16,12 +16,12 @@ 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"; @@ -50,6 +50,11 @@ public final class 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. @@ -57,35 +62,36 @@ public final class DexoptUtils { 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 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 @@ -98,33 +104,27 @@ public final class DexoptUtils { // 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 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; @@ -227,4 +227,25 @@ public final class DexoptUtils { 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; + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index f1f176789148c..21b286e68adfc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -41,6 +41,7 @@ public class DexoptUtilsTest { 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[]{ @@ -73,7 +74,8 @@ public class DexoptUtilsTest { 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]); @@ -89,14 +91,35 @@ public class DexoptUtilsTest { 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(