BackgroundDexOptService.java revision c480bdafdb984f024bb13a8f0de9b6e1efd34bcd
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.pm; 18 19import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; 20 21import android.app.AlarmManager; 22import android.app.job.JobInfo; 23import android.app.job.JobParameters; 24import android.app.job.JobScheduler; 25import android.app.job.JobService; 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.Intent; 29import android.content.IntentFilter; 30import android.os.BatteryManager; 31import android.os.Environment; 32import android.os.ServiceManager; 33import android.os.SystemProperties; 34import android.os.storage.StorageManager; 35import android.util.ArraySet; 36import android.util.Log; 37 38import com.android.server.pm.dex.DexManager; 39 40import java.io.File; 41import java.util.concurrent.atomic.AtomicBoolean; 42import java.util.concurrent.TimeUnit; 43 44/** 45 * {@hide} 46 */ 47public class BackgroundDexOptService extends JobService { 48 private static final String TAG = "BackgroundDexOptService"; 49 50 private static final boolean DEBUG = false; 51 52 private static final int JOB_IDLE_OPTIMIZE = 800; 53 private static final int JOB_POST_BOOT_UPDATE = 801; 54 55 private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG 56 ? TimeUnit.MINUTES.toMillis(1) 57 : TimeUnit.DAYS.toMillis(1); 58 59 private static ComponentName sDexoptServiceName = new ComponentName( 60 "android", 61 BackgroundDexOptService.class.getName()); 62 63 // Possible return codes of individual optimization steps. 64 65 // Optimizations finished. All packages were processed. 66 private static final int OPTIMIZE_PROCESSED = 0; 67 // Optimizations should continue. Issued after checking the scheduler, disk space or battery. 68 private static final int OPTIMIZE_CONTINUE = 1; 69 // Optimizations should be aborted. Job scheduler requested it. 70 private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2; 71 // Optimizations should be aborted. No space left on device. 72 private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3; 73 74 /** 75 * Set of failed packages remembered across job runs. 76 */ 77 static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>(); 78 static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>(); 79 80 /** 81 * Atomics set to true if the JobScheduler requests an abort. 82 */ 83 private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); 84 private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); 85 86 /** 87 * Atomic set to true if one job should exit early because another job was started. 88 */ 89 private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); 90 91 private final File mDataDir = Environment.getDataDirectory(); 92 93 public static void schedule(Context context) { 94 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 95 96 // Schedule a one-off job which scans installed packages and updates 97 // out-of-date oat files. 98 js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName) 99 .setMinimumLatency(TimeUnit.MINUTES.toMillis(1)) 100 .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1)) 101 .build()); 102 103 // Schedule a daily job which scans installed packages and compiles 104 // those with fresh profiling data. 105 js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName) 106 .setRequiresDeviceIdle(true) 107 .setRequiresCharging(true) 108 .setPeriodic(IDLE_OPTIMIZATION_PERIOD) 109 .build()); 110 111 if (DEBUG_DEXOPT) { 112 Log.i(TAG, "Jobs scheduled"); 113 } 114 } 115 116 public static void notifyPackageChanged(String packageName) { 117 // The idle maintanance job skips packages which previously failed to 118 // compile. The given package has changed and may successfully compile 119 // now. Remove it from the list of known failing packages. 120 synchronized (sFailedPackageNamesPrimary) { 121 sFailedPackageNamesPrimary.remove(packageName); 122 } 123 synchronized (sFailedPackageNamesSecondary) { 124 sFailedPackageNamesSecondary.remove(packageName); 125 } 126 } 127 128 // Returns the current battery level as a 0-100 integer. 129 private int getBatteryLevel() { 130 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 131 Intent intent = registerReceiver(null, filter); 132 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 133 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 134 135 if (level < 0 || scale <= 0) { 136 // Battery data unavailable. This should never happen, so assume the worst. 137 return 0; 138 } 139 140 return (100 * level / scale); 141 } 142 143 private long getLowStorageThreshold(Context context) { 144 @SuppressWarnings("deprecation") 145 final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir); 146 if (lowThreshold == 0) { 147 Log.e(TAG, "Invalid low storage threshold"); 148 } 149 150 return lowThreshold; 151 } 152 153 private boolean runPostBootUpdate(final JobParameters jobParams, 154 final PackageManagerService pm, final ArraySet<String> pkgs) { 155 if (mExitPostBootUpdate.get()) { 156 // This job has already been superseded. Do not start it. 157 return false; 158 } 159 new Thread("BackgroundDexOptService_PostBootUpdate") { 160 @Override 161 public void run() { 162 postBootUpdate(jobParams, pm, pkgs); 163 } 164 165 }.start(); 166 return true; 167 } 168 169 private void postBootUpdate(JobParameters jobParams, PackageManagerService pm, 170 ArraySet<String> pkgs) { 171 // Load low battery threshold from the system config. This is a 0-100 integer. 172 final int lowBatteryThreshold = getResources().getInteger( 173 com.android.internal.R.integer.config_lowBatteryWarningLevel); 174 final long lowThreshold = getLowStorageThreshold(this); 175 176 mAbortPostBootUpdate.set(false); 177 178 for (String pkg : pkgs) { 179 if (mAbortPostBootUpdate.get()) { 180 // JobScheduler requested an early abort. 181 return; 182 } 183 if (mExitPostBootUpdate.get()) { 184 // Different job, which supersedes this one, is running. 185 break; 186 } 187 if (getBatteryLevel() < lowBatteryThreshold) { 188 // Rather bail than completely drain the battery. 189 break; 190 } 191 long usableSpace = mDataDir.getUsableSpace(); 192 if (usableSpace < lowThreshold) { 193 // Rather bail than completely fill up the disk. 194 Log.w(TAG, "Aborting background dex opt job due to low storage: " + 195 usableSpace); 196 break; 197 } 198 199 if (DEBUG_DEXOPT) { 200 Log.i(TAG, "Updating package " + pkg); 201 } 202 203 // Update package if needed. Note that there can be no race between concurrent 204 // jobs because PackageDexOptimizer.performDexOpt is synchronized. 205 206 // checkProfiles is false to avoid merging profiles during boot which 207 // might interfere with background compilation (b/28612421). 208 // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will 209 // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a 210 // trade-off worth doing to save boot time work. 211 pm.performDexOpt(pkg, 212 /* checkProfiles */ false, 213 PackageManagerService.REASON_BOOT, 214 /* force */ false); 215 } 216 // Ran to completion, so we abandon our timeslice and do not reschedule. 217 jobFinished(jobParams, /* reschedule */ false); 218 } 219 220 private boolean runIdleOptimization(final JobParameters jobParams, 221 final PackageManagerService pm, final ArraySet<String> pkgs) { 222 new Thread("BackgroundDexOptService_IdleOptimization") { 223 @Override 224 public void run() { 225 int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this); 226 if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 227 Log.w(TAG, "Idle optimizations aborted because of space constraints."); 228 // If we didn't abort we ran to completion (or stopped because of space). 229 // Abandon our timeslice and do not reschedule. 230 jobFinished(jobParams, /* reschedule */ false); 231 } 232 } 233 }.start(); 234 return true; 235 } 236 237 // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes). 238 private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context) { 239 Log.i(TAG, "Performing idle optimizations"); 240 // If post-boot update is still running, request that it exits early. 241 mExitPostBootUpdate.set(true); 242 mAbortIdleOptimization.set(false); 243 244 long lowStorageThreshold = getLowStorageThreshold(context); 245 // Optimize primary apks. 246 int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true, 247 sFailedPackageNamesPrimary); 248 249 if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 250 return result; 251 } 252 253 if (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { 254 result = reconcileSecondaryDexFiles(pm.getDexManager()); 255 if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) { 256 return result; 257 } 258 259 result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false, 260 sFailedPackageNamesSecondary); 261 } 262 return result; 263 } 264 265 private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, 266 long lowStorageThreshold, boolean is_for_primary_dex, 267 ArraySet<String> failedPackageNames) { 268 for (String pkg : pkgs) { 269 int abort_code = abortIdleOptimizations(lowStorageThreshold); 270 if (abort_code != OPTIMIZE_CONTINUE) { 271 return abort_code; 272 } 273 274 synchronized (failedPackageNames) { 275 if (failedPackageNames.contains(pkg)) { 276 // Skip previously failing package 277 continue; 278 } else { 279 // Conservatively add package to the list of failing ones in case performDexOpt 280 // never returns. 281 failedPackageNames.add(pkg); 282 } 283 } 284 285 // Optimize package if needed. Note that there can be no race between 286 // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 287 boolean success = is_for_primary_dex 288 ? pm.performDexOpt(pkg, 289 /* checkProfiles */ true, 290 PackageManagerService.REASON_BACKGROUND_DEXOPT, 291 /* force */ false) 292 : pm.performDexOptSecondary(pkg, 293 PackageManagerService.REASON_BACKGROUND_DEXOPT, 294 /* force */ false); 295 if (success) { 296 // Dexopt succeeded, remove package from the list of failing ones. 297 synchronized (failedPackageNames) { 298 failedPackageNames.remove(pkg); 299 } 300 } 301 } 302 return OPTIMIZE_PROCESSED; 303 } 304 305 private int reconcileSecondaryDexFiles(DexManager dm) { 306 // TODO(calin): should we blacklist packages for which we fail to reconcile? 307 for (String p : dm.getAllPackagesWithSecondaryDexFiles()) { 308 if (mAbortIdleOptimization.get()) { 309 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 310 } 311 dm.reconcileSecondaryDexFiles(p); 312 } 313 return OPTIMIZE_PROCESSED; 314 } 315 316 // Evaluate whether or not idle optimizations should continue. 317 private int abortIdleOptimizations(long lowStorageThreshold) { 318 if (mAbortIdleOptimization.get()) { 319 // JobScheduler requested an early abort. 320 return OPTIMIZE_ABORT_BY_JOB_SCHEDULER; 321 } 322 long usableSpace = mDataDir.getUsableSpace(); 323 if (usableSpace < lowStorageThreshold) { 324 // Rather bail than completely fill up the disk. 325 Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); 326 return OPTIMIZE_ABORT_NO_SPACE_LEFT; 327 } 328 329 return OPTIMIZE_CONTINUE; 330 } 331 332 /** 333 * Execute the idle optimizations immediately. 334 */ 335 public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) { 336 // Create a new object to make sure we don't interfere with the scheduled jobs. 337 // Note that this may still run at the same time with the job scheduled by the 338 // JobScheduler but the scheduler will not be able to cancel it. 339 BackgroundDexOptService bdos = new BackgroundDexOptService(); 340 int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context); 341 return result == OPTIMIZE_PROCESSED; 342 } 343 344 @Override 345 public boolean onStartJob(JobParameters params) { 346 if (DEBUG_DEXOPT) { 347 Log.i(TAG, "onStartJob"); 348 } 349 350 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 351 // the checks above. This check is not "live" - the value is determined by a background 352 // restart with a period of ~1 minute. 353 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 354 if (pm.isStorageLow()) { 355 if (DEBUG_DEXOPT) { 356 Log.i(TAG, "Low storage, skipping this run"); 357 } 358 return false; 359 } 360 361 final ArraySet<String> pkgs = pm.getOptimizablePackages(); 362 if (pkgs.isEmpty()) { 363 if (DEBUG_DEXOPT) { 364 Log.i(TAG, "No packages to optimize"); 365 } 366 return false; 367 } 368 369 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 370 return runPostBootUpdate(params, pm, pkgs); 371 } else { 372 return runIdleOptimization(params, pm, pkgs); 373 } 374 } 375 376 @Override 377 public boolean onStopJob(JobParameters params) { 378 if (DEBUG_DEXOPT) { 379 Log.i(TAG, "onStopJob"); 380 } 381 382 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 383 mAbortPostBootUpdate.set(true); 384 } else { 385 mAbortIdleOptimization.set(true); 386 } 387 return false; 388 } 389} 390