From c660475aafe91269be306c802823cf543005ce36 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Wed, 25 Jan 2017 17:08:03 -0800 Subject: [PATCH 1/4] A bit more refactoring in BackgroundDexOptService Extract convenient methods to help with: - adding a shell command to run the background dexopt job - extending idleOptimizations to cover secondary dex files Test: device boots, background job run. Bug: 32871170 Change-Id: I18fde62a1cb05c030a620722d5f217589beaefb1 --- .../server/pm/BackgroundDexOptService.java | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 601a2194e8f37..28151abe9a6b5 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -124,9 +124,9 @@ public class BackgroundDexOptService extends JobService { return (100 * level / scale); } - private long getLowStorageThreshold() { + private long getLowStorageThreshold(Context context) { @SuppressWarnings("deprecation") - final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir); + final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir); if (lowThreshold == 0) { Log.e(TAG, "Invalid low storage threshold"); } @@ -155,7 +155,7 @@ public class BackgroundDexOptService extends JobService { // Load low battery threshold from the system config. This is a 0-100 integer. final int lowBatteryThreshold = getResources().getInteger( com.android.internal.R.integer.config_lowBatteryWarningLevel); - final long lowThreshold = getLowStorageThreshold(); + final long lowThreshold = getLowStorageThreshold(this); mAbortPostBootUpdate.set(false); @@ -206,47 +206,48 @@ public class BackgroundDexOptService extends JobService { new Thread("BackgroundDexOptService_IdleOptimization") { @Override public void run() { - idleOptimization(jobParams, pm, pkgs); + idleOptimization(pm, pkgs, BackgroundDexOptService.this); + if (!mAbortIdleOptimization.get()) { + // If we didn't abort we ran to completion (or stopped because of space). + // Abandon our timeslice and do not reschedule. + jobFinished(jobParams, /* reschedule */ false); + } } }.start(); return true; } - private void idleOptimization(JobParameters jobParams, PackageManagerService pm, - ArraySet pkgs) { + // Optimize the given packages and return true if the process was not aborted. + // The abort can happen either because of job scheduler or because of lack of space. + private boolean idleOptimization(PackageManagerService pm, ArraySet pkgs, + Context context) { Log.i(TAG, "Performing idle optimizations"); // If post-boot update is still running, request that it exits early. mExitPostBootUpdate.set(true); - mAbortIdleOptimization.set(false); - final long lowThreshold = getLowStorageThreshold(); + long lowStorageThreshold = getLowStorageThreshold(context); + return optimizePackages(pm, pkgs, lowStorageThreshold); + } + + private boolean optimizePackages(PackageManagerService pm, ArraySet pkgs, + long lowStorageThreshold) { for (String pkg : pkgs) { - if (mAbortIdleOptimization.get()) { - // JobScheduler requested an early abort. - return; + if (abortIdleOptimizations(lowStorageThreshold)) { + return false; } synchronized (sFailedPackageNames) { if (sFailedPackageNames.contains(pkg)) { // Skip previously failing package continue; + } else { + // Conservatively add package to the list of failing ones in case performDexOpt + // never returns. + sFailedPackageNames.add(pkg); } } - long usableSpace = mDataDir.getUsableSpace(); - if (usableSpace < lowThreshold) { - // Rather bail than completely fill up the disk. - Log.w(TAG, "Aborting background dex opt job due to low storage: " + - usableSpace); - break; - } - - // Conservatively add package to the list of failing ones in case performDexOpt - // never returns. - synchronized (sFailedPackageNames) { - sFailedPackageNames.add(pkg); - } // Optimize package if needed. Note that there can be no race between // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. if (pm.performDexOpt(pkg, @@ -259,8 +260,24 @@ public class BackgroundDexOptService extends JobService { } } } - // Ran to completion, so we abandon our timeslice and do not reschedule. - jobFinished(jobParams, /* reschedule */ false); + return true; + } + + // Return true if the idle optimizations should be aborted because of a space constraints + // or because the JobScheduler requested so. + private boolean abortIdleOptimizations(long lowStorageThreshold) { + if (mAbortIdleOptimization.get()) { + // JobScheduler requested an early abort. + return true; + } + long usableSpace = mDataDir.getUsableSpace(); + if (usableSpace < lowStorageThreshold) { + // Rather bail than completely fill up the disk. + Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); + return true; + } + + return false; } @Override @@ -281,7 +298,7 @@ public class BackgroundDexOptService extends JobService { } final ArraySet pkgs = pm.getOptimizablePackages(); - if (pkgs == null || pkgs.isEmpty()) { + if (pkgs.isEmpty()) { if (DEBUG_DEXOPT) { Log.i(TAG, "No packages to optimize"); } From cb5f41ea11b1a6fcd0977a64ee146dde8f537076 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Wed, 25 Jan 2017 17:16:08 -0800 Subject: [PATCH 2/4] Add a shell command to force the background dexopt job Add 'bg-dexopt-job' command to 'adb shell cmd package' which will force the background dexopt job to run the optimizations immediately. Note that the command only runs the background optimizer logic. It may overlap with the actual job but the job scheduler will not be able to cancel it. It will also run even if the device is not in the idle maintenance mode. Test: adb shell cmd package bg-dexopt-job Bug: 32871170 Change-Id: I8b0112074220b2e09baf13d842401ee1cf306a89 --- core/java/android/content/pm/IPackageManager.aidl | 5 +++++ .../server/pm/BackgroundDexOptService.java | 11 +++++++++++ .../android/server/pm/PackageManagerService.java | 8 ++++++++ .../server/pm/PackageManagerShellCommand.java | 15 ++++++++++++++- 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 1ba747122fdc6..2467e5307b7eb 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -517,6 +517,11 @@ interface IPackageManager { void forceDexOpt(String packageName); + /** + * Execute the background dexopt job immediately. + */ + boolean runBackgroundDexoptJob(); + /** * Reconcile the information we have about the secondary dex files belonging to * {@code packagName} and the actual dex files. For all dex files that were diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 28151abe9a6b5..a2f29c9e48fa1 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -280,6 +280,17 @@ public class BackgroundDexOptService extends JobService { return false; } + /** + * Execute the idle optimizations immediately. + */ + public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) { + // Create a new object to make sure we don't interfere with the scheduled jobs. + // Note that this may still run at the same time with the job scheduled by the + // JobScheduler but the scheduler will not be able to cancel it. + BackgroundDexOptService bdos = new BackgroundDexOptService(); + return bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); + } + @Override public boolean onStartJob(JobParameters params) { if (DEBUG_DEXOPT) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f6bf2db0c5847..8f1a4cc2f4a29 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8230,6 +8230,14 @@ public class PackageManagerService extends IPackageManager.Stub { mDexManager.reconcileSecondaryDexFiles(packageName); } + /** + * Execute the background dexopt job immediately. + */ + @Override + public boolean runBackgroundDexoptJob() { + return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext); + } + List findSharedNonSystemLibraries(PackageParser.Package p) { if (p.usesLibraries != null || p.usesOptionalLibraries != null || p.usesStaticLibraries != null) { diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index a179a6dc3f49b..1203e4d58c004 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -120,6 +120,8 @@ class PackageManagerShellCommand extends ShellCommand { return runCompile(); case "reconcile-secondary-dex-files": return runreconcileSecondaryDexFiles(); + case "bg-dexopt-job": + return runDexoptJob(); case "dump-profiles": return runDumpProfiles(); case "list": @@ -449,6 +451,11 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runDexoptJob() throws RemoteException { + boolean result = mInterface.runBackgroundDexoptJob(); + return result ? 0 : -1; + } + private int runDumpProfiles() throws RemoteException { String packageName = getNextArg(); mInterface.dumpProfiles(packageName); @@ -1530,7 +1537,13 @@ 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(" --secondary-dex: compile app secondary dex files"); + 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"); + pw.println(" overlap with the actual job but the job scheduler will not be able to"); + pw.println(" cancel it. It will also run even if the device is not in the idle"); + pw.println(" maintenance mode."); pw.println(" list features"); pw.println(" Prints all features of the system."); pw.println(" list instrumentation [-f] [TARGET-PACKAGE]"); From 51f521c3bf46e6040f36757bc53ea57ddc7be85e Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Wed, 25 Jan 2017 18:00:05 -0800 Subject: [PATCH 3/4] Compile secondary dex files during background dexopt job Extend the backgound dexopt job to compile secondary dex files when "dalvik.vm.deopt.secondary" system property is set to true. Test: adb shell cmd package bg-dexopt-job Bug: 32871170 Change-Id: If3a4b77f785e92c7f2f7869e25ff9b99904f165a --- .../server/pm/BackgroundDexOptService.java | 124 +++++++++++++----- .../server/pm/PackageManagerService.java | 6 + .../com/android/server/pm/dex/DexManager.java | 7 + .../server/pm/dex/PackageDexUsage.java | 15 +++ 4 files changed, 116 insertions(+), 36 deletions(-) diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index a2f29c9e48fa1..66977d66e0a5a 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -30,10 +30,13 @@ import android.content.IntentFilter; import android.os.BatteryManager; import android.os.Environment; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.storage.StorageManager; import android.util.ArraySet; import android.util.Log; +import com.android.server.pm.dex.DexManager; + import java.io.File; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.TimeUnit; @@ -59,21 +62,33 @@ public class BackgroundDexOptService extends JobService { "android", BackgroundDexOptService.class.getName()); + // Possible return codes of individual optimization steps. + + // Optimizations finished. All packages were processed. + private static final int OPTIMIZE_PROCESSED = 0; + // Optimizations should continue. Issued after checking the scheduler, disk space or battery. + private static final int OPTIMIZE_CONTINUE = 1; + // Optimizations should be aborted. Job scheduler requested it. + private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2; + // Optimizations should be aborted. No space left on device. + private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3; + /** * Set of failed packages remembered across job runs. */ - static final ArraySet sFailedPackageNames = new ArraySet(); + static final ArraySet sFailedPackageNamesPrimary = new ArraySet(); + static final ArraySet sFailedPackageNamesSecondary = new ArraySet(); /** * Atomics set to true if the JobScheduler requests an abort. */ - final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); - final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); + private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); + private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); /** * Atomic set to true if one job should exit early because another job was started. */ - final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); + private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); private final File mDataDir = Environment.getDataDirectory(); @@ -104,8 +119,11 @@ public class BackgroundDexOptService extends JobService { // The idle maintanance job skips packages which previously failed to // compile. The given package has changed and may successfully compile // now. Remove it from the list of known failing packages. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(packageName); + synchronized (sFailedPackageNamesPrimary) { + sFailedPackageNamesPrimary.remove(packageName); + } + synchronized (sFailedPackageNamesSecondary) { + sFailedPackageNamesSecondary.remove(packageName); } } @@ -206,8 +224,9 @@ public class BackgroundDexOptService extends JobService { new Thread("BackgroundDexOptService_IdleOptimization") { @Override public void run() { - idleOptimization(pm, pkgs, BackgroundDexOptService.this); - if (!mAbortIdleOptimization.get()) { + int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this); + if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { + Log.w(TAG, "Idle optimizations aborted because of space constraints."); // If we didn't abort we ran to completion (or stopped because of space). // Abandon our timeslice and do not reschedule. jobFinished(jobParams, /* reschedule */ false); @@ -217,67 +236,99 @@ public class BackgroundDexOptService extends JobService { return true; } - // Optimize the given packages and return true if the process was not aborted. - // The abort can happen either because of job scheduler or because of lack of space. - private boolean idleOptimization(PackageManagerService pm, ArraySet pkgs, - Context context) { + // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). + private int idleOptimization(PackageManagerService pm, ArraySet pkgs, Context context) { Log.i(TAG, "Performing idle optimizations"); // If post-boot update is still running, request that it exits early. mExitPostBootUpdate.set(true); mAbortIdleOptimization.set(false); long lowStorageThreshold = getLowStorageThreshold(context); - return optimizePackages(pm, pkgs, lowStorageThreshold); - } + // Optimize primary apks. + int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true, + sFailedPackageNamesPrimary); - private boolean optimizePackages(PackageManagerService pm, ArraySet pkgs, - long lowStorageThreshold) { - for (String pkg : pkgs) { - if (abortIdleOptimizations(lowStorageThreshold)) { - return false; + if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { + return result; + } + + if (SystemProperties.getBoolean("dalvik.vm.deopt.secondary", false)) { + result = reconcileSecondaryDexFiles(pm.getDexManager()); + if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { + return result; } - synchronized (sFailedPackageNames) { - if (sFailedPackageNames.contains(pkg)) { + result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, + sFailedPackageNamesSecondary); + } + return result; + } + + private int optimizePackages(PackageManagerService pm, ArraySet pkgs, + long lowStorageThreshold, boolean is_for_primary_dex, + ArraySet failedPackageNames) { + for (String pkg : pkgs) { + int abort_code = abortIdleOptimizations(lowStorageThreshold); + if (abort_code != OPTIMIZE_CONTINUE) { + return abort_code; + } + + synchronized (failedPackageNames) { + if (failedPackageNames.contains(pkg)) { // Skip previously failing package continue; } else { // Conservatively add package to the list of failing ones in case performDexOpt // never returns. - sFailedPackageNames.add(pkg); + failedPackageNames.add(pkg); } } // Optimize package if needed. Note that there can be no race between // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. - if (pm.performDexOpt(pkg, - /* checkProfiles */ true, - PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false)) { + boolean success = is_for_primary_dex + ? pm.performDexOpt(pkg, + /* checkProfiles */ true, + PackageManagerService.REASON_BACKGROUND_DEXOPT, + /* force */ false) + : pm.performDexOptSecondary(pkg, + PackageManagerServiceCompilerMapping.getFullCompilerFilter(), + /* force */ true); + if (success) { // Dexopt succeeded, remove package from the list of failing ones. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(pkg); + synchronized (failedPackageNames) { + failedPackageNames.remove(pkg); } } } - return true; + return OPTIMIZE_PROCESSED; } - // Return true if the idle optimizations should be aborted because of a space constraints - // or because the JobScheduler requested so. - private boolean abortIdleOptimizations(long lowStorageThreshold) { + private int reconcileSecondaryDexFiles(DexManager dm) { + // TODO(calin): should we blacklist packages for which we fail to reconcile? + for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { + if (mAbortIdleOptimization.get()) { + return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; + } + dm.reconcileSecondaryDexFiles(p); + } + return OPTIMIZE_PROCESSED; + } + + // Evaluate whether or not idle optimizations should continue. + private int abortIdleOptimizations(long lowStorageThreshold) { if (mAbortIdleOptimization.get()) { // JobScheduler requested an early abort. - return true; + return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; } long usableSpace = mDataDir.getUsableSpace(); if (usableSpace < lowStorageThreshold) { // Rather bail than completely fill up the disk. Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); - return true; + return OPTIMIZE_ABORT_NO_SPACE_LEFT; } - return false; + return OPTIMIZE_CONTINUE; } /** @@ -288,7 +339,8 @@ public class BackgroundDexOptService extends JobService { // Note that this may still run at the same time with the job scheduled by the // JobScheduler but the scheduler will not be able to cancel it. BackgroundDexOptService bdos = new BackgroundDexOptService(); - return bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); + int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); + return result == OPTIMIZE_PROCESSED; } @Override diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8f1a4cc2f4a29..876c28b02e7aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8230,6 +8230,12 @@ public class PackageManagerService extends IPackageManager.Stub { mDexManager.reconcileSecondaryDexFiles(packageName); } + // TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject + // a reference there. + /*package*/ DexManager getDexManager() { + return mDexManager; + } + /** * Execute the background dexopt job immediately. */ 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 e978700340eb6..cc4b1c20d8b40 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -334,6 +334,13 @@ public class DexManager { } } + /** + * Return all packages that contain records of secondary dex files. + */ + public Set getAllPackagesWithSecondaryDexFiles() { + return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles(); + } + /** * Retrieves the package which owns the given dexPath. */ diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 253d0e97f51e4..26134163a222d 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -437,6 +437,21 @@ public class PackageDexUsage extends AbstractStatsBase { } } + /** + * Return all packages that contain records of secondary dex files. + */ + public Set getAllPackagesWithSecondaryDexFiles() { + Set packages = new HashSet<>(); + synchronized (mPackageUseInfoMap) { + for (Map.Entry entry : mPackageUseInfoMap.entrySet()) { + if (!entry.getValue().mDexUseInfoMap.isEmpty()) { + packages.add(entry.getKey()); + } + } + } + return packages; + } + public void clear() { synchronized (mPackageUseInfoMap) { mPackageUseInfoMap.clear(); From b1097411028103b6c88ce325af23d2ff1ec746c8 Mon Sep 17 00:00:00 2001 From: Calin Juravle Date: Thu, 26 Jan 2017 18:53:23 -0800 Subject: [PATCH 4/4] Save package dex usage info after secondary dex reconciliation Bug: 1833991 Test: add a bogus entry to /syste/data/package-dex-usage.list adb shell cmd package reconcile-secondary-dex-files com.android.google.gms wait a bit and check the bogus entry is gone Change-Id: Ic07126bfb8730933081a5e010e3c357d338786e8 --- .../java/com/android/server/pm/dex/DexManager.java | 14 +++++++++++--- .../com/android/server/pm/dex/PackageDexUsage.java | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) 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 cc4b1c20d8b40..964ed98806058 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -282,6 +282,7 @@ public class DexManager { return; } Set dexFilesToRemove = new HashSet<>(); + boolean updated = false; for (Map.Entry entry : useInfo.getDexUseInfoMap().entrySet()) { String dexPath = entry.getKey(); DexUseInfo dexUseInfo = entry.getValue(); @@ -302,7 +303,8 @@ public class DexManager { Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName + " for user " + dexUseInfo.getOwnerUserId()); // Update the usage and continue, another user might still have the package. - mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); + updated = mPackageDexUsage.removeUserPackage( + packageName, dexUseInfo.getOwnerUserId()) || updated; continue; } ApplicationInfo info = pkg.applicationInfo; @@ -313,7 +315,8 @@ public class DexManager { flags |= StorageManager.FLAG_STORAGE_CE; } else { Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName); - mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId()); + updated = mPackageDexUsage.removeUserPackage( + packageName, dexUseInfo.getOwnerUserId()) || updated; continue; } @@ -329,8 +332,13 @@ public class DexManager { } } if (!dexStillExists) { - mPackageDexUsage.removeDexFile(packageName, dexPath, dexUseInfo.getOwnerUserId()); + updated = mPackageDexUsage.removeDexFile( + packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated; } + + } + if (updated) { + mPackageDexUsage.maybeWriteAsync(); } } diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 26134163a222d..3693bce04eb10 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -378,6 +378,8 @@ public class PackageDexUsage extends AbstractStatsBase { /** * Remove all the records about package {@code packageName} belonging to user {@code userId}. + * @return true if the record was found and actually deleted, + * false if the record doesn't exist */ public boolean removeUserPackage(String packageName, int userId) { synchronized (mPackageUseInfoMap) { @@ -402,6 +404,8 @@ public class PackageDexUsage extends AbstractStatsBase { /** * Remove the secondary dex file record belonging to the package {@code packageName} * and user {@code userId}. + * @return true if the record was found and actually deleted, + * false if the record doesn't exist */ public boolean removeDexFile(String packageName, String dexFile, int userId) { synchronized (mPackageUseInfoMap) {