Android之App Standby分析

概述

從 Android 6.0(API 級(jí)別 23)開(kāi)始危虱,Android 引入了兩項(xiàng)省電功能,通過(guò)管理應(yīng)用在設(shè)備未連接至電源時(shí)的行為方式,幫助用戶(hù)延長(zhǎng)電池壽命。
應(yīng)用待機(jī)模式會(huì)延遲用戶(hù)近期未與之交互的應(yīng)用的后臺(tái)網(wǎng)絡(luò)活動(dòng)。

應(yīng)用待機(jī)模式允許系統(tǒng)判定應(yīng)用在用戶(hù)未主動(dòng)使用它時(shí)是否處于閑置狀態(tài)徘键。當(dāng)用戶(hù)有一段時(shí)間未觸摸應(yīng)用時(shí),系統(tǒng)便會(huì)作出此判定,以下條件均不適用:

1.用戶(hù)明確啟動(dòng)應(yīng)用酱固。
2.應(yīng)用當(dāng)前有一個(gè)進(jìn)程在前臺(tái)運(yùn)行(作為活動(dòng)或前臺(tái)服務(wù),或者正在由其他活動(dòng)或前臺(tái)服務(wù)使用)头朱。
注意:您只能將前臺(tái)服務(wù)用于用戶(hù)希望系統(tǒng)立即執(zhí)行或不中斷的任務(wù)运悲。 此類(lèi)情況包括將照片上傳到社交媒體,或者即使在音樂(lè)播放器應(yīng)用不在前臺(tái)運(yùn)行時(shí)也能播放音樂(lè)项钮。您不應(yīng)該只是為了阻止系統(tǒng)判定您的應(yīng)用處于閑置狀態(tài)而啟動(dòng)前臺(tái)服務(wù)班眯。

3.應(yīng)用生成用戶(hù)可在鎖定屏幕或通知欄中看到的通知。
4.應(yīng)用是正在使用中的設(shè)備管理應(yīng)用(例如設(shè)備政策控制器)烁巫。雖然設(shè)備管理應(yīng)用通常在后臺(tái)運(yùn)行署隘,但永遠(yuǎn)不會(huì)進(jìn)入應(yīng)用待機(jī)模式,因?yàn)樗鼈儽仨毐3挚捎眯匝窍叮员汶S時(shí)從服務(wù)器接收策略磁餐。
當(dāng)用戶(hù)將設(shè)備插入電源時(shí),系統(tǒng)會(huì)從待機(jī)狀態(tài)釋放應(yīng)用阿弃,允許它們自由訪問(wèn)網(wǎng)絡(luò)并執(zhí)行任何待處理的作業(yè)和同步诊霹。如果設(shè)備長(zhǎng)時(shí)間處于閑置狀態(tài)羞延,系統(tǒng)將允許閑置應(yīng)用訪問(wèn)網(wǎng)絡(luò),頻率大約每天一次脾还。

adb查詢(xún)

adb shell dumpsys usagestats

app接口

UsageStatsManager stats = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE) 

代碼簡(jiǎn)要分析

frameworks/base/services/core/java/com/android/server/SystemServer.java
frameworks/base/services/core/java/com/android/server/usage/UsageStatsService.java
frameworks/base/services/core/java/com/android/server/usage/AppIdleHistory.java

注:
1)7.1

1.啟動(dòng)
SystemServer.java::ServerThread:run --> startCoreServices

    private void startCoreServices() {
        ···
        // Tracks application usage stats.
        mSystemServiceManager.startService(UsageStatsService.class);
        mActivityManagerService.setUsageStatsManager(
                LocalServices.getService(UsageStatsManagerInternal.class));
        ···
    }

2.UsageStatsService分析
1)UsageStatsService extends SystemService
2)onStart()

    public void onStart() {
        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
        mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
        mPackageManager = getContext().getPackageManager();
        mHandler = new H(BackgroundThread.get().getLooper());

        File systemDataDir = new File(Environment.getDataDirectory(), "system");
        mUsageStatsDir = new File(systemDataDir, "usagestats");
        mUsageStatsDir.mkdirs();
        if (!mUsageStatsDir.exists()) {
            throw new IllegalStateException("Usage stats directory does not exist: "
                    + mUsageStatsDir.getAbsolutePath());
        }

        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
        filter.addAction(Intent.ACTION_USER_STARTED);
        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                null, mHandler);

        IntentFilter packageFilter = new IntentFilter();
        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        packageFilter.addDataScheme("package");

        getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
                null, mHandler);

       //config_enableAutoPowerModes跟Doze模式共用一個(gè)配置文件肴楷,釋放開(kāi)啟app standby功能
        mAppIdleEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);
        if (mAppIdleEnabled) {
            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
            getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
        }

        synchronized (mLock) {
            cleanUpRemovedUsersLocked();
            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
        }

        //開(kāi)機(jī)時(shí)長(zhǎng)記錄
        mRealTimeSnapshot = SystemClock.elapsedRealtime();
        //系統(tǒng)時(shí)長(zhǎng)記錄,可通過(guò)網(wǎng)絡(luò)荠呐、手動(dòng)切換的時(shí)間格式
        mSystemTimeSnapshot = System.currentTimeMillis();

        //注冊(cè)共享服務(wù)
        publishLocalService(UsageStatsManagerInternal.class, new LocalService());
        publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
    }

onStart做了一些初始化及監(jiān)聽(tīng)業(yè)務(wù)

3)onBootPhase

    public void onBootPhase(int phase) {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
            // Observe changes to the threshold
            // 初始化appidle臨界值的變化赛蔫。adb查詢(xún)方式:adb shell settings get global app_idle_constants
            // 格式:idle_duration2=long,wallclock_threshold=long,parole_interval=long,parole_duration=long
            // long為數(shù)據(jù)類(lèi)型。臨界值可自定義
            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
            settingsObserver.registerObserver();
            settingsObserver.updateSettings();

            mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
            mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
            mBatteryStats = IBatteryStats.Stub.asInterface(
                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
            mDisplayManager = (DisplayManager) getContext().getSystemService(
                    Context.DISPLAY_SERVICE);
            mPowerManager = getContext().getSystemService(PowerManager.class);

            //監(jiān)聽(tīng)屏幕狀態(tài)
            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
            synchronized (mLock) {
                //更新appidle數(shù)據(jù)
                mAppIdleHistory.updateDisplayLocked(isDisplayOn(), SystemClock.elapsedRealtime());
            }

            if (mPendingOneTimeCheckIdleStates) {
                //檢查各個(gè)idle狀態(tài)
                postOneTimeCheckIdleStates();
            }

            mSystemServicesReady = true;
        } else if (phase == PHASE_BOOT_COMPLETED) {
            setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
        }
    }

如果說(shuō)onStart()初始化業(yè)務(wù)泥张,那onBootPhase是更新業(yè)務(wù)呵恢,核心點(diǎn)是mAppIdleHistory中數(shù)據(jù)的更新

3.臨界數(shù)據(jù)分析

    //應(yīng)用是否進(jìn)入idle狀態(tài)的臨界值,這里比較的是亮屏狀態(tài)的時(shí)間媚创。默認(rèn)12小時(shí)
    long mAppIdleScreenThresholdMillis;
    
    //定時(shí)檢查app idle狀態(tài)渗钉。默認(rèn)3小時(shí)
    long mCheckIdleIntervalMillis;
    
    //應(yīng)用是否進(jìn)入idle狀態(tài)的臨界值,這里比較的累計(jì)開(kāi)機(jī)狀態(tài)的時(shí)間钞钙。默認(rèn)兩天
    long mAppIdleWallclockThresholdMillis;
    
    //相鄰 2 次進(jìn)入假釋狀態(tài)的時(shí)間間隔鳄橘。默認(rèn)1天
    long mAppIdleParoleIntervalMillis;
    
    //假釋狀態(tài)的持續(xù)時(shí)間 10分鐘
    long mAppIdleParoleDurationMillis;

以下是數(shù)據(jù)的來(lái)源:

    private class SettingsObserver extends ContentObserver {
        /**
         * This flag has been used to disable app idle on older builds with bug b/26355386.
         */
        @Deprecated
        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";

        private static final String KEY_IDLE_DURATION = "idle_duration2";
        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
        private static final String KEY_PAROLE_DURATION = "parole_duration";

        private final KeyValueListParser mParser = new KeyValueListParser(',');

        SettingsObserver(Handler handler) {
            super(handler);
        }

        void registerObserver() {
            getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
        }

        @Override
        public void onChange(boolean selfChange) {
            updateSettings();
            postOneTimeCheckIdleStates();
        }

       //數(shù)據(jù)來(lái)源
        void updateSettings() {
            synchronized (mLock) {
                // Look at global settings for this.
                // TODO: Maybe apply different thresholds for different users.
                try {
                    mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
                            Settings.Global.APP_IDLE_CONSTANTS));
                } catch (IllegalArgumentException e) {
                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
                    // fallthrough, mParser is empty and all defaults will be returned.
                }

                // Default: 12 hours of screen-on time sans dream-time
                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
                       COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);

                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days

                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours

                // Default: 24 hours between paroles
                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);

                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
                        mAppIdleScreenThresholdMillis);
            }
        }
    }

4.案例分析
app的Activity調(diào)用onResume的數(shù)據(jù)采集(onPause也會(huì)進(jìn)行數(shù)據(jù)采集,這樣對(duì)等起來(lái))
1)ActivityStackSupervisor.reportResumedActivityLocked

    boolean reportResumedActivityLocked(ActivityRecord r) {
        final ActivityStack stack = r.task.stack;
        if (isFocusedStack(stack)) {
            mService.updateUsageStats(r, true);//調(diào)用AMS更新app使用情況
        }
        ···
        return false;
    }
    
ActivityManagerService
    void updateUsageStats(ActivityRecord component, boolean resumed) {
        ···
        if (resumed) {
            if (mUsageStatsService != null) {
                mUsageStatsService.reportEvent(component.realActivity, component.userId,
                        UsageEvents.Event.MOVE_TO_FOREGROUND);//通知UsageStatsService
            }
            ···
        } else {
            ···
        }
    }

通過(guò)AMS通知UsageStatsService更新app數(shù)據(jù)

2)UsageStatsService接收通知進(jìn)行更新

    private final class LocalService extends UsageStatsManagerInternal {

        @Override
        public void reportEvent(ComponentName component, int userId, int eventType) {
            if (component == null) {
                Slog.w(TAG, "Event reported without a component name");
                return;
            }

            UsageEvents.Event event = new UsageEvents.Event();
            event.mPackage = component.getPackageName();
            event.mClass = component.getClassName();

            // This will later be converted to system time.
            event.mTimeStamp = SystemClock.elapsedRealtime();

            event.mEventType = eventType;
            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();//有handler處理
        }
        ···
    }
    
    class H extends Handler
        case MSG_REPORT_EVENT:
            reportEvent((UsageEvents.Event) msg.obj, msg.arg1);
            break;
    
    void reportEvent(UsageEvents.Event event, int userId) {
        synchronized (mLock) {
            //獲取timeNow的函數(shù)芒炼,做了兩件事情:1.直接給timeNow值 2.判斷系統(tǒng)的時(shí)間是否改變
            final long timeNow = checkAndGetTimeLocked();
            final long elapsedRealtime = SystemClock.elapsedRealtime();
            convertToSystemTimeLocked(event);

            //通過(guò)userId獲取對(duì)應(yīng)的UserUsageStatsService
            final UserUsageStatsService service =
                    getUserDataAndInitializeIfNeededLocked(userId, timeNow);
            // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
            // about apps that are on some kind of whitelist anyway.
            //判斷app是否為idle狀態(tài)
            final boolean previouslyIdle = mAppIdleHistory.isIdleLocked(
                    event.mPackage, userId, elapsedRealtime);
            service.reportEvent(event);
            // Inform listeners if necessary
            if ((event.mEventType == Event.MOVE_TO_FOREGROUND
                    || event.mEventType == Event.MOVE_TO_BACKGROUND
                    || event.mEventType == Event.SYSTEM_INTERACTION
                    || event.mEventType == Event.USER_INTERACTION)) {
                mAppIdleHistory.reportUsageLocked(event.mPackage, userId, elapsedRealtime);
                //如果以前是idle狀態(tài)瘫怜,則通知監(jiān)聽(tīng)者進(jìn)行策略更新
                if (previouslyIdle) {
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                            /* idle = */ 0, event.mPackage));
                    notifyBatteryStats(event.mPackage, userId, false);
                }
            }
        }
    }

這一階段是業(yè)務(wù)的核心之處,有4處可說(shuō)本刽,3處值得好好分析
第一處:timeNow

    //方法的目的是監(jiān)測(cè)系統(tǒng)時(shí)間是否有更新
    //如果有更新鲸湃,則通知各個(gè)UserUsageStatsService更新數(shù)據(jù)
    private long checkAndGetTimeLocked() {
        final long actualSystemTime = System.currentTimeMillis();//當(dāng)前系統(tǒng)時(shí)間
        final long actualRealtime = SystemClock.elapsedRealtime();//當(dāng)前開(kāi)機(jī)時(shí)長(zhǎng)
        
        //期望系統(tǒng)時(shí)間=當(dāng)前開(kāi)機(jī)時(shí)長(zhǎng) - 上一次記錄的開(kāi)機(jī)時(shí)長(zhǎng) + 上一次的系統(tǒng)時(shí)間
        //換一句話(huà)說(shuō),期望系統(tǒng)時(shí)間=上一次的系統(tǒng)時(shí)間+距離當(dāng)前時(shí)間的時(shí)間差
        final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
        
        //當(dāng)前系統(tǒng)時(shí)間-期望系統(tǒng)時(shí)間 > 時(shí)間臨界值
        //說(shuō)明系統(tǒng)時(shí)間已經(jīng)有更新子寓,此時(shí)應(yīng)該通知各個(gè)UserUsageStatsService更新數(shù)據(jù)
        final long diffSystemTime = actualSystemTime - expectedSystemTime;
        if (Math.abs(diffSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS) {//
            // The time has changed.
            Slog.i(TAG, "Time changed in UsageStats by " + (diffSystemTime / 1000) + " seconds");
            final int userCount = mUserState.size();
            for (int i = 0; i < userCount; i++) {
                final UserUsageStatsService service = mUserState.valueAt(i);
                service.onTimeChanged(expectedSystemTime, actualSystemTime);
            }
            mRealTimeSnapshot = actualRealtime;
            mSystemTimeSnapshot = actualSystemTime;
        }
        return actualSystemTime;
    }

第二處:判斷app是否為idle狀態(tài)

AppIdleHistory.isIdleLocked

    public boolean isIdleLocked(String packageName, int userId, long elapsedRealtime) {
        //從mIdleHistory獲取對(duì)應(yīng)userid的map
        ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
        
        //從userHistory中獲取對(duì)應(yīng)pkg的數(shù)據(jù)結(jié)構(gòu):PackageHistory
        PackageHistory packageHistory =
                getPackageHistoryLocked(userHistory, packageName, elapsedRealtime);
        if (packageHistory == null) {
            return false; // Default to not idle
        } else {
            //根據(jù)packageHistory判斷是否是idle狀態(tài)
            return hasPassedThresholdsLocked(packageHistory, elapsedRealtime);
        }
    }
    
    private boolean hasPassedThresholdsLocked(PackageHistory packageHistory, long elapsedRealtime) {
        //臨界值終于發(fā)揮作用了:
        //mScreenOnTimeThreshold對(duì)應(yīng)UsageStatsService.mAppIdleScreenThresholdMillis
        //mScreenOnTimeThreshold對(duì)應(yīng)UsageStatsService.mAppIdleWallclockThresholdMillis
        return (packageHistory.lastUsedScreenTime
                    <= getScreenOnTimeLocked(elapsedRealtime) - mScreenOnTimeThreshold)
                && (packageHistory.lastUsedElapsedTime
                        <= getElapsedTimeLocked(elapsedRealtime) - mElapsedTimeThreshold);
    }

第三處:AppIdleHistory的數(shù)據(jù)記錄

/data/system/screen_on_time
0     --- 開(kāi)機(jī)之后的亮屏幕的時(shí)間
55017962   --- 從第一次開(kāi)機(jī)累計(jì)的開(kāi)機(jī)時(shí)長(zhǎng)

app idle一列表
/data/system/users/0/app_idle_stats.xml


UsageStatsManager.queryUsageStats 可查詢(xún)?nèi)缦滦畔?/data/system/usagestats/0/   
例如:
jj(S0):/data/system/usagestats/0 # ls
daily monthly version weekly yearly

第四處:UsageStatsService通知監(jiān)聽(tīng)者

class H extends Handler 
                case MSG_INFORM_LISTENERS:
                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
                    break;
                    
    void informListeners(String packageName, int userId, boolean isIdle) {
        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
            listener.onAppIdleStateChanged(packageName, userId, isIdle);
        }
    }

此時(shí)NetworkPolicyManagerService接收到命令:
NetworkPolicyManagerService在systemReady就注冊(cè)了監(jiān)聽(tīng):
      mUsageStats.addAppIdleStateChangeListener(new AppIdleStateChangeListener());

    private class AppIdleStateChangeListener
            extends UsageStatsManagerInternal.AppIdleStateChangeListener {

        @Override
        public void onAppIdleStateChanged(String packageName, int userId, boolean idle) {
            try {
                final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                        PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
                if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
                synchronized (mUidRulesFirstLock) {
                    updateRuleForAppIdleUL(uid);//控制網(wǎng)絡(luò)
                    updateRulesForPowerRestrictionsUL(uid);
                }
            } catch (NameNotFoundException nnfe) {
            }
        }
        ···
    }
    
    void updateRuleForAppIdleUL(int uid) {
        if (!isUidValidForBlacklistRules(uid)) return;

        int appId = UserHandle.getAppId(uid);
        //不在白名單 + app在idle + 不是前臺(tái)業(yè)務(wù)
        //ture 進(jìn)行網(wǎng)絡(luò)限制
        //false 保持默認(rèn)狀態(tài) 
        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);
        }
    }
    
    private void setUidFirewallRule(int chain, int uid, int rule) {
        ···
        try {
            //通過(guò)NetworkManagementService控制網(wǎng)絡(luò)
            mNetworkManager.setFirewallUidRule(chain, uid, rule);
        } catch (IllegalStateException e) {
            Log.wtf(TAG, "problem setting firewall uid rules", e);
        } catch (RemoteException e) {
            // ignored; service lives in system_server
        }
    }

5.總結(jié)

1.UsageStatsService本身不帶白名單機(jī)制暗挑,它主要負(fù)責(zé)把信息分發(fā)出去,讓執(zhí)行者判斷斜友。例如NetworkPolicyManagerService根據(jù)自身的白名單做業(yè)務(wù)處理

2.UsageStatsService有控制開(kāi)關(guān)炸裆。framework-res.apk的config_enableAutoPowerModes

3.UsageStatsService是被動(dòng)調(diào)用者,例如AMS鲜屏、NMS調(diào)用它烹看,它根據(jù)實(shí)際事情分發(fā)給對(duì)應(yīng)的監(jiān)聽(tīng)者NetworkPolicy、JobSchedulerService等等

3.對(duì)于檢查app是否為idle墙歪,也有一些名單強(qiáng)制控制听系。具體如下:
    private boolean isAppIdleFiltered(String packageName, int appId, int userId,
            long elapsedRealtime) {
        if (packageName == null) return false;
        // If not enabled at all, of course nobody is ever idle.
        if (!mAppIdleEnabled) { //app Standby開(kāi)關(guān)
            return false;
        }
        if (appId < Process.FIRST_APPLICATION_UID) {//系統(tǒng)應(yīng)用沒(méi)有idle業(yè)務(wù)
            // System uids never go idle.
            return false;
        }
        if (packageName.equals("android")) { //包名為android的app沒(méi)有idle
            // Nor does the framework (which should be redundant with the above, but for MR1 we will
            // retain this for safety).
            return false;
        }
        if (mSystemServicesReady) {
            try {
                // We allow all whitelisted apps, including those that don't want to be whitelisted
                // for idle mode, because app idle (aka app standby) is really not as big an issue
                // for controlling who participates vs. doze mode.
                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
                    return false;
                }
            } catch (RemoteException re) {
                throw re.rethrowFromSystemServer();
            }

            if (isActiveDeviceAdmin(packageName, userId)) {
                return false;
            }

            if (isActiveNetworkScorer(packageName)) {
                return false;
            }

            if (mAppWidgetManager != null
                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {//widget
                return false;
            }

            if (isDeviceProvisioningPackage(packageName)) {//開(kāi)機(jī)向?qū)?                return false;
            }
        }

        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
            return false;
        }

        // Check this last, as it is the most expensive check
        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
        if (isCarrierApp(packageName)) {//通信app
            return false;
        }

        return true;
    }

原生設(shè)置案例

9.x
1.原生settings中,有一個(gè)界面:
UsageStatsActivity.java
統(tǒng)計(jì)apk使用情況
mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
        cal.getTimeInMillis(), System.currentTimeMillis());

2.調(diào)用的是UsageStatsManager.java
UsageStatsManager usm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE)

ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime,
        endTime, mContext.getOpPackageName());


3.跨進(jìn)程調(diào)用的服務(wù)為UsageStatsService.java

final UserUsageStatsService service =
        getUserDataAndInitializeIfNeededLocked(userId, timeNow);
List<UsageStats> list = service.queryUsageStats(bucketType, beginTime, endTime);
if (list == null) {
    return null;
}

4.而最終實(shí)現(xiàn)的地方在UserUsageStatsService中
List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
    return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
}

5.數(shù)據(jù)真正處理的地方在UsageStatsDatabase中虹菲,也就是
/data/system/usagestats/用戶(hù)/daily或weekly或monthly或yearly
例如靠胜,/data/system/usagestats/0/daily或weekly或monthly或yearly

6.UsageStatsService中關(guān)聯(lián)了AppStandbyController,生成了一個(gè)對(duì)象。
AppStandbyController對(duì)象被處理,主要由UsageStatsService操作

7.AppStandbyController核心的是AppIdleHistory對(duì)象mAppIdleHistory浪漠,此對(duì)象
涉及到一個(gè)文件:app_idle_stats.xml陕习,具體目錄為:/data/system/users/用戶(hù)/app_idle_stats.xml
注:可以通過(guò)adb指令生成最新?tīng)顟B(tài)的xml文件
adb shell dumpsys usagestats flush

參考學(xué)習(xí)

https://developer.android.com/training/monitoring-device-state/doze-standby
https://blog.csdn.net/liu362732346/article/details/113107337
https://lishuaiqi.top/2017/01/03/UsageStats1-usageStatsServiceStartProcess/#1-new-
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市址愿,隨后出現(xiàn)的幾起案子该镣,更是在濱河造成了極大的恐慌,老刑警劉巖响谓,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件损合,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡娘纷,警方通過(guò)查閱死者的電腦和手機(jī)嫁审,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赖晶,“玉大人律适,你說(shuō)我怎么就攤上這事《舨澹” “怎么了捂贿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)胳嘲。 經(jīng)常有香客問(wèn)我厂僧,道長(zhǎng),這世上最難降的妖魔是什么胎围? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任吁系,我火速辦了婚禮,結(jié)果婚禮上白魂,老公的妹妹穿的比我還像新娘。我一直安慰自己上岗,他們只是感情好福荸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著肴掷,像睡著了一般敬锐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呆瞻,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天台夺,我揣著相機(jī)與錄音,去河邊找鬼痴脾。 笑死颤介,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滚朵,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼冤灾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了辕近?” 一聲冷哼從身側(cè)響起韵吨,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎移宅,沒(méi)想到半個(gè)月后归粉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漏峰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年盏浇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芽狗。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绢掰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出童擎,到底是詐尸還是另有隱情滴劲,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布顾复,位于F島的核電站班挖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏芯砸。R本人自食惡果不足惜萧芙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望假丧。 院中可真熱鬧双揪,春花似錦、人聲如沸包帚。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)渴邦。三九已至疯趟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谋梭,已是汗流浹背信峻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瓮床,地道東北人盹舞。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓产镐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親矾策。 傳聞我的和親對(duì)象是個(gè)殘疾皇子磷账,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容