From 2053168eb4506e2f8795afdbe9731c6451e1589c Mon Sep 17 00:00:00 2001 From: Narayan Kamath Date: Mon, 14 Jul 2014 13:18:43 +0100 Subject: [PATCH] Dexopt for Context.createPackageContext when code is included. The package manager now keeps track of per ISA dex-opt state. There are two important things to keep in mind here : - dexopt can potentially be very slow. In cases where the target package hasn't been dexopted yet, this can take multiple seconds and may cause an ANR in the caller if the context is being created from the main thread. - We will need to remove the constraint that dexopt can only be requested by the system (or root). Apps will implicitly be requesting dexopt by asking for package contexts with code included. It's important to note that unlike dalvik, the dexopt stage in ART isn't optional. ART cannot load classes directly from dex files. bug: 15313272 Change-Id: I0bd6c323a9c1f62f1c08f6292b7f0f7f08942726 --- core/java/android/app/LoadedApk.java | 17 +++++++ core/java/android/content/pm/IPackageManager.aidl | 12 +++-- core/java/android/content/pm/PackageParser.java | 2 +- .../android/server/am/ActivityManagerService.java | 2 +- .../android/server/pm/BackgroundDexOptService.java | 2 +- .../android/server/pm/PackageManagerService.java | 57 +++++++++++++--------- 6 files changed, 64 insertions(+), 28 deletions(-) diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 12e18ccfa6d4..38614a06cfab 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -56,6 +56,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; +import java.util.Objects; final class IntentReceiverLeaked extends AndroidRuntimeException { public IntentReceiverLeaked(String msg) { @@ -252,6 +253,22 @@ public final class LoadedApk { } if (mIncludeCode && !mPackageName.equals("android")) { + // Avoid the binder call when the package is the current application package. + // The activity manager will perform ensure that dexopt is performed before + // spinning up the process. + if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) { + final String isa = VMRuntime.getRuntime().vmInstructionSet(); + try { + // TODO: We can probably do away with the isa argument since + // the AM and PM have enough information to figure this out + // themselves. If we do need it, we should match it against the + // list of devices ISAs before sending it down to installd. + ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa); + } catch (RemoteException re) { + // Ignored. + } + } + final ArrayList zipPaths = new ArrayList<>(); final ArrayList libPaths = new ArrayList<>(); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 3a98f5d53e3c..eb46cf0452a5 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -384,10 +384,16 @@ interface IPackageManager { /** * Ask the package manager to perform dex-opt (if needed) on the given - * package, if it already hasn't done mode. Only does this if running - * in the special development "no pre-dexopt" mode. + * package and for the given instruction set if it already hasn't done + * so. + * + * If the supplied instructionSet is null, the package manager will use + * the packages default instruction set. + * + * In most cases, apps are dexopted in advance and this function will + * be a no-op. */ - boolean performDexOpt(String packageName); + boolean performDexOptIfNeeded(String packageName, String instructionSet); /** * Update status of external media on the package manager to scan and diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 43c2b15202d6..ab33d75fe438 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -4201,7 +4201,7 @@ public class PackageParser { public int mPreferredOrder = 0; // For use by package manager to keep track of where it needs to do dexopt. - public boolean mDexOptNeeded = true; + public final ArraySet mDexOptPerformed = new ArraySet<>(4); // For use by package manager to keep track of when a package was last used. public long mLastPackageUsageTimeInMills; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 79cb60e16d4a..1316da1afa12 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2809,7 +2809,7 @@ public final class ActivityManagerService extends ActivityManagerNative void ensurePackageDexOpt(String packageName) { IPackageManager pm = AppGlobals.getPackageManager(); try { - if (pm.performDexOpt(packageName)) { + if (pm.performDexOptIfNeeded(packageName, null /* instruction set */)) { mDidDexOpt = true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 1d53016f9856..355f34fe9e43 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -75,7 +75,7 @@ public class BackgroundDexOptService extends JobService { schedule(BackgroundDexOptService.this); return; } - pm.performDexOpt(pkg, false); + pm.performDexOpt(pkg, null /* instruction set */, false); } // ran to completion, so we abandon our timeslice and do not reschedule jobFinished(jobParams, false); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6513c88e56a2..0ad3a68b9ae1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -26,7 +26,6 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.content.pm.PackageManager.INSTALL_EXTERNAL; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER; -import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION; @@ -4542,24 +4541,29 @@ public class PackageManagerService extends IPackageManager.Stub { } PackageParser.Package p = pkg; synchronized (mInstallLock) { - if (p.mDexOptNeeded) { - performDexOptLI(p, false /* force dex */, false /* defer */, - true /* include dependencies */); - } + performDexOptLI(p, null /* instruction sets */, false /* force dex */, false /* defer */, + true /* include dependencies */); } } } } @Override - public boolean performDexOpt(String packageName) { - enforceSystemOrRoot("Only the system can request dexopt be performed"); - return performDexOpt(packageName, true); + public boolean performDexOptIfNeeded(String packageName, String instructionSet) { + return performDexOpt(packageName, instructionSet, true); } - public boolean performDexOpt(String packageName, boolean updateUsage) { + private static String getPrimaryInstructionSet(ApplicationInfo info) { + if (info.primaryCpuAbi == null) { + return getPreferredInstructionSet(); + } + + return VMRuntime.getInstructionSet(info.primaryCpuAbi); + } + public boolean performDexOpt(String packageName, String instructionSet, boolean updateUsage) { PackageParser.Package p; + final String targetInstructionSet; synchronized (mPackages) { p = mPackages.get(packageName); if (p == null) { @@ -4569,13 +4573,17 @@ public class PackageManagerService extends IPackageManager.Stub { p.mLastPackageUsageTimeInMills = System.currentTimeMillis(); } mPackageUsage.write(false); - if (!p.mDexOptNeeded) { + + targetInstructionSet = instructionSet != null ? instructionSet : + getPrimaryInstructionSet(p.applicationInfo); + if (p.mDexOptPerformed.contains(targetInstructionSet)) { return false; } } synchronized (mInstallLock) { - return performDexOptLI(p, false /* force dex */, false /* defer */, + final String[] instructionSets = new String[] { targetInstructionSet }; + return performDexOptLI(p, instructionSets, false /* force dex */, false /* defer */, true /* include dependencies */) == DEX_OPT_PERFORMED; } } @@ -4585,9 +4593,9 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { for (PackageParser.Package p : mPackages.values()) { if (DEBUG_DEXOPT) { - Log.i(TAG, p.packageName + " mDexOptNeeded=" + p.mDexOptNeeded); + Log.i(TAG, p.packageName + " mDexOptPerformed=" + p.mDexOptPerformed.toArray()); } - if (!p.mDexOptNeeded) { + if (!p.mDexOptPerformed.isEmpty()) { continue; } if (pkgs == null) { @@ -4655,6 +4663,10 @@ public class PackageManagerService extends IPackageManager.Stub { // 3.) we are skipping an unneeded dexopt for (String path : paths) { for (String instructionSet : instructionSets) { + if (!forceDex && pkg.mDexOptPerformed.contains(instructionSet)) { + continue; + } + try { final boolean isDexOptNeeded = DexFile.isDexOptNeededInternal(path, pkg.packageName, instructionSet, defer); @@ -4669,10 +4681,10 @@ public class PackageManagerService extends IPackageManager.Stub { // Don't bother running dexopt again if we failed, it will probably // just result in an error again. Also, don't bother dexopting for other // paths & ISAs. - pkg.mDexOptNeeded = false; return DEX_OPT_FAILED; } else { performedDexOpt = true; + pkg.mDexOptPerformed.add(instructionSet); } } @@ -4706,7 +4718,6 @@ public class PackageManagerService extends IPackageManager.Stub { // deferred dex-opt. We've either dex-opted one more paths or instruction sets or // we've skipped all of them because they are up to date. In both cases this // package doesn't need dexopt any longer. - pkg.mDexOptNeeded = false; return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } @@ -4762,8 +4773,8 @@ public class PackageManagerService extends IPackageManager.Stub { return allInstructionSets; } - private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer, - boolean inclDependencies) { + private int performDexOptLI(PackageParser.Package pkg, String[] instructionSets, + boolean forceDex, boolean defer, boolean inclDependencies) { HashSet done; if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { done = new HashSet(); @@ -4771,7 +4782,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { done = null; } - return performDexOptLI(pkg, null /* target instruction sets */, forceDex, defer, done); + return performDexOptLI(pkg, instructionSets, forceDex, defer, done); } private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) { @@ -5569,7 +5580,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((scanMode&SCAN_NO_DEX) == 0) { - if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false) + if (performDexOptLI(pkg, null /* instruction sets */, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) { if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) { removeDataDirsLI(pkg.packageName); @@ -5648,7 +5659,8 @@ public class PackageManagerService extends IPackageManager.Stub { if ((scanMode&SCAN_NO_DEX) == 0) { for (int i=0; i