Android 9 引入了一项新的电池管理功能,即应用待机群组。应用待机群组可以基于应用最近使用时间和使用频率,帮助系统排定应用请求资源的优先级。 根据使用模式,每个应用都会归类到五个优先级群组之一中。 系统将根据应用所属的群组限制每个应用可以访问的设备资源。
五个群组按照以下特性将应用分组:
如果用户当前正在使用应用,应用将被归到“活跃”群组中,例如:
-
应用已启动一个 Activity
-
应用正在运行前台服务
-
应用的同步适配器与某个前台应用使用的 content provider 关联
-
用户在应用中点击了某个通知
如果应用处于“活跃”群组,系统不会对应用的作业、报警或 FCM 消息施加任何限制。
如果应用经常运行,但当前未处于活跃状态,它将被归到“工作集”群组中。 例如,用户在大部分时间都启动的某个社交媒体应用可能就属于“工作集”群组。 如果应用被间接使用,它们也会被升级到“工作集”群组中 。
如果应用处于“工作集”群组,系统会对它运行作业和触发报警的能力施加轻度限制。 如需了解详细信息,请参阅
电源管理限制
。
如果应用会定期使用,但不是每天都必须使用,它将被归到“常用”群组中。 例如,用户在健身房运行的某个锻炼跟踪应用可能就属于“常用”群组。
如果应用处于“常用”群组,系统将对它运行作业和触发报警的能力施加较强的限制,也会对高优先级 FCM 消息的数量设定限制。
如果应用不经常使用,那么它属于“极少使用”群组。 例如,用户仅在入住酒店期间运行的酒店应用就可能属于“极少使用”群组。
如果应用处于“极少使用”群组,系统将对它运行作业、触发警报和接收高优先级 FCM 消息的能力施加严格限制。系统还会限制应用连接到网络的能力。
安装但是从未运行过的应用会被归到“从未使用”群组中。 系统会对这些应用施加极强的限制。
系统设置各个app的bucket无非是为了限制各个app的资源,已达到节省功耗的目的。我们来看看这个流程的大致功能图:
这一节我们主要讲述了系统设置app的bucket,获取bucket,以及系统何时去检查app bucket的状态。我们先看下时序图:
2.1 设置bucket
这个功能的实现是在UsageStatsService中实现的,在这个service的onstart函数中创建了AppStandbyController对象,而且对于各个app设置自己属于哪个群组的对外接口是reportEvent函数。这个接口会在AMS以及NotificationManagerService中调用UsageStatsService的reportEvent,这个函数会调用AppStandbyController的reportEvent函数,我们来看下这个函数主要还是调用了AppUsageHistory的reportUsage函数。但是我们要注意其两个时间的参数不一样。这里后面有一个延迟消息很重要,主要就是多一段时间再来检测这个app的idle状态。我们来看这个延迟消息,当是Notification类型的消息的延迟时间为12小时,SYSTEM_INTERACTION为10分钟(这种主要是调用startInstrumentation函数),其他的mStrongUsageTimeoutMillis为1小时(正常应用启动)。
void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
if (!mAppIdleEnabled) return;//mAppIdleEnabled必须使能
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.MOVE_TO_FOREGROUND
|| event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
|| 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)) {
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);
nextCheckTime = mNotificationSeenTimeoutMillis;//12小时
} else if (event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION) {
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_ACTIVE, subReason,
0, elapsedRealtime + mSystemInteractionTimeoutMillis);
nextCheckTime = mSystemInteractionTimeoutMillis;//10分钟
} else {
mAppIdleHistory.reportUsage(appHistory, event.mPackage,
STANDBY_BUCKET_ACTIVE, subReason,
elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
nextCheckTime = mStrongUsageTimeoutMillis;//1小时
mHandler.sendMessageDelayed(mHandler.obtainMessage
(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, event.mPackage),
nextCheckTime);//延迟消息,检查Idle的状态
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);
下面我们再来看AppUsageHistory的reportUsage函数,这里主要就是设置每个app的状态的参数
public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName,
int newBucket, int usageReason, long elapsedRealtime, long timeout) {
// Set the timeout if applicable
if (timeout > elapsedRealtime) {//timeout就是用来检查app后续的状态
// Convert to elapsed timebase//这里非Active和working_set状态的不让设置timeout
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) {//设置的Bucket必须必以前的小
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的处理,是在checkAndUpdateStandbyState函数中处理。我们来看这个函数其实逻辑很简单,有一些特殊的app我们做特殊的处理。然后我们通过getBucketForLocked函数来获取当前app当前应该是属于哪个bucket,最后设置app新的bucket
private void checkAndUpdateStandbyState(String packageName, @UserIdInt int userId,
int uid, long elapsedRealtime) {
......
final boolean isSpecial = isAppSpecial(packageName,
UserHandle.getAppId(uid),
userId);
if (isSpecial) {//特殊app,比如系统app,带android字眼的app等,这些app做特殊处理
synchronized (mAppIdleLock) {
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime,
STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
maybeInformListeners(packageName, userId, elapsedRealtime,
STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT, false);
} else {
synchronized (mAppIdleLock) {
final AppIdleHistory.AppUsageHistory app =
mAppIdleHistory.getAppUsageHistory(packageName,
userId, elapsedRealtime);
int reason = app.bucketingReason;
final int oldMainReason = reason & REASON_MAIN_MASK;
// If the bucket was forced by the user/developer, leave it alone.
// A usage event will be the only way to bring it out of this forced state
if (oldMainReason == REASON_MAIN_FORCED) {
return;
final int oldBucket = app.currentBucket;
int newBucket = Math.max(oldBucket, STANDBY_BUCKET_ACTIVE); // Undo EXEMPTED
boolean predictionLate = predictionTimedOut(app, elapsedRealtime);
// Compute age-based bucket
if (oldMainReason == REASON_MAIN_DEFAULT
|| oldMainReason == REASON_MAIN_USAGE
|| oldMainReason == REASON_MAIN_TIMEOUT
|| predictionLate) {
if (!predictionLate && app.lastPredictedBucket >= STANDBY_BUCKET_ACTIVE
&& app.lastPredictedBucket <= STANDBY_BUCKET_RARE) {
newBucket = app.lastPredictedBucket;
reason = REASON_MAIN_PREDICTED | REASON_SUB_PREDICTED_RESTORED;
if (DEBUG) {
Slog.d(TAG, "Restored predicted newBucket = " + newBucket);
} else {
newBucket = getBucketForLocked(packageName, userId,//该函数就是获取当前app的Bucket
elapsedRealtime);
if (DEBUG) {
Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket);
reason = REASON_MAIN_TIMEOUT;
......
if (oldBucket < newBucket || predictionLate) {
mAppIdleHistory.setAppStandbyBucket(packageName, userId,//设置新状态
elapsedRealtime, newBucket, reason);
maybeInformListeners(packageName, userId, elapsedRealtime,
newBucket, reason, false);
这里核心函数无非就是getBucketForLocked函数,这个函数就是调用了AppIdleHistoryLocked的getThresholdIndex函数。然后返回一个index,再从THRESHOLD_BUCKETS获取bucket。
@StandbyBuckets int getBucketForLocked(String packageName, int userId,
long elapsedRealtime) {
int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
return THRESHOLD_BUCKETS[bucketIndex];
我们来看下THRESHOLD_BUCKETS数组就是index对应的各个bucket。
static final int[] THRESHOLD_BUCKETS = {
STANDBY_BUCKET_ACTIVE,
STANDBY_BUCKET_WORKING_SET,
STANDBY_BUCKET_FREQUENT,
STANDBY_BUCKET_RARE
我们先来看这两个值,这两个值就是mAppStandbyScreenThresholds和mAppStandbyElapsedThresholds的值,这里COMPRESS_TIME是false。
static final long[] SCREEN_TIME_THRESHOLDS = {
COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR
static final long[] ELAPSED_TIME_THRESHOLDS = {
COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR,
COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR,
COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR
我们接下来AppIdleHistoryLocked的getThresholdIndex函数,获取app之前的lastUsedScreenTime和lastUsedElapsedTime。然后计算到现在的差值,得出两个值一个就是上一次屏幕亮屏的时间到现在屏幕亮度的时间差;上一次开机的时间和这一次距离开机的时间差。这两个差值和我们上面的数组对比,降序比,哪一列值都大,那一列就是当前的bucket。
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;
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
然后比第一种情况下的就是active的bucket。
而这个延迟消息的用处主要就是当timeout之后将app的bucket从Active降到working_set。而将app的bucket设置为Frequent或者rare我们还需看2.3节,系统会每隔一天来检查所有的app的bucket。
2.2 获取bucket
获取app的bucket我们从UsageStatsService的getAppStandbyBucket到AppStandbyController的getAppStandbyBucket函数,到AppIdleHistory的getAppStandbyBucket函数就是获取其currentBucket的值。
public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory =
getPackageHistory(userHistory, packageName, elapsedRealtime, true);
return appUsageHistory.currentBucket;
2.3 检查各个App的bucket
前面我们通过延迟消息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。
public void onStatsReloaded() {
mAppStandby.postOneTimeCheckIdleStates();
AppStandbyController的postOneTimeCheckIdleStates函数如下,因为这个时候已经开机,因此发送了一个MSG_ONE_TIME_CHECK_IDLE_STATES消息。
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;
该函数就是当前userId所有的app都会调用checkAndUpdateStandbyState函数来检查现在app的bucket。因为这个距离上一次检查隔了一天,很多app的bucket可能已经是frequent或者rare了。
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;
2.4 应用待机模式
之前在android App Standby(应用待机模式)在这里和bucket合并了。也就是说之前的应用待机模式就是app的bucket为rare以及以上bucket(就是rare和never)。
在UsageStatsService的setAppInactive函数,最后是通过AppStandbyController的消息发送到forceIdleState函数中调用了AppIdleHistory.setIdle函数,下面设置为idle直接将app的bucket设置为rare,如果idle为false,就是设置app的bucket为active。
这里就是之前的App standby(之前使用时间的方式)这里和bucket合并起来了。
public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
elapsedRealtime, true);
if (idle) {
appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
appUsageHistory.bucketingReason = REASON_MAIN_FORCED;
} else {
appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
appUsageHistory.bucketingReason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION;
return appUsageHistory.currentBucket;
而查看app是否为idle,就是看该app的bucket是否为rare以上状态(就是rare和never),其他状态都是false。
public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
AppUsageHistory appUsageHistory =
getPackageHistory(userHistory, packageName, elapsedRealtime, true);
if (appUsageHistory == null) {
return false; // Default to not idle
} else {
return appUsageHistory.currentBucket >= STANDBY_BUCKET_RARE;
// Whether or not it's passed will now be externally calculated and the
// bucket will be pushed to the history using setAppStandbyBucket()
//return hasPassedThresholds(appUsageHistory, elapsedRealtime);
2.5 配置
这里面所有的时间都是可以配置的。bucket是否enable也可以配置,看下面函数需要config_enableAutoPowerModes Global.APP_STANDBY_ENBALE同时开启bucket才会生效。
boolean isAppIdleEnabled() {
final boolean buildFlag = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
final boolean runtimeFlag = Global.getInt(mContext.getContentResolver(),
Global.APP_STANDBY_ENABLED, 1) == 1
&& Global.getInt(mContext.getContentResolver(),
Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, 1) == 1;
return buildFlag && runtimeFlag;
这节主要介绍系统将根据应用所属的群组限制每个应用可以访问的设备资源。
这里我们先看一张资源限制表格,具体地址:https://developer.android.google.cn/topic/performance/power/power-details
Setting | Jobs * | Alarms † | Network ‡ | Firebase Cloud Messaging § |
---|
User Restricts Background Activity | | | | |
Restrictions enabled: | Never | Never | Never | No restriction |
Doze | | | | |
Doze active: | Deferred to window | Regular alarms: Deferred to window While-idle alarms: Deferred up to 9 minutes | Deferred to window | High priority: No restriction Normal priority: Deferred to window |
App Standby Buckets (by bucket) | | | | |
Active: | No restriction | No restriction | No restriction | No restriction |
Working set: | Deferred up to 2 hours | Deferred up to 6 minutes | No restriction | No restriction |
Frequent: | Deferred up to 8 hours | Deferred up to 30 minutes | No restriction | High priority: 10/day |
Rare: | Deferred up to 24 hours | Deferred up to 2 hours | Deferred up to 24 hours | High priority: 5/day |
3.1 Alarm
Alarm的限制主要在设置alarm的时候,重新调整了发送时间。
主要在setImplLocked函数中,调用了adjustDeliveryTimeBasedOnStandbyBucketLocked函数。还有其他一些地方会调用了这个函数,比如当app_standby_bucket配置改变等。
我们来看这个函数其实就是通过改变alarm的whenElapsed来改变触发时间,主要是通过getMinDelayForBucketLocked函数就是获取alarm发送的延迟时间。
private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(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, SystemClock.elapsedRealtime());
final Pair<String, Integer> packageUser = Pair.create(sourcePackage, sourceUserId);
final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L);//获取上一次的发送alarm的时间
if (lastElapsed > 0) {
final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);//根据bucket获取下一次发送的时间
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);
我们来看下下面的表格。getMinDelayForBucketLocked就是根据不同的bucket来获取不同的alarm延迟时间,从0,6分钟,半小时,2小时。最后达到一个延迟发送alarm的目的。
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
3.2 网络
是通过UsageStatsService的isAppIdle接口来判断是否当前应用处于应用待机模式,也就是app的bucket的为rare以上。这个时候会限制网络,通过iptables 制作rule限制uid的方式。
3.3 JobSchedulerService
这个后续补充
您可以使用 ADB 为您的应用手动指定应用待机群组。 要更改应用的群组,请使用以下命令:
$ adb shell am set-standby-bucket packagename active|working_set|frequent|rare
您还可以使用该命令一次设置多个软件包:
$ adb shell am set-standby-bucket package1 bucket1 package2 bucket2...
要检查应用处于哪一个群组,请运行以下命令:
$ adb shell am get-standby-bucket [packagename]
如果您不传递 packagename 参数,命令将列出所有应用的群组。
一、概述 Android 9 引入了一项新的电池管理功能,即应用待机群组。应用待机群组可以基于应用最近使用时间和使用频率,帮助系统排定应用请求资源的优先级。 根据使用模式,每个应用都会归类到五个优先级群组之一中。 系统将根据应用所属的群组限制每个应用可以访问的设备资源。五个群组按照以下特性将应用分组:活跃如果用户当前正在使用应用,应用将被归到“活跃”群组中,例如:应用已启动一个...
开机启动时,首先执行PhoneWindowManager.systemReady()(这之前的流程不分析)。调用KeyguardViewMediator.onSystemReady()进行待机锁屏及解锁逻辑。
KeyguardViewMediator是整个待机解/锁屏业务的调度器,负责调度锁屏界面的相关动作及查询
Android和kernel间,通过设备节点建立关联
待机API由kernel建立提供,待机请求由android通过节点向kernel发送。
# ls sys/android_power/
state
request_state
acquire_full_wake_lock
acquire_partial_wake_lock
release_wake_lock
这些节点中,stat
Android9.x应用待机群组特性导致后台应用无法联网问题分析9.x增加的电源管理新特性1 应用分组2 查看和修改分组3 省电管理白名单源码分析
9.x增加的电源管理新特性
我们在工作中发现一个问题,我们的一个应用,启动时只启动一个后台服务,而不会启动界面。并且在后台服务中网络请求超时。但是如果启动一个activity之后,就可以正常联网。
Android P(9.x)为了更加严格的限制后台应用...
Andriod后台限制汇总
从Android开发者网站上,目前看到4种限制机制,分别是低电耗模式,应用待机模式,应用待机分组和省电模式。本文对这些后台限制模式进行简单的汇总。
该文章的内容均来自于Android开发者网站。对于这些限制,本人没有全部实际测试,只是为了记录。
此外,每个手机厂商可能会定制一些自己的逻辑,所以可能和Android开发者网站的描述可能不一致。
低电耗模式
低电耗模式就是doze模式。Android开发者网站对低电耗模式的描述如下:
如果用户未插接设备的电源,在屏幕关闭的情况下,让
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License.
Android 9.0引入了一些限制,以帮助优化设备的电池寿命和性能。这些限制包括了对应用在后台运行的限制,这可能会影响应用的保活。
然而,您可以采取一些措施来尽可能使应用在后台保持活动状态。以下是一些常见的方法:
1. 前台服务:将应用的某些功能放在前台服务中运行,这样可以提高应用的优先级并减少限制。
2. JobScheduler API:使用JobScheduler API来安排应用在特定时间执行任务,以避免在应用处于后台时执行耗时操作。
3. 后台限制排除:您可以请求后台限制排除,以便在某些情况下允许您的应用在后台运行更长时间。但是,这需要用户同意。
4. 使用AlarmManager:如果您需要在特定时间执行任务,可以使用AlarmManager来安排应用在后台唤醒并执行操作。
请注意,不建议滥用保活机制,因为这可能会对用户的设备性能和电池寿命产生负面影响。只有在确实需要保持应用活动状态时才应使用这些方法,并且要遵循Android开发者指南中的最佳实践。