Android P新特性 ---应用待机群组(#####笔记#####)

简介
Android 9 引入了一项新的电池管理功能,即应用待机群组。通过在原有的App Standby 功能中增加新的管理组(Active & Working set)实现。应用待机群组可以基于应用最近使用时间和使用频率,帮助系统排定应用请求资源的优先级。 根据使用模式,每个应用都会归类到五个优先级群组之一中。 系统将根据应用所属的群组限制每个应用可以访问的设备资源。

关于分组
五个群组按照以下特性将应用分组:
活跃(Active)
如果用户当前正在使用的应用满足以下任意一个条件,应用将被归到“活跃(Active)”群组中,例如:
应用已启动一个 Activity
应用正在运行前台服务
前台应用使用的 content provider应用
用户在应用中点击了某个通知
账户同步服务运行中

如果应用处于“活跃”群组,系统不会对应用的作业、报警或 FCM 消息施加任何限制。

工作集(Working Set)
如果应用经常运行,但当前未处于活跃状态,它将被归到“工作集”群组中。 例如,用户在大部分时间都启动的某个社交媒体应用可能就属于“工作集”群组。 如果应用被间接使用,它们也会被升级到“工作集”群组中 。
如果应用处于“工作集”群组,系统会对它运行作业和触发报警的能力施加轻度限制。

常用(Frequent)
如果应用会定期使用,但不是每天都必须使用,它将被归到“常用”群组中。 例如,用户在健身房运行的某个锻炼跟踪应用可能就属于“常用”群组。
如果应用处于“常用”群组,系统将对它运行作业和触发报警的能力施加较强的限制,也会对高优先级 FCM 消息的数量设定限制。

极少使用(Rare)
如果应用不经常使用,那么它属于“极少使用”群组。 例如,用户仅在入住酒店期间运行的酒店应用就可能属于“极少使用”群组。
如果应用处于“极少使用”群组,系统将对它运行作业、触发警报和接收高优先级 FCM 消息的能力施加严格限制。系统还会限制应用连接到网络的能力。

从未使用(Never)
安装但是从未运行过的应用会被归到“从未使用”群组中。 系统会对这些应用施加极强的限制。

关于运行时分组调整
简单区分为应用在待机群组中优先级的被动上调、被动下调和主动调整:
1.上调
A> 用户点击查看通知(u-ns)、点击通知(u-ui);
B> 用户切换应用时(u-mf、u-mb);
C> 正在提供系统更新服务的应用(u-su);
D> Content Provider被绑定(u-sa);
E> Instrumentation启动(u-si);
F> 账户同步服务启动,根据设备是否Idle模式判断分组(u-en、u-ed)
G> SliceManagerService服务上报的事件(u-lp、u-lv)

片段1(A、B、E、G):

void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
        if (!mAppIdleEnabled) return;
        ........
            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)) {
       ........
}

片段2(C):

void initializeDefaultsForSystemApps(int userId) {
     ..............
                    // past usage pattern was.
                    mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE,
                            REASON_SUB_USAGE_SYSTEM_UPDATE, 0,
                            elapsedRealtime + mSystemUpdateUsageTimeoutMillis);
 }

片段3(D):

 void reportContentProviderUsage(String authority, String providerPkgName, int userId) {...
mAppIdleHistory.reportUsage(packageName, userId,
                                STANDBY_BUCKET_ACTIVE,              REASON_SUB_USAGE_SYNC_ADAPTER,
                                0,
                                elapsedRealtime + mSyncAdapterTimeoutMillis);
...        }

片段4(F):

 void reportExemptedSyncScheduled(String packageName, int userId) {
        ...
            AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId,
                    bucketToPromote, usageReason,
                    0,
                    elapsedRealtime + durationMillis);
            maybeInformListeners(packageName, userId, elapsedRealtime,
                    appUsage.currentBucket, appUsage.bucketingReason, false);
        }
}

2.下调
A> timeout延迟检查,即当上调事件发生并影响应用优先级时,根据事件类型,设定timeout,在timeout时会对此应用做出对应的下调动作,简单理解为上调的有效期;

/** Minimum time a strong usage event should keep the bucket elevated. */
    long mStrongUsageTimeoutMillis; //1 小时
    /** Minimum time a notification seen event should keep the bucket elevated. */
    long mNotificationSeenTimeoutMillis; //12小时
    /** Minimum time a system update event should keep the buckets elevated. */
    long mSystemUpdateUsageTimeoutMillis; //2 小时
    /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
    long mPredictionTimeoutMillis; //12 小时

B> AppStandbyController 轮询,开机时由UsageStats服务注册的广播接收器监听用户启动,当用户启动会对该用户下所有的应用的分组情况执行一次检查更新,后每隔3H小时检查更新一次。

private class UserActionsReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
...
            } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                if (userId >=0) {
                    mAppStandby.postCheckIdleStates(userId);
                }
case MSG_CHECK_IDLE_STATES:
                    if (checkIdleStates(msg.arg1) && mAppIdleEnabled) {
                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
                                mCheckIdleIntervalMillis);
                    }
            break;

mCheckIdleIntervalMillis=+3h0m0s0ms

C> Usage Stats回归检查(防止长时间数据无法更新),在
UserUsageStatsService.reportEvvent ->
UserUsageStatsService.rolloverStats ->
UserUsageStatsService.rolloverStats ->
StatsUpdatedListener.onStatsReloaded ->
AppStandByController.postOneTimeCheckIdleStates ->
AppStandByController.checkIdleStates

     case MSG_ONE_TIME_CHECK_IDLE_STATES:
                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
                    waitForAdminData();
                    checkIdleStates(UserHandle.USER_ALL);
            break;

3.主动调整

主动调整会在UsageStats的日志文件中被载入为”p”,原因扩展为REASON_MAIN_PREDICTED,即预测。目前此类调整只在通过调用Usgae Stats服务提供的公开API才能实现。应该是留给GMS应用做AI引入的接口。

UsageStatsService.setAppStandbyBucket ->
AppStandByController.setAppStandbyBucket ->
AppIdleHistory.setAppStandbyBucket

注:
1,主动调整不能降低有效期内的Active和Working Set优先级;
2.主动调整不能调入或调出STANDBY_BUCKET_NEVER组;
3.不能设置高于STANDBY_BUCKET_ACTIVE优先级的群组

关于分组后的资源限制
资源限制在谷歌官网上解释如下:
这里写图片描述

代码均以实现UsageStatsManagerInternal.AppIdleStateChangeListener接口的方式完成Buckets监听,维护应用分组状态列表,实现控制。

1.关于Jobs
根据取回的bucket状态,选择推迟Jobs的开始时间,这一部分跟我们SpeedBooster关于Jobs限制方式一致。

 private boolean isReadyToBeExecutedLocked(JobStatus job) {
...
        if (!mInParole
                && !job.uidActive
                && !job.getJob().isExemptedFromAppStandby()) {
            final int bucket = job.getStandbyBucket();

                if (bucket >= mConstants.STANDBY_BEATS.length
                        || (mHeartbeat > appLastRan
                                && mHeartbeat < appLastRan + mConstants.STANDBY_BEATS[bucket])) {

                    return false;
...
 }

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
        };

在设置闹钟时对应用所属的组群进行检查:
AlarmManagerService->setImplLocked
AlarmManagerSerivice->adjustDeliveryTimeBasedOnStandbyBucketLocked
AlarmManagerService->getMinDelayForBucketLocked

  private boolean adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) {
       ...
        final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket(
                sourcePackage, sourceUserId, SystemClock.elapsedRealtime());
       ...
        if (lastElapsed > 0) {
            final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket);
       ...
}

3.关于Network

注册AppIdle的监听器,根据上报的应用状态更新UID网络访问权限(待深入):

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);//更新UID状态
                    updateRulesForPowerRestrictionsUL(uid);
                }
            } catch (NameNotFoundException nnfe) {
            }
        }
void updateRuleForAppIdleUL(int uid) {
        if (!isUidValidForBlacklistRules(uid)) return;

        if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
            Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateRuleForAppIdleUL: " + uid );
        }
        try {
            int appId = UserHandle.getAppId(uid);
            if (!mPowerSaveTempWhitelistAppIds.get(appId) && isUidIdle(uid)
                    && !isUidForegroundOnRestrictPowerUL(uid)) {//根据组群
                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DENY);
            } else {
                setUidFirewallRule(FIREWALL_CHAIN_STANDBY, uid, FIREWALL_RULE_DEFAULT);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
        }
    }

4.FCM限制

国内无法使用FCM服务。

一堆订单BUG,FFFFFFF,溜了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章