App Standby是一种电池管理技术,根据应用最近使用时间和使用频率,来进行对应用使用jobs,alarm,network的优化,达到省电的目的
从这个表上,就可以看出根据应用的使用情况分了5个群组
ACTIVE
如果用户当前正在使用应用,应用将被归到“活跃”群组中,例如:
如果应用处于“活跃”群组,系统不会对应用的job,alarm,network施加任何限制。
WORKING_SER
如果应用经常运行,但当前未处于活跃状态,它将被归到“工作集”群组中。 例如,用户在大部分时间都启动的某个社交媒体应用可能就属于“工作集”群组。 如果应用被间接使用,它们也会被升级到“工作集”群组中 。
如果应用处于“工作集”群组,系统会对它运行作业和触发报警的能力施加轻度限制。 如需了解详细信息,请参阅电源管理限制。
FREQUENT
如果应用会定期使用,但不是每天都必须使用,它将被归到“常用”群组中。 例如,用户在健身房运行的某个锻炼跟踪应用可能就属于“常用”群组。
如果应用处于“常用”群组,系统将对它运行作业和触发报警的能力施加较强的限制,也会对高优先级 FCM 消息的数量设定限制。
RARE
如果应用不经常使用,那么它属于“极少使用”群组。 例如,用户仅在入住酒店期间运行的酒店应用就可能属于“极少使用”群组。
如果应用处于“极少使用”群组,系统将对它运行作业、触发警报和接收高优先级 FCM 消息的能力施加严格限制。系统还会限制应用连接到网络的能力。
NEVER
安装但是从未运行过的应用会被归到“从未使用”群组中。 系统会对这些应用施加极强的限制。
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 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 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 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执行的时间
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> filterPackages = new ArraySet<>();
filterPackages.add(Pair.create((String) msg.obj, msg.arg1));
if (reorderAlarmsBasedOnStandbyBuckets(filterPackages)) {
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
}
}
break;
重点看reorderAlarmsBasedOnStandbyBuckets(),在这个方法里会调用adjustDeliveryTimeBasedOnBucketLocked(),时间延时调整就在这里
boolean reorderAlarmsBasedOnStandbyBuckets(ArraySet> targetPackages) {
final long start = mStatLogger.getTime();
final ArrayList 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 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) +
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
};
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 白名单设置
只是doze白名单设置