您现在的位置是:首页 > 正文

Android 8.0 App Standby

2024-02-01 04:45:01阅读 5

一、概述

低电耗模式和应用待机模式是从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>

网站文章

  • 维宏控制卡win7 驱动_雕刻机专用维宏5.55运动驱动控制卡

    详情介绍雕刻机维宏控制卡电脑雕刻机控制系统功能特点: 1.全中文windows界面,图形三维显示,操作简捷; 2.pci总线插卡结构,pci 2.1标准,windows 2000/nt/98兼容; 3...

    2024-02-01 04:44:52
  • Vue3 计算属性

    Vue3 计算属性

    上一篇博文说了 vue3 项目的 toRefs 函数和 toRef 函数,今天就稍微总结一下 vue3 的计算属性,其实学过 vue2 的宝子们应该都清楚,计算属性这个东西在项目开发过程中使用的还是比...

    2024-02-01 04:44:25
  • Spring data elasticsearch简单上手 | ES-7版本,springboot 2.4

    本来觉得写这个没什么意义,感觉看看别人就够了,然鹅,,,,被网上的坑的很惨再次强调本文的环境:2021年1月23日01:51:43的最新配置spring boot 2.4版本Elasticsearch...

    2024-02-01 04:44:18
  • Typora 开始收费,改用好玩的MarkText

    Typora 开始收费,改用好玩的MarkText

    收费……可以考虑使用:MarkText简述MarkTextMarkText 这个工具侧重于“命令”,导航栏都被收起来了。有些小伙伴感觉反而不好用,其实不然,是未了解该工具的强大之处。md 文本本身就是...

    2024-02-01 04:44:12
  • jsp中base标签的两种使用方法

    方法一 &lt;% String basePath = request.getScheme() + &quot;://&quot; + request.getServerName() + &quot;...

    2024-02-01 04:43:41
  • Cython入门到放弃(一) 热门推荐

    无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。教程链接:https://www.cbedai.net/qtlyx python作为一门强大的脚本语言,优势自然不必说,目前中低频的量化投资基本都是使用python作为research和production作为语...

    2024-02-01 04:43:33
  • dubbo微服务架构中传递MuitipartFile的问题及解决方式

    在编程的过程中总会遇到各式各样的问题,只有遇到的多了,总结的多了,我们才会有提升,在未来的道路上走的更远。

    2024-02-01 04:43:26
  • python处理命令行参数

    python处理命令行参数

    导航:起始页 &gt; Dive Into Python &gt; 脚本和流 &gt; 处理命令行参数 &gt;&gt; 深入 Python :Dive Into Python 中文版 Python 从新手到专家 [Dip_5.4b_CPyUG_Release] Find: 10.6. 处理命令行参数 Python

    2024-02-01 04:42:55
  • 【CSS学习】CSS中常用样式大全

    【CSS学习】CSS中常用样式大全,各种在web设计中使用到的css样式

    2024-02-01 04:42:49
  • Redis性能瓶颈揭秘:如何优化大key问题?

    Redis性能瓶颈揭秘:如何优化大key问题?

    Redis大key问题指的是某个key对应的value值所占的内存空间比较大,导致Redis的性能下降、内存不足、数据不均衡以及主从同步延迟等问题。到底多大的数据量才算是大key?没有固定的判别标准,...

    2024-02-01 04:42:42