Android 8.0 App Standby

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kc58236582/article/details/81944383

一、概述

低电耗模式和应用待机模式是从Android M引入的新特性,之前一直没有分析,低电耗模式就是Doze,应用待机模式就是 App Standby。

Doze模式我们之前分析过了,Doze模式在Android N又有修改,Android 6.0(API 级别 23)引入了低电耗模式,当用户设备未插接电源、处于静止状态且屏幕关闭时,该模式会推迟 CPU 和网络活动,从而延长电池寿命。而 Android 7.0 则通过在设备未插接电源且屏幕关闭状态下、但不一定要处于静止状态(例如用户外出时把手持式设备装在口袋里)时应用部分 CPU 和网络限制,进一步增强了低电耗模式。这个我们之后再分析。

这里主要分析App standy模式。

二、实现

设置应用的待机模式接口主要是从UsageStatsManager的setAppInactive接口,实现在UsageStatsService的setAppInactive接口,这个接口最后调用了setAppIdleAsync函数

    void setAppIdleAsync(String packageName, boolean idle, int userId) {
        if (packageName == null) return;

        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
                .sendToTarget();
    }

这个消息的处理函数是forceIdleState函数主要是调用了AppIdleHistory的setIdle函数。

    void forceIdleState(String packageName, int userId, boolean idle) {
        final int appId = getAppId(packageName);
        if (appId < 0) return;
        final long elapsedRealtime = SystemClock.elapsedRealtime();//系统开机后的时间值

        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
                userId, elapsedRealtime);
        synchronized (mAppIdleLock) {
            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
        }
        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
                userId, elapsedRealtime);
        // Inform listeners if necessary
        if (previouslyIdle != stillIdle) {//改变了就通知listener
            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
                    /* idle = */ stillIdle ? 1 : 0, packageName));
            if (!stillIdle) {
                notifyBatteryStats(packageName, userId, idle);
            }
        }
    }

因此我们还是继续分析AppIdleHistory的setIdle函数。这里getElapsedTime函数就是当前从系统开始的所有时间,getScreenOnTime是当前screenOn的所有时间,而mElapsedTimeThreahold为12小时,MScreenOntimeThreshold为2天。

设置Idle为false,就是lastUsedScreenTime会不一样。

    public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
        PackageHistory packageHistory = getPackageHistory(userHistory, packageName,
                elapsedRealtime);
        packageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime)
                - mElapsedTimeThreshold;
        packageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime)
                - (idle ? mScreenOnTimeThreshold : 0) - 1000 /* just a second more */;
    }

这里设置app的Idle就是通过了时间,下面我们来看如果判断一个app是否Idle,通过UsageStatsService的isAppIdle函数,这个函数调用了isAppIdleFiltered函数,这里的时间是当前的时间。这个函数就是去除各种app,如果是系统app、带android字眼的app等都不是idle,而且DeviceIdleController的白名单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) {
            return false;
        }
        if (appId < Process.FIRST_APPLICATION_UID) {
            // System uids never go idle.
            return false;
        }
        if (packageName.equals("android")) {
            // 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)) {
                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)) {
            return false;
        }

        return true;
    }

我们最后主要看下IsAppIdleUnfiltered函数,这个函数最后调用了AppIdleHistory.isIdle函数

    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
        synchronized (mAppIdleLock) {
            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
        }
    }

我们再来看AppIdleHistory.isIdle函数,最后调用了hasPassedThresholds函数

    public boolean isIdle(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, PackageHistory> userHistory = getUserHistory(userId);
        PackageHistory packageHistory =
                getPackageHistory(userHistory, packageName, elapsedRealtime);
        if (packageHistory == null) {
            return false; // Default to not idle
        } else {
            return hasPassedThresholds(packageHistory, elapsedRealtime);
        }
    }

最后来看hasPassedThresholds函数,结合之前设置这两个参数来看,当我们之前setIdle设置为true,这里获取的肯定也是true。因为会把mScreenOnTimeThreshold和mElapsedTimeThreshold去除再来做比较。之前setIdle设置为false,那么packageHistory.lastUsedScreenTime + mScreenOnTimeThreshold会大于当前时间(前提是小于12小时),也就是说12小时内该app不是Idle,超过12小时还是Idle。

    private boolean hasPassedThresholds(PackageHistory packageHistory, long elapsedRealtime) {
        return (packageHistory.lastUsedScreenTime
                    <= getScreenOnTime(elapsedRealtime) - mScreenOnTimeThreshold)
                && (packageHistory.lastUsedElapsedTime
                        <= getElapsedTime(elapsedRealtime) - mElapsedTimeThreshold);
    }

三、限制

最后我们来看应用是Idle会有哪些限制,被设置我Idle的应用会限制网络访问,具体通过NetworkPolicyManagerService,最后应该通过iptables来实现。

四、命令

我们可以使用如下命令来使应用进入应用待机模式。

1. 首先要运行应用并使其保持活动状态
2. 通过运行以下命令强制应用进入应用待机模式:
$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true
3. 使用以下命令模拟唤醒应用:
$ adb shell am set-inactive <packageName> false
$ adb shell am get-inactive <packageName>

猜你喜欢

转载自blog.csdn.net/kc58236582/article/details/81944383