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