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