一,App Standby介紹
App Standby是一種電池管理技術,根據應用最近使用時間和使用頻率,來進行對應用使用jobs,alarm,network的優化,達到省電的目的

從這個表上,就可以看出根據應用的使用情況分了5個群組
ACTIVE
如果用戶當前正在使用應用,應用將被歸到“活躍”群組中,例如:
- 應用已啟動一個 Activity
- 應用正在運行前臺服務
- 應用的同步配接器與某個前臺應用使用的 content provider 關聯
- 用戶在應用中點擊了某個通知
如果應用處于“活躍”群組,系統不會對應用的job,alarm,network施加任何限制,
WORKING_SER
如果應用經常運行,但當前未處于活躍狀態,它將被歸到“作業集”群組中, 例如,用戶在大部分時間都啟動的某個社交媒體應用可能就屬于“作業集”群組, 如果應用被間接使用,它們也會被升級到“作業集”群組中 ,
如果應用處于“作業集”群組,系統會對它運行作業和觸發報警的能力施加輕度限制, 如需了解詳細資訊,請參閱電源管理限制,
FREQUENT
如果應用會定期使用,但不是每天都必須使用,它將被歸到“常用”群組中, 例如,用戶在健身房運行的某個鍛煉跟蹤應用可能就屬于“常用”群組,
如果應用處于“常用”群組,系統將對它運行作業和觸發報警的能力施加較強的限制,也會對高優先級 FCM 訊息的數量設定限制,
RARE
如果應用不經常使用,那么它屬于“極少使用”群組, 例如,用戶僅在入住酒店期間運行的酒店應用就可能屬于“極少使用”群組,
如果應用處于“極少使用”群組,系統將對它運行作業、觸發警報和接收高優先級 FCM 訊息的能力施加嚴格限制,系統還會限制應用連接到網路的能力,
NEVER
安裝但是從未運行過的應用會被歸到“從未使用”群組中, 系統會對這些應用施加極強的限制,
二,setings設定bucket
settings->開發者選項->Standby apps,可以手動設定應用群組

settings->InactiveApps.java->onPreferenceChange()
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
mUsageStats.setAppStandbyBucket(preference.getKey(), Integer.parseInt((String) newValue));
updateSummary((ListPreference) preference);
return false;
}
呼叫了UsageStatsManager.java->setAppStandbyBucket(),將選擇的應用包名和群組類別寫入到service中
@SystemApi
@RequiresPermission(android.Manifest.permission.CHANGE_APP_IDLE_STATE)
public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) {
try {
mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
UsageStatsService.java->setAppStandbyBucket()
@Override
public void setAppStandbyBucket(String packageName,
int bucket, int userId) {
getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
"No permission to change app standby state");
if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
|| bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
}
final int callingUid = Binder.getCallingUid();
try {
userId = ActivityManager.getService().handleIncomingUser(
Binder.getCallingPid(), callingUid, userId, false, true,
"setAppStandbyBucket", null);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
final boolean systemCaller = UserHandle.isCore(callingUid);
final int reason = systemCaller
? UsageStatsManager.REASON_MAIN_FORCED
: UsageStatsManager.REASON_MAIN_PREDICTED;
final long token = Binder.clearCallingIdentity();
try {
final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
// Caller cannot set their own standby state
if (packageUid == callingUid) {
throw new IllegalArgumentException("Cannot set your own standby bucket");
}
if (packageUid < 0) {
throw new IllegalArgumentException(
"Cannot set standby bucket for non existent package (" + packageName
+ ")");
}
mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
SystemClock.elapsedRealtime(), shellCaller);
} finally {
Binder.restoreCallingIdentity(token);
}
}
AppStandbyController.java->setAppStandbyBucket()
void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
int reason, long elapsedRealtime) {
setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false);
}
void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
int reason, long elapsedRealtime, boolean resetTimeout) {
synchronized (mAppIdleLock) {
// If the package is not installed, don't allow the bucket to be set.
if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
return;
}
AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName,
userId, elapsedRealtime);
boolean predicted = (reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED;
// Don't allow changing bucket if higher than ACTIVE
if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
// Don't allow prediction to change from/to NEVER
if ((app.currentBucket == STANDBY_BUCKET_NEVER
|| newBucket == STANDBY_BUCKET_NEVER)
&& predicted) {
return;
}
// If the bucket was forced, don't allow prediction to override
if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return;
// If the bucket is required to stay in a higher state for a specified duration, don't
// override unless the duration has passed
if (predicted) {
// Check if the app is within one of the timeouts for forced bucket elevation
final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime);
// In case of not using the prediction, just keep track of it for applying after
// ACTIVE or WORKING_SET timeout.
mAppIdleHistory.updateLastPrediction(app, elapsedTimeAdjusted, newBucket);
if (newBucket > STANDBY_BUCKET_ACTIVE
&& app.bucketActiveTimeoutTime > elapsedTimeAdjusted) {
newBucket = STANDBY_BUCKET_ACTIVE;
reason = app.bucketingReason;
if (DEBUG) {
Slog.d(TAG, " Keeping at ACTIVE due to min timeout");
}
} else if (newBucket > STANDBY_BUCKET_WORKING_SET
&& app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) {
newBucket = STANDBY_BUCKET_WORKING_SET;
if (app.currentBucket != newBucket) {
reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
} else {
reason = app.bucketingReason;
}
if (DEBUG) {
Slog.d(TAG, " Keeping at WORKING_SET due to min timeout");
}
}
}
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
reason, resetTimeout);
}
maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, reason, false);
}
AppIdleHistory.java->setAppStandbyBucket()
public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
int bucket, int reason, boolean resetTimeout) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory =
getPackageHistory(userHistory, packageName, elapsedRealtime, true);
appUsageHistory.currentBucket = bucket;
appUsageHistory.bucketingReason = reason;
final long elapsed = getElapsedTime(elapsedRealtime);
if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) {
appUsageHistory.lastPredictedTime = elapsed;
appUsageHistory.lastPredictedBucket = bucket;
}
if (resetTimeout) {
appUsageHistory.bucketActiveTimeoutTime = elapsed;
appUsageHistory.bucketWorkingSetTimeoutTime = elapsed;
}
if (DEBUG) {
Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+ ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
}
}
三,系統根據對應用的使用情況自動修改應用群組
UsageStatsService是統籌負責App Standbyde ,它向外提供了reportEvent來收集app的行為,從而來根據app的使用情況來調節app的群組,比如ams,NotificationManagerService等都會呼叫reportEvent來告知UsageStatsService有app有行為變化
這個方法主要就是呼叫了AppStandbyController的reportEvent方法,所以我們直接看AppStandbyController.java-->reportEvent()
void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
if (!mAppIdleEnabled) return;
synchronized (mAppIdleLock) {
// TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
// about apps that are on some kind of whitelist anyway.
final boolean previouslyIdle = mAppIdleHistory.isIdle(
event.mPackage, userId, elapsedRealtime);
// Inform listeners if necessary
if ((event.mEventType == UsageEvents.Event.ACTIVITY_RESUMED
|| event.mEventType == UsageEvents.Event.ACTIVITY_PAUSED
|| event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
|| event.mEventType == UsageEvents.Event.USER_INTERACTION
|| event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
|| event.mEventType == UsageEvents.Event.SLICE_PINNED
|| event.mEventType == UsageEvents.Event.SLICE_PINNED_PRIV
|| event.mEventType == UsageEvents.Event.FOREGROUND_SERVICE_START)) {
final AppUsageHistory appHistory = mAppIdleHistory.getAppUsageHistory(
event.mPackage, userId, elapsedRealtime);
final int prevBucket = appHistory.currentBucket;
final int prevBucketReason = appHistory.bucketingReason;
final long nextCheckTime;
final int subReason = usageEventToSubReason(event.mEventType);
final int reason = REASON_MAIN_USAGE | subReason;
if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
|| event.mEventType == UsageEvents.Event.SLICE_PINNED) {
// Mild usage elevates to WORKING_SET but doesn't change usage time.
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_WORKING_SET, subReason,
0, elapsedRealtime + mNotificationSeenTimeoutMillis);
//當是Notification型別的訊息的延遲時間為12小時
nextCheckTime = mNotificationSeenTimeoutMillis;
} else if (event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION) {
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_ACTIVE, subReason,
0, elapsedRealtime + mSystemInteractionTimeoutMillis);
nextCheckTime = mSystemInteractionTimeoutMillis;
} else if (event.mEventType == UsageEvents.Event.FOREGROUND_SERVICE_START) {
// Only elevate bucket if this is the first usage of the app
if (prevBucket != STANDBY_BUCKET_NEVER) return;
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_ACTIVE, subReason,
0, elapsedRealtime + mInitialForegroundServiceStartTimeoutMillis);
nextCheckTime = mInitialForegroundServiceStartTimeoutMillis;
} else {
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_ACTIVE, subReason,
elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
nextCheckTime = mStrongUsageTimeoutMillis;
}
mHandler.sendMessageDelayed(mHandler.obtainMessage
(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, event.mPackage),
nextCheckTime);
final boolean userStartedInteracting =
appHistory.currentBucket == STANDBY_BUCKET_ACTIVE &&
prevBucket != appHistory.currentBucket &&
(prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
maybeInformListeners(event.mPackage, userId, elapsedRealtime,
appHistory.currentBucket, reason, userStartedInteracting);
if (previouslyIdle) {
notifyBatteryStats(event.mPackage, userId, false);
}
}
}
}
比如當點擊使用一個app時,屬于Strong Usage,系統會直接將STANDBY_BUCKET_ACTIVE傳遞給AppIdleHistory的reportUsage方法,將點擊使用的app加入到ACTIVE群組中
public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName,
int newBucket, int usageReason, long elapsedRealtime, long timeout) {
// Set the timeout if applicable
if (timeout > elapsedRealtime) {
// Convert to elapsed timebase
final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
if (newBucket == STANDBY_BUCKET_ACTIVE) {
appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
appUsageHistory.bucketActiveTimeoutTime);
} else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
appUsageHistory.bucketWorkingSetTimeoutTime);
} else {
throw new IllegalArgumentException("Cannot set a timeout on bucket=" +
newBucket);
}
}
if (elapsedRealtime != 0) {
appUsageHistory.lastUsedElapsedTime = mElapsedDuration
+ (elapsedRealtime - mElapsedSnapshot);
appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
}
if (appUsageHistory.currentBucket > newBucket) {
appUsageHistory.currentBucket = newBucket;
if (DEBUG) {
Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory
.currentBucket
+ ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
}
}
appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason;
return appUsageHistory;
}
同時會發送一個MSG_CHECK_PACKAGE_IDLE_STATE的延時message,mStrongUsageTimeoutMillis(一個小時)后再次檢查app的狀態
case MSG_CHECK_PACKAGE_IDLE_STATE:
checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2,
mInjector.elapsedRealtime());
break;
checkAndUpdateStandbyState()中主要是呼叫getBucketForLocked()來重新計算app應該哪個bucket,并設定app新的bucket,所以我們直接看下getBucketForLocked()
@GuardedBy("mAppIdleLock")
@StandbyBuckets int getBucketForLocked(String packageName, int userId,
long elapsedRealtime) {
int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
return THRESHOLD_BUCKETS[bucketIndex];
}
getBucketForLocked()最后return了一個THRESHOLD_BUCKETS陣列,
static final int[] THRESHOLD_BUCKETS = {
STANDBY_BUCKET_ACTIVE,
STANDBY_BUCKET_WORKING_SET,
STANDBY_BUCKET_FREQUENT,
STANDBY_BUCKET_RARE
};
可以看出THRESHOLD_BUCKETS就是對應了各個bucket
再看下mAppStandbyScreenThresholds和mAppStandbyElapsedThresholds的值,這里COMPRESS_TIME是true,我們除錯代碼的時候,可以將這個COMPRESS_TIME改為false,可以快速更新app狀態
static final long[] SCREEN_TIME_THRESHOLDS = {
0,
0,
COMPRESS_TIME ? 120 * 1000 : 120 * 1000,
COMPRESS_TIME ? 240 * 1000 : 240 * 1000
};
static final long[] ELAPSED_TIME_THRESHOLDS = {
0,
COMPRESS_TIME ? 1 * ONE_MINUTE : 1 * ONE_MINUTE,
COMPRESS_TIME ? 4 * ONE_MINUTE : 4 * ONE_MINUTE,
COMPRESS_TIME ? 16 * ONE_MINUTE : 16 * ONE_MINUTE
};
我們繼續看AppIdleHistory的getThresholdIndex()方法
/*
*mScreenOnSnapshot 從開機到當前亮屏時的時間
*mScreenOnDuration 累計亮屏時間,從刷機后第一次開機開始累計,關機時會保存在檔案中,再開機時會讀取出之前的累計時間
*mElapsedSnapshot 從開機到當前的時間,和mScreenOnSnapshot其實是一個值
*mElapsedDuration 累計開機時間,從刷機后第一次開機開始累計,關機時會保存在檔案中,再開機時會讀取出之前的累計時間
*/
int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, false);
// If we don't have any state for the app, assume never used
if (appUsageHistory == null) return screenTimeThresholds.length - 1;
long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
if (DEBUG && packageName.equals("com.mediatek.camera")) Log.d("lzq", packageName
+ " lastUsedScreen=" + appUsageHistory.lastUsedScreenTime
+ " lastUsedElapsed=" + appUsageHistory.lastUsedElapsedTime);
if (DEBUG && packageName.equals("com.mediatek.camera")) Slog.d(TAG, packageName + " screenOn=" + screenOnDelta
+ ", elapsed=" + elapsedDelta);
for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
if (screenOnDelta >= screenTimeThresholds[i]
&& elapsedDelta >= elapsedTimeThresholds[i]) {
return i;
}
}
return 0;
}
然后從上面兩個表格看也就說
1. 開機時間從上一次使用app的時間差超過12小時bucket為working_set,
2. 亮屏時間差超過1小時、使用時間差超過24小時bucket為FREQUENT
3. 亮屏時間差超過2小時、使用時間差超過48小時bucket為RARE
前面提到通過延遲訊息MSG_CHECK_PACKAGE_IDLE_STATE,最后在checkAndUpdateStandbyState函式中處理,來完成每個app的bucket的檢查,但是如果隔了很長時間又如何檢查呢?
在UsageStatsService的reportEvent中會呼叫UserUsageStatsService的reportEvent,每一個userId都有一個UserUsageStatsService用來統計資料,而每過一天會在UserUsageStatsService的reportEvent函式中呼叫rolloverStats函式,rolloverStats函式中會呼叫loadActiveStats函式,loadActiveStats函式會呼叫mListener.onStatsReloaded函式,而這個mLisener正是UsageStatsService,而UsageStatsService的onStatsReloaded函式,是呼叫了AppStandbyController的postOneTimeCheckIdleStates,
@Override
public void onStatsReloaded() {
mAppStandby.postOneTimeCheckIdleStates();
}
void postOneTimeCheckIdleStates() {
if (mInjector.getBootPhase() < PHASE_SYSTEM_SERVICES_READY) {
// Not booted yet; wait for it!
mPendingOneTimeCheckIdleStates = true;
} else {
mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
mPendingOneTimeCheckIdleStates = false;
}
}
發送MSG_ONE_TIME_CHECK_IDLE_STATES
case MSG_ONE_TIME_CHECK_IDLE_STATES:
mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
waitForAdminData();
checkIdleStates(UserHandle.USER_ALL);
break;
主要是呼叫了checkIdleStates()
/**
* Check all running users' or specified user's apps to see if they enter an idle state.
* @return Returns whether checking should continue periodically.
*/
boolean checkIdleStates(int checkUserId) {
if (!mAppIdleEnabled) {
return false;
}
final int[] runningUserIds;
try {
runningUserIds = mInjector.getRunningUserIds();
if (checkUserId != UserHandle.USER_ALL
&& !ArrayUtils.contains(runningUserIds, checkUserId)) {
return false;
}
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
final long elapsedRealtime = mInjector.elapsedRealtime();
for (int i = 0; i < runningUserIds.length; i++) {
final int userId = runningUserIds[i];
if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
continue;
}
if (DEBUG) {
Slog.d(TAG, "Checking idle state for user " + userId);
}
List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
PackageManager.MATCH_DISABLED_COMPONENTS,
userId);
final int packageCount = packages.size();
for (int p = 0; p < packageCount; p++) {
final PackageInfo pi = packages.get(p);
final String packageName = pi.packageName;
checkAndUpdateStandbyState(packageName, userId, pi.applicationInfo.uid,
elapsedRealtime);
}
}
if (DEBUG) {
Slog.d(TAG, "checkIdleStates took "
+ (mInjector.elapsedRealtime() - elapsedRealtime));
}
return true;
}
在這個方法里我們可以看到又呼叫了checkAndUpdateStandbyState()來更新應用群組
如果app的bucket為RARE或者NEVER,那么就會設定應用為idle
四,系統針對不同bucket的限制

系統的alarm,job,network是怎么監聽app bucket的變化呢,系統提供了如下介面
public static abstract class AppIdleStateChangeListener {
/** Callback to inform listeners that the idle state has changed to a new bucket. */
public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
boolean idle, int bucket, int reason);
/**
* Callback to inform listeners that the parole state has changed. This means apps are
* allowed to do work even if they're idle or in a low bucket.
*/
public abstract void onParoleStateChanged(boolean isParoleOn);
/**
* Optional callback to inform the listener that the app has transitioned into
* an active state due to user interaction.
*/
public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
// No-op by default
}
}
AlarmManagerService,JobSchedulerService,NetworkPolicyManagerService都實作了這個介面,用來監聽app的變化,
當app bucket變化時,AppStandbyController的informListeners()方法會被呼叫,通知其他service ,有app bucket變化了
void informListeners(String packageName, int userId, int bucket, int reason,
boolean userInteraction) {
final boolean idle = bucket >= STANDBY_BUCKET_RARE;
synchronized (mPackageAccessListeners) {
for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
listener.onAppIdleStateChanged(packageName, userId, idle, bucket, reason);
if (userInteraction) {
listener.onUserInteractionStarted(packageName, userId);
}
}
}
}
下面我們繼續看是怎么進行限制的
Alarm
Alarm的限制主要在設定alarm的時候,延遲alarm執行的時間
private final class AppStandbyTracker extends
UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
boolean idle, int bucket, int reason) {
if (DEBUG_STANDBY) {
Slog.d("lzq", "Package " + packageName + " for user " + userId + " now in bucket " +
bucket);
}
mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
.sendToTarget();
}
發送AlarmHandler.APP_STANDBY_BUCKET_CHANGED
case APP_STANDBY_BUCKET_CHANGED:
synchronized (mLock) {
final ArraySet<Pair<String, Integer>> filterPackages = new ArraySet<>();
filterPackages.add(Pair.create((String) msg.obj, msg.arg1));
if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) {
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
}
}
break;
重點看reorderAlarmsBasedOnStandbyBuckets(),在這個方法里會呼叫adjustDeliveryTimeBasedOnBucketLocked(),時間延時調整就在這里
boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet<Pair<String, Integer>> targetPackages) {
final long start = mStatLogger.getTime();
final ArrayList<Alarm> rescheduledAlarms = new ArrayList<>();
for (int batchIndex = mAlarmBatches.size() - 1; batchIndex >= 0; batchIndex--) {
final Batch batch = mAlarmBatches.get(batchIndex);
for (int alarmIndex = batch.size() - 1; alarmIndex >= 0; alarmIndex--) {
final Alarm alarm = batch.get(alarmIndex);
final Pair<String, Integer> packageUser =
Pair.create(alarm.sourcePackage, UserHandle.getUserId(alarm.creatorUid));
if (targetPackages != null && !targetPackages.contains(packageUser)) {
continue;
}
if (adjustDeliveryTimeBasedOnBucketLocked(alarm)) {
batch.remove(alarm);
rescheduledAlarms.add(alarm);
}
}
if (batch.size() == 0) {
mAlarmBatches.remove(batchIndex);
}
}
for (int i = 0; i < rescheduledAlarms.size(); i++) {
final Alarm a = rescheduledAlarms.get(i);
insertAndBatchAlarmLocked(a);
}
mStatLogger.logDurationStat(Stats.REORDER_ALARMS_FOR_STANDBY, start);
return rescheduledAlarms.size() > 0;
}
adjustDeliveryTimeBasedOnBucketLocked()
private boolean adjustDeliveryTimeBasedOnBucketLocked(Alarm alarm) {
if (isExemptFromAppStandby(alarm)) {
return false;
}
if (mAppStandbyParole) {
if (alarm.whenElapsed > alarm.expectedWhenElapsed) {
// We did defer this alarm earlier, restore original requirements
alarm.whenElapsed = alarm.expectedWhenElapsed;
alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
return true;
}
return false;
}
final long oldWhenElapsed = alarm.whenElapsed;
final long oldMaxWhenElapsed = alarm.maxWhenElapsed;
final String sourcePackage = alarm.sourcePackage;
final int sourceUserId = UserHandle.getUserId(alarm.creatorUid);
final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
sourcePackage, sourceUserId, mInjector.getElapsedRealtime());
if (mConstants.APP_STANDBY_QUOTAS_ENABLED) {
// Quota deferring implementation:
final int wakeupsInWindow = mAppWakeupHistory.getTotalWakeupsInWindow(sourcePackage,
sourceUserId);
final int quotaForBucket = getQuotaForBucketLocked(standbyBucket);
boolean deferred = false;
if (wakeupsInWindow >= quotaForBucket) {
final long minElapsed;
if (quotaForBucket <= 0) {
// Just keep deferring for a day till the quota changes
minElapsed = mInjector.getElapsedRealtime() + MILLIS_IN_DAY;
} else {
// Suppose the quota for window was q, and the qth last delivery time for this
// package was t(q) then the next delivery must be after t(q) + <window_size>
final long t = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
sourceUserId, quotaForBucket);
minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW;
}
if (alarm.expectedWhenElapsed < minElapsed) {
alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
deferred = true;
}
}
if (!deferred) {
// Restore original requirements in case they were changed earlier.
alarm.whenElapsed = alarm.expectedWhenElapsed;
alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
}
} else {
// Minimum delay deferring implementation:
final long lastElapsed = mAppWakeupHistory.getLastWakeupForPackage(sourcePackage,
sourceUserId, 1);
if (lastElapsed > 0) {
final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
if (alarm.expectedWhenElapsed < minElapsed) {
alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed;
} else {
// app is now eligible to run alarms at the originally requested window.
// Restore original requirements in case they were changed earlier.
alarm.whenElapsed = alarm.expectedWhenElapsed;
alarm.maxWhenElapsed = alarm.expectedMaxWhenElapsed;
}
}
}
return (oldWhenElapsed != alarm.whenElapsed || oldMaxWhenElapsed != alarm.maxWhenElapsed);
}
在這里可以看到,會通過getAppStandbyBucket()拿到app的standbyBucket,通過standbyBucket,呼叫getMinDelayForBucketLocked()計算alarm延遲時間
@VisibleForTesting
long getMinDelayForBucketLocked(int bucket) {
// UsageStats bucket values are treated as floors of their behavioral range.
// In other words, a bucket value between WORKING and ACTIVE is treated as
// WORKING, not as ACTIVE. The ACTIVE and NEVER bucket apply only at specific
// values.
final int index;
if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) index = NEVER_INDEX;
else if (bucket > UsageStatsManager.STANDBY_BUCKET_FREQUENT) index = RARE_INDEX;
else if (bucket > UsageStatsManager.STANDBY_BUCKET_WORKING_SET) index = FREQUENT_INDEX;
else if (bucket > UsageStatsManager.STANDBY_BUCKET_ACTIVE) index = WORKING_INDEX;
else index = ACTIVE_INDEX;
return mConstants.APP_STANDBY_MIN_DELAYS[index];
}
可以看到最后return的是APP_STANDBY_MIN_DELAYS,
private final long[] DEFAULT_APP_STANDBY_DELAYS = {
0, // Active
6 * 60_000, // Working
30 * 60_000, // Frequent
2 * 60 * 60_000, // Rare
10 * 24 * 60 * 60_000 // Never
};
Network
NetworkPolicyManagerService.java
是通過UsageStatsService的isAppIdle介面來判斷是否當前應用處于應用待機模式,也就是app的bucket的為rare以上,這個時候會限制網路
private class AppIdleStateChangeListener
extends UsageStatsManagerInternal.AppIdleStateChangeListener {
@Override
public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket,
int reason) {
try {
final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
synchronized (mUidRulesFirstLock) {
mLogger.appIdleStateChanged(uid, idle);
updateRuleForAppIdleUL(uid);
updateRulesForPowerRestrictionsUL(uid);
}
} catch (NameNotFoundException nnfe) {
}
}
在updateRulesForPowerRestrictionsUL()中會判斷app是否不在白名單并且是idle狀態,是的話就會呼叫setUidFirewallRule來限制其使用網路
@GuardedBy("mUidRulesFirstLock")
private void updateRulesForPowerRestrictionsUL(int uid) {
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false);
if (newUidRules == RULE_NONE) {
mUidRules.delete(uid);
} else {
mUidRules.put(uid, newUidRules);
}
}
private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
"updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+ (paroled ? "P" : "-"));
}
try {
return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
}
final boolean isIdle = !paroled && isUidIdle(uid);
final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
int newRule = RULE_NONE;
// First step: define the new rule based on user restrictions and foreground state.
// NOTE: if statements below could be inlined, but it's easier to understand the logic
// by considering the foreground and non-foreground states.
if (isForeground) {
if (restrictMode) {
newRule = RULE_ALLOW_ALL;
}
} else if (restrictMode) {
newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
}
final int newUidRules = (oldUidRules & MASK_METERED_NETWORKS) | newRule;
if (LOGV) {
Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
+ ", isIdle: " + isIdle
+ ", mRestrictPower: " + mRestrictPower
+ ", mDeviceIdleMode: " + mDeviceIdleMode
+ ", isForeground=" + isForeground
+ ", isWhitelisted=" + isWhitelisted
+ ", oldRule=" + uidRulesToString(oldRule)
+ ", newRule=" + uidRulesToString(newRule)
+ ", newUidRules=" + uidRulesToString(newUidRules)
+ ", oldUidRules=" + uidRulesToString(oldUidRules));
}
// Second step: notify listeners if state changed.
if (newRule != oldRule) {
if (newRule == RULE_NONE || hasRule(newRule, RULE_ALLOW_ALL)) {
if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
} else if (hasRule(newRule, RULE_REJECT_ALL)) {
if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
} else {
// All scenarios should have been covered above
Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
+ ": foreground=" + isForeground
+ ", whitelisted=" + isWhitelisted
+ ", newRule=" + uidRulesToString(newUidRules)
+ ", oldRule=" + uidRulesToString(oldUidRules));
}
mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
}
return newUidRules;
}
會判斷應用是否是idle狀態,是否在白名單中
@GuardedBy("mUidRulesFirstLock")
private boolean isWhitelistedFromPowerSaveUL(int uid, boolean deviceIdleMode) {
final int appId = UserHandle.getAppId(uid);
boolean isWhitelisted = mPowerSaveTempWhitelistAppIds.get(appId)
|| mPowerSaveWhitelistAppIds.get(appId);
if (!deviceIdleMode) {
isWhitelisted = isWhitelisted || mPowerSaveWhitelistExceptIdleAppIds.get(appId);
}
return isWhitelisted;
}
@GuardedBy("mUidRulesFirstLock")
void updatePowerSaveWhitelistUL() {
try {
int[] whitelist = mDeviceIdleController.getAppIdWhitelistExceptIdle();
mPowerSaveWhitelistExceptIdleAppIds.clear();
if (whitelist != null) {
for (int uid : whitelist) {
mPowerSaveWhitelistExceptIdleAppIds.put(uid, true);
}
}
whitelist = mDeviceIdleController.getAppIdWhitelist();
mPowerSaveWhitelistAppIds.clear();
if (whitelist != null) {
for (int uid : whitelist) {
mPowerSaveWhitelistAppIds.put(uid, true);
}
}
} catch (RemoteException e) {
}
}
白名單設定
frameworks/base/data/etc/platform.xml
app standby & doze 白名單設定
<allow-in-power-save package="com.android.shell" />
只是doze白名單設定
<allow-in-power-save-except-idle package="com.android.providers.calendar" />
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/254085.html
標籤:其他
上一篇:Retrofit+Okhttp實作注冊登錄+后端代碼超詳細步驟
下一篇:android組件化
