BackgroundDexOptService.java revision a57ef163750ca72180092de7e85835d2b8f464bc
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.storage.StorageManager; 34import android.util.ArraySet; 35import android.util.Log; 36 37import java.io.File; 38import java.util.concurrent.atomic.AtomicBoolean; 39import java.util.concurrent.TimeUnit; 40 41/** 42 * {@hide} 43 */ 44public class BackgroundDexOptService extends JobService { 45 private static final String TAG = "BackgroundDexOptService"; 46 47 private static final boolean DEBUG = false; 48 49 private static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR; 50 51 private static final int JOB_IDLE_OPTIMIZE = 800; 52 private static final int JOB_POST_BOOT_UPDATE = 801; 53 54 private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG 55 ? TimeUnit.MINUTES.toMillis(1) 56 : TimeUnit.DAYS.toMillis(1); 57 58 private static ComponentName sDexoptServiceName = new ComponentName( 59 "android", 60 BackgroundDexOptService.class.getName()); 61 62 /** 63 * Set of failed packages remembered across job runs. 64 */ 65 static final ArraySet<String> sFailedPackageNames = new ArraySet<String>(); 66 67 /** 68 * Atomics set to true if the JobScheduler requests an abort. 69 */ 70 final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); 71 final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); 72 73 /** 74 * Atomic set to true if one job should exit early because another job was started. 75 */ 76 final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); 77 78 private final File mDataDir = Environment.getDataDirectory(); 79 80 public static void schedule(Context context) { 81 JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); 82 83 // Schedule a one-off job which scans installed packages and updates 84 // out-of-date oat files. 85 js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName) 86 .setMinimumLatency(TimeUnit.MINUTES.toMillis(1)) 87 .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1)) 88 .build()); 89 90 // Schedule a daily job which scans installed packages and compiles 91 // those with fresh profiling data. 92 js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName) 93 .setRequiresDeviceIdle(true) 94 .setRequiresCharging(true) 95 .setPeriodic(IDLE_OPTIMIZATION_PERIOD) 96 .build()); 97 98 if (DEBUG_DEXOPT) { 99 Log.i(TAG, "Jobs scheduled"); 100 } 101 } 102 103 public static void notifyPackageChanged(String packageName) { 104 // The idle maintanance job skips packages which previously failed to 105 // compile. The given package has changed and may successfully compile 106 // now. Remove it from the list of known failing packages. 107 synchronized (sFailedPackageNames) { 108 sFailedPackageNames.remove(packageName); 109 } 110 } 111 112 // Returns the current battery level as a 0-100 integer. 113 private int getBatteryLevel() { 114 IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 115 Intent intent = registerReceiver(null, filter); 116 int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 117 int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 118 119 if (level < 0 || scale <= 0) { 120 // Battery data unavailable. This should never happen, so assume the worst. 121 return 0; 122 } 123 124 return (100 * level / scale); 125 } 126 127 private long getLowStorageThreshold(Context context) { 128 @SuppressWarnings("deprecation") 129 final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir); 130 if (lowThreshold == 0) { 131 Log.e(TAG, "Invalid low storage threshold"); 132 } 133 134 return lowThreshold; 135 } 136 137 private boolean runPostBootUpdate(final JobParameters jobParams, 138 final PackageManagerService pm, final ArraySet<String> pkgs) { 139 if (mExitPostBootUpdate.get()) { 140 // This job has already been superseded. Do not start it. 141 return false; 142 } 143 new Thread("BackgroundDexOptService_PostBootUpdate") { 144 @Override 145 public void run() { 146 postBootUpdate(jobParams, pm, pkgs); 147 } 148 149 }.start(); 150 return true; 151 } 152 153 private void postBootUpdate(JobParameters jobParams, PackageManagerService pm, 154 ArraySet<String> pkgs) { 155 // Load low battery threshold from the system config. This is a 0-100 integer. 156 final int lowBatteryThreshold = getResources().getInteger( 157 com.android.internal.R.integer.config_lowBatteryWarningLevel); 158 final long lowThreshold = getLowStorageThreshold(this); 159 160 mAbortPostBootUpdate.set(false); 161 162 for (String pkg : pkgs) { 163 if (mAbortPostBootUpdate.get()) { 164 // JobScheduler requested an early abort. 165 return; 166 } 167 if (mExitPostBootUpdate.get()) { 168 // Different job, which supersedes this one, is running. 169 break; 170 } 171 if (getBatteryLevel() < lowBatteryThreshold) { 172 // Rather bail than completely drain the battery. 173 break; 174 } 175 long usableSpace = mDataDir.getUsableSpace(); 176 if (usableSpace < lowThreshold) { 177 // Rather bail than completely fill up the disk. 178 Log.w(TAG, "Aborting background dex opt job due to low storage: " + 179 usableSpace); 180 break; 181 } 182 183 if (DEBUG_DEXOPT) { 184 Log.i(TAG, "Updating package " + pkg); 185 } 186 187 // Update package if needed. Note that there can be no race between concurrent 188 // jobs because PackageDexOptimizer.performDexOpt is synchronized. 189 190 // checkProfiles is false to avoid merging profiles during boot which 191 // might interfere with background compilation (b/28612421). 192 // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will 193 // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a 194 // trade-off worth doing to save boot time work. 195 pm.performDexOpt(pkg, 196 /* checkProfiles */ false, 197 PackageManagerService.REASON_BOOT, 198 /* force */ false); 199 } 200 // Ran to completion, so we abandon our timeslice and do not reschedule. 201 jobFinished(jobParams, /* reschedule */ false); 202 } 203 204 private boolean runIdleOptimization(final JobParameters jobParams, 205 final PackageManagerService pm, final ArraySet<String> pkgs) { 206 new Thread("BackgroundDexOptService_IdleOptimization") { 207 @Override 208 public void run() { 209 idleOptimization(pm, pkgs, BackgroundDexOptService.this); 210 if (!mAbortIdleOptimization.get()) { 211 // If we didn't abort we ran to completion (or stopped because of space). 212 // Abandon our timeslice and do not reschedule. 213 jobFinished(jobParams, /* reschedule */ false); 214 } 215 } 216 }.start(); 217 return true; 218 } 219 220 // Optimize the given packages and return true if the process was not aborted. 221 // The abort can happen either because of job scheduler or because of lack of space. 222 private boolean idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, 223 Context context) { 224 Log.i(TAG, "Performing idle optimizations"); 225 // If post-boot update is still running, request that it exits early. 226 mExitPostBootUpdate.set(true); 227 mAbortIdleOptimization.set(false); 228 229 long lowStorageThreshold = getLowStorageThreshold(context); 230 return optimizePackages(pm, pkgs, lowStorageThreshold); 231 } 232 233 private boolean optimizePackages(PackageManagerService pm, ArraySet<String> pkgs, 234 long lowStorageThreshold) { 235 for (String pkg : pkgs) { 236 if (abortIdleOptimizations(lowStorageThreshold)) { 237 return false; 238 } 239 240 synchronized (sFailedPackageNames) { 241 if (sFailedPackageNames.contains(pkg)) { 242 // Skip previously failing package 243 continue; 244 } else { 245 // Conservatively add package to the list of failing ones in case performDexOpt 246 // never returns. 247 sFailedPackageNames.add(pkg); 248 } 249 } 250 251 // Optimize package if needed. Note that there can be no race between 252 // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 253 if (pm.performDexOpt(pkg, 254 /* checkProfiles */ true, 255 PackageManagerService.REASON_BACKGROUND_DEXOPT, 256 /* force */ false)) { 257 // Dexopt succeeded, remove package from the list of failing ones. 258 synchronized (sFailedPackageNames) { 259 sFailedPackageNames.remove(pkg); 260 } 261 } 262 } 263 return true; 264 } 265 266 // Return true if the idle optimizations should be aborted because of a space constraints 267 // or because the JobScheduler requested so. 268 private boolean abortIdleOptimizations(long lowStorageThreshold) { 269 if (mAbortIdleOptimization.get()) { 270 // JobScheduler requested an early abort. 271 return true; 272 } 273 long usableSpace = mDataDir.getUsableSpace(); 274 if (usableSpace < lowStorageThreshold) { 275 // Rather bail than completely fill up the disk. 276 Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace); 277 return true; 278 } 279 280 return false; 281 } 282 283 @Override 284 public boolean onStartJob(JobParameters params) { 285 if (DEBUG_DEXOPT) { 286 Log.i(TAG, "onStartJob"); 287 } 288 289 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 290 // the checks above. This check is not "live" - the value is determined by a background 291 // restart with a period of ~1 minute. 292 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 293 if (pm.isStorageLow()) { 294 if (DEBUG_DEXOPT) { 295 Log.i(TAG, "Low storage, skipping this run"); 296 } 297 return false; 298 } 299 300 final ArraySet<String> pkgs = pm.getOptimizablePackages(); 301 if (pkgs.isEmpty()) { 302 if (DEBUG_DEXOPT) { 303 Log.i(TAG, "No packages to optimize"); 304 } 305 return false; 306 } 307 308 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 309 return runPostBootUpdate(params, pm, pkgs); 310 } else { 311 return runIdleOptimization(params, pm, pkgs); 312 } 313 } 314 315 @Override 316 public boolean onStopJob(JobParameters params) { 317 if (DEBUG_DEXOPT) { 318 Log.i(TAG, "onStopJob"); 319 } 320 321 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 322 mAbortPostBootUpdate.set(true); 323 } else { 324 mAbortIdleOptimization.set(true); 325 } 326 return false; 327 } 328} 329