diff options
| author | 2017-03-03 00:23:39 +0000 | |
|---|---|---|
| committer | 2017-03-03 00:23:39 +0000 | |
| commit | 97b4de24d26314bdd8d3bbb7da411278632451df (patch) | |
| tree | a1690b116f363d5a42dacaf06bced7dddf0c2860 | |
| parent | a6adb97aa063c298d6249b6ed3a000327340865d (diff) | |
| parent | 468be64cf2a7dac72f9803f6b1fb0fbed0a17fa6 (diff) | |
Compile secondary dex files in DexManager am: 7218363a92
am: 468be64cf2
Change-Id: Ie0ebc07e2cf4fd4892b9171e1823d63414eef194
8 files changed, 226 insertions, 20 deletions
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index c51945e4e27d..4ed84ab34561 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -482,6 +482,7 @@ interface IPackageManager { */ boolean performDexOpt(String packageName, boolean checkProfiles, int compileReason, boolean force); + /** * Ask the package manager to perform a dex-opt with the given compiler filter. * @@ -492,6 +493,16 @@ interface IPackageManager { String targetCompilerFilter, boolean force); /** + * 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. + */ + boolean performDexOptSecondary(String packageName, + String targetCompilerFilter, boolean force); + + /** * Ask the package manager to dump profiles associated with a package. */ void dumpProfiles(String packageName); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 04569c27071e..bc80374435ea 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -50,6 +50,14 @@ public class Installer extends SystemService { public static final int DEXOPT_BOOTCOMPLETE = 1 << 4; /** Hint that the dexopt type is profile-guided. */ public static final int DEXOPT_PROFILE_GUIDED = 1 << 5; + /** The compilation is for a secondary dex file. */ + public static final int DEXOPT_SECONDARY_DEX = 1 << 6; + /** Ignore the result of dexoptNeeded and force compilation. */ + public static final int DEXOPT_FORCE = 1 << 7; + /** Indicates that the dex file passed to dexopt in on CE storage. */ + public static final int DEXOPT_STORAGE_CE = 1 << 8; + /** Indicates that the dex file passed to dexopt in on DE storage. */ + public static final int DEXOPT_STORAGE_DE = 1 << 9; // NOTE: keep in sync with installd public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8; diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 8e201aca783f..db712aeba0cd 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -19,6 +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.PowerManager; @@ -35,6 +36,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Set; import dalvik.system.DexFile; @@ -43,6 +45,10 @@ import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE; import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED; import static com.android.server.pm.Installer.DEXOPT_PUBLIC; import static com.android.server.pm.Installer.DEXOPT_SAFEMODE; +import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX; +import static com.android.server.pm.Installer.DEXOPT_FORCE; +import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE; +import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; @@ -52,13 +58,13 @@ import static dalvik.system.DexFile.isProfileGuidedCompilerFilter; /** * Helper class for running dexopt command on packages. */ -class PackageDexOptimizer { +public class PackageDexOptimizer { private static final String TAG = "PackageManager.DexOptimizer"; static final String OAT_DIR_NAME = "oat"; // TODO b/19550105 Remove error codes and use exceptions - static final int DEX_OPT_SKIPPED = 0; - static final int DEX_OPT_PERFORMED = 1; - static final int DEX_OPT_FAILED = -1; + public static final int DEX_OPT_SKIPPED = 0; + public static final int DEX_OPT_PERFORMED = 1; + public static final int DEX_OPT_FAILED = -1; private final Installer mInstaller; private final Object mInstallLock; @@ -100,6 +106,9 @@ class PackageDexOptimizer { return DEX_OPT_SKIPPED; } synchronized (mInstallLock) { + // During boot the system doesn't need to instantiate and obtain a wake lock. + // PowerManager might not be ready, but that doesn't mean that we can't proceed with + // dexopt. final boolean useLock = mSystemReady; if (useLock) { mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid)); @@ -130,9 +139,11 @@ class PackageDexOptimizer { final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly(); final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid); - final String compilerFilter = getRealCompilerFilter(pkg, targetCompilerFilter); + final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo, + targetCompilerFilter, isUsedByOtherApps(pkg)); final boolean profileUpdated = checkForProfileUpdates && isProfileUpdated(pkg, sharedGid, compilerFilter); + // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code // paths (b/34169257). final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries); @@ -201,6 +212,79 @@ class PackageDexOptimizer { } /** + * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}. + * + * @return + * DEX_OPT_FAILED if there was any exception during dexopt + * DEX_OPT_PERFORMED if dexopt was performed successfully on the given path. + * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file + * didn't need an update. That's because at the moment we don't get more than success/failure + * from installd. + * + * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than + * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though + * that seems wasteful. + */ + public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas, + String compilerFilter, boolean isUsedByOtherApps) { + synchronized (mInstallLock) { + // During boot the system doesn't need to instantiate and obtain a wake lock. + // PowerManager might not be ready, but that doesn't mean that we can't proceed with + // dexopt. + final boolean useLock = mSystemReady; + if (useLock) { + mDexoptWakeLock.setWorkSource(new WorkSource(info.uid)); + mDexoptWakeLock.acquire(); + } + try { + return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter, + isUsedByOtherApps); + } finally { + if (useLock) { + mDexoptWakeLock.release(); + } + } + } + } + + @GuardedBy("mInstallLock") + private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas, + String compilerFilter, boolean isUsedByOtherApps) { + int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX; + // Check the app storage and add the appropriate flags. + if (info.dataDir.equals(info.deviceProtectedDataDir)) { + dexoptFlags |= DEXOPT_STORAGE_DE; + } else if (info.dataDir.equals(info.credentialProtectedDataDir)) { + dexoptFlags |= DEXOPT_STORAGE_CE; + } else { + Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); + return DEX_OPT_FAILED; + } + compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps); + Log.d(TAG, "Running dexopt on: " + path + + " pkg=" + info.packageName + " isa=" + isas + + " dexoptFlags=" + printDexoptFlags(dexoptFlags) + + " target-filter=" + compilerFilter); + + try { + for (String isa : isas) { + // Reuse the same dexopt path as for the primary apks. We don't need all the + // arguments as some (dexopNeeded and oatDir) will be computed by installd because + // system server cannot read untrusted app content. + // TODO(calin): maybe add a separate call. + mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0, + /*oatDir*/ null, dexoptFlags, + compilerFilter, info.volumeUuid, /*sharedLibrariesPath*/ null); + } + + return DEX_OPT_PERFORMED; + } catch (InstallerException e) { + Slog.w(TAG, "Failed to dexopt", e); + return DEX_OPT_FAILED; + } + } + + /** * Adjust the given dexopt-needed value. Can be overridden to influence the decision to * optimize or not (and in what way). */ @@ -246,8 +330,9 @@ class PackageDexOptimizer { * The target filter will be updated if the package code is used by other apps * or if it has the safe mode flag set. */ - private String getRealCompilerFilter(PackageParser.Package pkg, String targetCompilerFilter) { - int flags = pkg.applicationInfo.flags; + private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter, + boolean isUsedByOtherApps) { + int flags = info.flags; boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; if (vmSafeMode) { // For the compilation, it doesn't really matter what we return here because installd @@ -259,7 +344,7 @@ class PackageDexOptimizer { return getNonProfileGuidedCompilerFilter(targetCompilerFilter); } - if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps(pkg)) { + if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) { // If the dex files is used by other apps, we cannot use profile-guided compilation. return getNonProfileGuidedCompilerFilter(targetCompilerFilter); } @@ -272,12 +357,16 @@ class PackageDexOptimizer { * filter. */ private int getDexFlags(PackageParser.Package pkg, String compilerFilter) { - int flags = pkg.applicationInfo.flags; + return getDexFlags(pkg.applicationInfo, compilerFilter); + } + + private int getDexFlags(ApplicationInfo info, String compilerFilter) { + int flags = info.flags; boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0; boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; // Profile guide compiled oat files should not be public. boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter); - boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter; + boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter; int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0; int dexFlags = (isPublic ? DEXOPT_PUBLIC : 0) @@ -437,6 +526,19 @@ class PackageDexOptimizer { if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) { flagsList.add("safemode"); } + if ((flags & DEXOPT_SECONDARY_DEX) == DEXOPT_SECONDARY_DEX) { + flagsList.add("secondary"); + } + if ((flags & DEXOPT_FORCE) == DEXOPT_FORCE) { + flagsList.add("force"); + } + if ((flags & DEXOPT_STORAGE_CE) == DEXOPT_STORAGE_CE) { + flagsList.add("storage_ce"); + } + if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) { + flagsList.add("storage_de"); + } + return String.join(",", flagsList); } @@ -461,5 +563,12 @@ class PackageDexOptimizer { // TODO: The return value is wrong when patchoat is needed. return DexFile.DEX2OAT_FROM_SCRATCH; } + + @Override + protected int adjustDexoptFlags(int flags) { + // Add DEXOPT_FORCE flag to signal installd that it should force compilation + // and discard dexoptanalyzer result. + return flags | DEXOPT_FORCE; + } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 86a29c6813c2..3f0a651c0e4a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2133,7 +2133,7 @@ public class PackageManagerService extends IPackageManager.Stub { mInstaller = installer; mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context, "*dexopt*"); - mDexManager = new DexManager(); + mDexManager = new DexManager(this, mPackageDexOptimizer); mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper()); mOnPermissionChangeListeners = new OnPermissionChangeListeners( @@ -7540,6 +7540,15 @@ public class PackageManagerService extends IPackageManager.Stub { targetCompilerFilter, getOrCreateCompilerPackageStats(p)); } + // 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) { + return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force); + } + Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) { if (p.usesLibraries != null || p.usesOptionalLibraries != null) { ArrayList<PackageParser.Package> retValue = new ArrayList<>(); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java index 8a3f48efb504..9c9a6711c581 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java @@ -23,7 +23,7 @@ import dalvik.system.DexFile; /** * Manage (retrieve) mappings from compilation reason to compilation filter. */ -class PackageManagerServiceCompilerMapping { +public class PackageManagerServiceCompilerMapping { // Names for compilation reasons. static final String REASON_STRINGS[] = { "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "nsys-library", "shared-apk", diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 3bfa6b880315..38047e56a843 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -288,6 +288,7 @@ class PackageManagerShellCommand extends ShellCommand { String compilerFilter = null; String compilationReason = null; String checkProfilesRaw = null; + boolean secondaryDex = false; String opt; while ((opt = getNextOption()) != null) { @@ -315,6 +316,9 @@ class PackageManagerShellCommand extends ShellCommand { clearProfileData = true; compilationReason = "install"; break; + case "--secondary-dex": + secondaryDex = true; + break; default: pw.println("Error: Unknown option: " + opt); return 1; @@ -387,8 +391,11 @@ class PackageManagerShellCommand extends ShellCommand { mInterface.clearApplicationProfileData(packageName); } - boolean result = mInterface.performDexOptMode(packageName, - checkProfiles, targetCompilerFilter, forceCompilation); + boolean result = secondaryDex + ? mInterface.performDexOptSecondary(packageName, + targetCompilerFilter, forceCompilation) + : mInterface.performDexOptMode(packageName, + checkProfiles, targetCompilerFilter, forceCompilation); if (!result) { failedPackages.add(packageName); } @@ -1448,6 +1455,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: copmile app secondary dex files"); pw.println(" list features"); pw.println(" Prints all features of the system."); pw.println(" list instrumentation [-f] [TARGET-PACKAGE]"); 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 e8092131a74f..c5f1646d3c81 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -16,13 +16,16 @@ package com.android.server.pm.dex; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageParser; -import android.content.pm.ApplicationInfo; - +import android.os.RemoteException; import android.util.Slog; +import com.android.server.pm.PackageDexOptimizer; import com.android.server.pm.PackageManagerServiceUtils; +import com.android.server.pm.PackageManagerServiceCompilerMapping; import java.io.File; import java.io.IOException; @@ -32,6 +35,9 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; + /** * This class keeps track of how dex files are used. * Every time it gets a notification about a dex file being loaded it tracks @@ -54,15 +60,20 @@ public class DexManager { // encode and save the dex usage data. private final PackageDexUsage mPackageDexUsage; + private final IPackageManager mPackageManager; + private final PackageDexOptimizer mPackageDexOptimizer; + // Possible outcomes of a dex search. private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex - public DexManager() { + public DexManager(IPackageManager pms, PackageDexOptimizer pdo) { mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); + mPackageManager = pms; + mPackageDexOptimizer = pdo; } /** @@ -199,11 +210,62 @@ public class DexManager { * Get the package dex usage for the given package name. * @return the package data or null if there is no data available for this package. */ - public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) { + public PackageUseInfo getPackageUseInfo(String packageName) { return mPackageDexUsage.getPackageUseInfo(packageName); } /** + * 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) { + // 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 + ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) + : mPackageDexOptimizer; + PackageUseInfo useInfo = getPackageUseInfo(packageName); + if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "No secondary dex use for package:" + packageName); + } + // Nothing to compile, return true. + return true; + } + boolean success = true; + for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { + String dexPath = entry.getKey(); + DexUseInfo dexUseInfo = entry.getValue(); + PackageInfo pkg = null; + try { + pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, + dexUseInfo.getOwnerUserId()); + } catch (RemoteException e) { + throw new AssertionError(e); + } + // It may be that the package gets uninstalled while we try to compile its + // secondary dex files. If that's the case, just ignore. + // Note that we don't break the entire loop because the package might still be + // installed for other users. + if (pkg == null) { + Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName + + " for user " + dexUseInfo.getOwnerUserId()); + // Skip over it, another user might still have the package. + continue; + } + int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, + dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); + success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); + } + return success; + } + + /** * Retrieves the package which owns the given dexPath. */ private DexSearchResult getDexPackage( diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 2d07e651fdd1..4d76312a2b1c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -73,8 +73,7 @@ public class DexManagerTests { mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); - - mDexManager = new DexManager(); + mDexManager = new DexManager(null, null); // Foo and Bar are available to user0. // Only Bar is available to user1; |