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,溜了

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