美文网首页
Android之App Standby分析

Android之App Standby分析

作者: 锄禾豆 | 来源:发表于2022-01-17 09:17 被阅读0次

概述

从 Android 6.0(API 级别 23)开始,Android 引入了两项省电功能,通过管理应用在设备未连接至电源时的行为方式,帮助用户延长电池寿命。
应用待机模式会延迟用户近期未与之交互的应用的后台网络活动。

应用待机模式允许系统判定应用在用户未主动使用它时是否处于闲置状态。当用户有一段时间未触摸应用时,系统便会作出此判定,以下条件均不适用:

1.用户明确启动应用。
2.应用当前有一个进程在前台运行(作为活动或前台服务,或者正在由其他活动或前台服务使用)。
注意:您只能将前台服务用于用户希望系统立即执行或不中断的任务。 此类情况包括将照片上传到社交媒体,或者即使在音乐播放器应用不在前台运行时也能播放音乐。您不应该只是为了阻止系统判定您的应用处于闲置状态而启动前台服务。

3.应用生成用户可在锁定屏幕或通知栏中看到的通知。
4.应用是正在使用中的设备管理应用(例如设备政策控制器)。虽然设备管理应用通常在后台运行,但永远不会进入应用待机模式,因为它们必须保持可用性,以便随时从服务器接收策略。
当用户将设备插入电源时,系统会从待机状态释放应用,允许它们自由访问网络并执行任何待处理的作业和同步。如果设备长时间处于闲置状态,系统将允许闲置应用访问网络,频率大约每天一次。

adb查询

adb shell dumpsys usagestats

app接口

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

代码简要分析

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.启动
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模式共用一个配置文件,释放开启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());
        }

        //开机时长记录
        mRealTimeSnapshot = SystemClock.elapsedRealtime();
        //系统时长记录,可通过网络、手动切换的时间格式
        mSystemTimeSnapshot = System.currentTimeMillis();

        //注册共享服务
        publishLocalService(UsageStatsManagerInternal.class, new LocalService());
        publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
    }

onStart做了一些初始化及监听业务

3)onBootPhase

    public void onBootPhase(int phase) {
        if (phase == PHASE_SYSTEM_SERVICES_READY) {
            // Observe changes to the threshold
            // 初始化appidle临界值的变化。adb查询方式:adb shell settings get global app_idle_constants
            // 格式:idle_duration2=long,wallclock_threshold=long,parole_interval=long,parole_duration=long
            // long为数据类型。临界值可自定义
            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);

            //监听屏幕状态
            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
            synchronized (mLock) {
                //更新appidle数据
                mAppIdleHistory.updateDisplayLocked(isDisplayOn(), SystemClock.elapsedRealtime());
            }

            if (mPendingOneTimeCheckIdleStates) {
                //检查各个idle状态
                postOneTimeCheckIdleStates();
            }

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

如果说onStart()初始化业务,那onBootPhase是更新业务,核心点是mAppIdleHistory中数据的更新

3.临界数据分析

    //应用是否进入idle状态的临界值,这里比较的是亮屏状态的时间。默认12小时
    long mAppIdleScreenThresholdMillis;
    
    //定时检查app idle状态。默认3小时
    long mCheckIdleIntervalMillis;
    
    //应用是否进入idle状态的临界值,这里比较的累计开机状态的时间。默认两天
    long mAppIdleWallclockThresholdMillis;
    
    //相邻 2 次进入假释状态的时间间隔。默认1天
    long mAppIdleParoleIntervalMillis;
    
    //假释状态的持续时间 10分钟
    long mAppIdleParoleDurationMillis;

以下是数据的来源:

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

       //数据来源
        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调用onResume的数据采集(onPause也会进行数据采集,这样对等起来)
1)ActivityStackSupervisor.reportResumedActivityLocked

    boolean reportResumedActivityLocked(ActivityRecord r) {
        final ActivityStack stack = r.task.stack;
        if (isFocusedStack(stack)) {
            mService.updateUsageStats(r, true);//调用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 {
            ···
        }
    }

通过AMS通知UsageStatsService更新app数据

2)UsageStatsService接收通知进行更新

    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的函数,做了两件事情:1.直接给timeNow值 2.判断系统的时间是否改变
            final long timeNow = checkAndGetTimeLocked();
            final long elapsedRealtime = SystemClock.elapsedRealtime();
            convertToSystemTimeLocked(event);

            //通过userId获取对应的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状态
            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状态,则通知监听者进行策略更新
                if (previouslyIdle) {
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                            /* idle = */ 0, event.mPackage));
                    notifyBatteryStats(event.mPackage, userId, false);
                }
            }
        }
    }

这一阶段是业务的核心之处,有4处可说,3处值得好好分析
第一处:timeNow

    //方法的目的是监测系统时间是否有更新
    //如果有更新,则通知各个UserUsageStatsService更新数据
    private long checkAndGetTimeLocked() {
        final long actualSystemTime = System.currentTimeMillis();//当前系统时间
        final long actualRealtime = SystemClock.elapsedRealtime();//当前开机时长
        
        //期望系统时间=当前开机时长 - 上一次记录的开机时长 + 上一次的系统时间
        //换一句话说,期望系统时间=上一次的系统时间+距离当前时间的时间差
        final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
        
        //当前系统时间-期望系统时间 > 时间临界值
        //说明系统时间已经有更新,此时应该通知各个UserUsageStatsService更新数据
        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状态

AppIdleHistory.isIdleLocked

    public boolean isIdleLocked(String packageName, int userId, long elapsedRealtime) {
        //从mIdleHistory获取对应userid的map
        ArrayMap<String, PackageHistory> userHistory = getUserHistoryLocked(userId);
        
        //从userHistory中获取对应pkg的数据结构:PackageHistory
        PackageHistory packageHistory =
                getPackageHistoryLocked(userHistory, packageName, elapsedRealtime);
        if (packageHistory == null) {
            return false; // Default to not idle
        } else {
            //根据packageHistory判断是否是idle状态
            return hasPassedThresholdsLocked(packageHistory, elapsedRealtime);
        }
    }
    
    private boolean hasPassedThresholdsLocked(PackageHistory packageHistory, long elapsedRealtime) {
        //临界值终于发挥作用了:
        //mScreenOnTimeThreshold对应UsageStatsService.mAppIdleScreenThresholdMillis
        //mScreenOnTimeThreshold对应UsageStatsService.mAppIdleWallclockThresholdMillis
        return (packageHistory.lastUsedScreenTime
                    <= getScreenOnTimeLocked(elapsedRealtime) - mScreenOnTimeThreshold)
                && (packageHistory.lastUsedElapsedTime
                        <= getElapsedTimeLocked(elapsedRealtime) - mElapsedTimeThreshold);
    }

第三处:AppIdleHistory的数据记录

/data/system/screen_on_time
0     --- 开机之后的亮屏幕的时间
55017962   --- 从第一次开机累计的开机时长

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


UsageStatsManager.queryUsageStats 可查询如下信息
/data/system/usagestats/0/   
例如:
jj(S0):/data/system/usagestats/0 # ls
daily monthly version weekly yearly

第四处:UsageStatsService通知监听者

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

此时NetworkPolicyManagerService接收到命令:
NetworkPolicyManagerService在systemReady就注册了监听:
      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);//控制网络
                    updateRulesForPowerRestrictionsUL(uid);
                }
            } catch (NameNotFoundException nnfe) {
            }
        }
        ···
    }
    
    void updateRuleForAppIdleUL(int uid) {
        if (!isUidValidForBlacklistRules(uid)) return;

        int appId = UserHandle.getAppId(uid);
        //不在白名单 + app在idle + 不是前台业务
        //ture 进行网络限制
        //false 保持默认状态 
        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 {
            //通过NetworkManagementService控制网络
            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.总结

1.UsageStatsService本身不带白名单机制,它主要负责把信息分发出去,让执行者判断。例如NetworkPolicyManagerService根据自身的白名单做业务处理

2.UsageStatsService有控制开关。framework-res.apk的config_enableAutoPowerModes

3.UsageStatsService是被动调用者,例如AMS、NMS调用它,它根据实际事情分发给对应的监听者NetworkPolicy、JobSchedulerService等等

3.对于检查app是否为idle,也有一些名单强制控制。具体如下:
    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开关
            return false;
        }
        if (appId < Process.FIRST_APPLICATION_UID) {//系统应用没有idle业务
            // System uids never go idle.
            return false;
        }
        if (packageName.equals("android")) { //包名为android的app没有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)) {//开机向导
                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;
    }

原生设置案例

9.x
1.原生settings中,有一个界面:
UsageStatsActivity.java
统计apk使用情况
mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
        cal.getTimeInMillis(), System.currentTimeMillis());

2.调用的是UsageStatsManager.java
UsageStatsManager usm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE)

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


3.跨进程调用的服务为UsageStatsService.java

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

4.而最终实现的地方在UserUsageStatsService中
List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
    return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
}

5.数据真正处理的地方在UsageStatsDatabase中,也就是
/data/system/usagestats/用户/daily或weekly或monthly或yearly
例如,/data/system/usagestats/0/daily或weekly或monthly或yearly

6.UsageStatsService中关联了AppStandbyController,生成了一个对象。
AppStandbyController对象被处理,主要由UsageStatsService操作

7.AppStandbyController核心的是AppIdleHistory对象mAppIdleHistory,此对象
涉及到一个文件:app_idle_stats.xml,具体目录为:/data/system/users/用户/app_idle_stats.xml
注:可以通过adb指令生成最新状态的xml文件
adb shell dumpsys usagestats flush

参考学习

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-

相关文章

网友评论

      本文标题:Android之App Standby分析

      本文链接:https://www.haomeiwen.com/subject/ecathrtx.html