BackgroundDexOptService.java revision 3a2b7f7d59da9f54d9de4afbbfeebcfb4a3866f2
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() { 128 @SuppressWarnings("deprecation") 129 final long lowThreshold = StorageManager.from(this).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(); 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(jobParams, pm, pkgs); 210 } 211 }.start(); 212 return true; 213 } 214 215 private void idleOptimization(JobParameters jobParams, PackageManagerService pm, 216 ArraySet<String> pkgs) { 217 Log.i(TAG, "Performing idle optimizations"); 218 // If post-boot update is still running, request that it exits early. 219 mExitPostBootUpdate.set(true); 220 221 mAbortIdleOptimization.set(false); 222 223 final long lowThreshold = getLowStorageThreshold(); 224 for (String pkg : pkgs) { 225 if (mAbortIdleOptimization.get()) { 226 // JobScheduler requested an early abort. 227 return; 228 } 229 230 synchronized (sFailedPackageNames) { 231 if (sFailedPackageNames.contains(pkg)) { 232 // Skip previously failing package 233 continue; 234 } 235 } 236 237 long usableSpace = mDataDir.getUsableSpace(); 238 if (usableSpace < lowThreshold) { 239 // Rather bail than completely fill up the disk. 240 Log.w(TAG, "Aborting background dex opt job due to low storage: " + 241 usableSpace); 242 break; 243 } 244 245 // Conservatively add package to the list of failing ones in case performDexOpt 246 // never returns. 247 synchronized (sFailedPackageNames) { 248 sFailedPackageNames.add(pkg); 249 } 250 // Optimize package if needed. Note that there can be no race between 251 // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. 252 if (pm.performDexOpt(pkg, 253 /* checkProfiles */ true, 254 PackageManagerService.REASON_BACKGROUND_DEXOPT, 255 /* force */ false)) { 256 // Dexopt succeeded, remove package from the list of failing ones. 257 synchronized (sFailedPackageNames) { 258 sFailedPackageNames.remove(pkg); 259 } 260 } 261 } 262 // Ran to completion, so we abandon our timeslice and do not reschedule. 263 jobFinished(jobParams, /* reschedule */ false); 264 } 265 266 @Override 267 public boolean onStartJob(JobParameters params) { 268 if (DEBUG_DEXOPT) { 269 Log.i(TAG, "onStartJob"); 270 } 271 272 // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from 273 // the checks above. This check is not "live" - the value is determined by a background 274 // restart with a period of ~1 minute. 275 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 276 if (pm.isStorageLow()) { 277 if (DEBUG_DEXOPT) { 278 Log.i(TAG, "Low storage, skipping this run"); 279 } 280 return false; 281 } 282 283 final ArraySet<String> pkgs = pm.getOptimizablePackages(); 284 if (pkgs == null || pkgs.isEmpty()) { 285 if (DEBUG_DEXOPT) { 286 Log.i(TAG, "No packages to optimize"); 287 } 288 return false; 289 } 290 291 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 292 return runPostBootUpdate(params, pm, pkgs); 293 } else { 294 return runIdleOptimization(params, pm, pkgs); 295 } 296 } 297 298 @Override 299 public boolean onStopJob(JobParameters params) { 300 if (DEBUG_DEXOPT) { 301 Log.i(TAG, "onStopJob"); 302 } 303 304 if (params.getJobId() == JOB_POST_BOOT_UPDATE) { 305 mAbortPostBootUpdate.set(true); 306 } else { 307 mAbortIdleOptimization.set(true); 308 } 309 return false; 310 } 311} 312