这篇文章中简绍Android的耗电原理,以及飞书的耗电管理规划。

Android耗电核算原理

咱们先了解一下Android系统是如何进行耗电的核算的,最准确的办法当然是运用电流仪来进行核算,可是正常状况下手机硬件不支持,所以系核算算耗电时,运用的基本是模块功率✖️模块耗时这个公式来进行的,但不同的模块仍是会有一些不同。这种核算办法无法做到十分的准确,可是也基本能反应出各运用电量的耗费巨细。

模块功率

咱们先来看看模块功率,每个模块的耗电功率都是不相同的,以核算办法来分,又分为下面三类

Android耗电原理及飞书耗电治理

  1. 第一类是Camera、FlashLight、MediaPlayer等一般传感器或设备的模块的模块。其作业功率基本和额定功率坚持一致,所以模块电量的核算只需求核算模块的运用时长再乘以额定功率即可。
  1. 第二类是Wifi、Mobile、BlueTooth这类数据模块。其作业功率能够分为不同的档位,比方,当手机的 Wifi 信号比较弱的时候,Wifi 模块就必须作业在比较高的功率档位以坚持数据链路,所以这类模块的电量核算有点相似于咱们日常的电费核算,需求 “阶梯计费”。
  1. 第三类是屏幕,CPU模块。CPU 模块除了每一个CPU Core 需求像数据模块那样阶梯核算电量之外,CPU 的每一个集群(Cluster,一般一个集群包括一个或多个规格相同的 Core)也有额外的耗电,此外整个 CPU 处理器芯片也有功耗。简略核算的话,CPU 电量 = SUM (各中心功耗) + 各集群(Cluster)功耗 + 芯片功耗 。屏幕模块的电量核算就更麻烦了,很难把屏幕功耗合理地分配给各个 App, 因而 Android 系统仅仅简略地核算 App 屏幕锁(WakeLock)的持有时长,按固定系数增加 App CPU 的核算时长,粗略地把屏幕功耗算进 CPU 里边。

每个模块的功耗巨细位于framework的power_profile.xml文件中,由厂商自己供给,里边规矩了每个模块的功耗,下面是一台一加9的测试机的power_profile文件

Android耗电原理及飞书耗电治理

经过apktook反解出来的power_profile如下

Android耗电原理及飞书耗电治理

文件中每个模块的对应阐明,能够在谷歌供给的文档中看到具体的阐明。

source.android.com/devices/tec…

Android耗电原理及飞书耗电治理

模块耗时

了解了模块的功率,咱们再来看看模块耗时,耗电模块在作业或许状况变更时,都会告诉batterystats这个service,而BatteryStatsService会调用BatteryStats方针进行耗时的核算,BatteryStats的结构函数中会初始化各个模块的Timer,用来进行耗时的核算,并将核算的数据存储在batterystats.bin文件中

BatteryStatsImpl.java

Android耗电原理及飞书耗电治理

咱们来具体看看下面几个模块的是如何进核算的,

  • wifi模块
    public void noteWifiOnLocked() {
        if (!mWifiOn) {
            final long elapsedRealtime = mClocks.elapsedRealtime();
            final long uptime = mClocks.uptimeMillis();
            mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
            addHistoryRecordLocked(elapsedRealtime, uptime);
            mWifiOn = true;
            mWifiOnTimer.startRunningLocked(elapsedRealtime);
            scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI);
        }
    }
    public void noteWifiOffLocked() {
        final long elapsedRealtime = mClocks.elapsedRealtime();
        final long uptime = mClocks.uptimeMillis();
        if (mWifiOn) {
            mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
            addHistoryRecordLocked(elapsedRealtime, uptime);
            mWifiOn = false;
            mWifiOnTimer.stopRunningLocked(elapsedRealtime);
            scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI);
        }
    }
  • Audio模块
    public void noteAudioOnLocked(int uid) {
        uid = mapUid(uid);
        final long elapsedRealtime = mClocks.elapsedRealtime();
        final long uptime = mClocks.uptimeMillis();
        if (mAudioOnNesting == 0) {
            mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
            if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
                    + Integer.toHexString(mHistoryCur.states));
            addHistoryRecordLocked(elapsedRealtime, uptime);
            mAudioOnTimer.startRunningLocked(elapsedRealtime);
        }
        mAudioOnNesting++;
        getUidStatsLocked(uid).noteAudioTurnedOnLocked(elapsedRealtime);
    }
    public void noteAudioOffLocked(int uid) {
        if (mAudioOnNesting == 0) {
            return;
        }
        uid = mapUid(uid);
        final long elapsedRealtime = mClocks.elapsedRealtime();
        final long uptime = mClocks.uptimeMillis();
        if (--mAudioOnNesting == 0) {
            mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
            if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
                    + Integer.toHexString(mHistoryCur.states));
            addHistoryRecordLocked(elapsedRealtime, uptime);
            mAudioOnTimer.stopRunningLocked(elapsedRealtime);
        }
        getUidStatsLocked(uid).noteAudioTurnedOffLocked(elapsedRealtime);
    }
  • Activity状况改动
public void noteActivityResumedLocked(int uid) {
    uid = mapUid(uid);
    getUidStatsLocked(uid).noteActivityResumedLocked(mClocks.elapsedRealtime());
}
public void noteActivityPausedLocked(int uid) {
    uid = mapUid(uid);
    getUidStatsLocked(uid).noteActivityPausedLocked(mClocks.elapsedRealtime());
}
public static class Uid extends BatteryStats.Uid {
    @Override
    public void noteActivityPausedLocked(long elapsedRealtimeMs) {
        if (mForegroundActivityTimer != null) {
            mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);
        }
    }
    @Override
    public void noteActivityPausedLocked(long elapsedRealtimeMs) {
        if (mForegroundActivityTimer != null) {
            mForegroundActivityTimer.stopRunningLocked(elapsedRealtimeMs);
        }
    }
}

经过上面三个比如能够看到,BatteryStats在核算模块耗时,首要经过Timer来进行时长的核算,如WifiOnTimer、AudioOnTimer、ForegroundActivityTimer,而且依据是否有UID来决定是否要核算到UID对应的数据中,系统在核算运用的耗电时,便是依据UID下各个模块的核算数据,来进行运用的耗电核算的。

耗电核算

当咱们知道了每个模块的耗时,每个模块的功耗,那么就能核算各个模块的耗电量了,耗电量的核算在BatteryStatsHelper这个类中,下面具体看一下Setting中,运用耗电详情这个功用核算耗电的完成,Setting中的耗电核算这个运用首要是调用了BatteryStatsHelper中的refreshStats() 函数

Android耗电原理及飞书耗电治理

refreshStats首要两个办法是processappUsage核算运用的耗电,记忆processMiscUsage核算杂项耗电,如WIFI,通话等等

  • 核算app的电量

Android耗电原理及飞书耗电治理

这儿以CameraPowerCalculator这个简略的模块看看它是如何核算电量的

CameraPowerCalculator.java

Android耗电原理及飞书耗电治理

能够看到,里边仅仅简略的用了totalTime * mCameraPowerOnAvg,mCameraPowerOnAvg则是从power_profile.xml读取出来,其他教负责的如CPU模块的核算,感兴趣的能够自己看看,就不在这儿说了。

  • 核算misc杂项的电量

Android耗电原理及飞书耗电治理

杂项电量用来核算一些没有特定UID的耗电,如蓝牙,屏幕等等,核算办法也是相似的。

Android的耗电优化战略

Doze形式

Doze形式也被称为低电耗形式,是针对整个系统进行一个耗电优化战略,进入Doze形式后会暂停一切的Jobs,Alarm和Network活动并推迟到窗口期履行,以及其他的一些约束来节省电量。

Doze形式的进入和退出

Doze形式分为Deep Doze和Light Doze两种形式,Doze形式是在Android6.0引进的,也便是Deep Doze形式,Light Doze是Android7.0引进的,两者进入的条件不相同,Deep Doze的条件会更严厉,下面先介绍Deep Doze

Deep Doze

系统处于息屏状况,而且30分钟不移动的情况下,就会进入到Deep Doze形式,Deep Doze机制中有七种状况,别离如下

//mState值,表明设备处于活动状况
private static final int STATE_ACTIVE = 0;
//mState值,表明设备处于不交互状况,灭屏、停止
private static final int STATE_INACTIVE = 1;
//mState值,表明设备刚结束不交互状况,等候进入IDLE状况
private static final int STATE_IDLE_PENDING = 2;
//mState值,表明设备正在感应动作
private static final int STATE_SENSING = 3;
//mState值,表明设备正在定位
private static final int STATE_LOCATING = 4;
//mState值,表明设备处于闲暇状况,也即Doze形式
private static final int STATE_IDLE = 5;
//mState值,表明设备正处于Doze形式,紧接着退出Doze进入保护状况
private static final int STATE_IDLE_MAINTENANCE = 6;

这七种状况的转化联系如下

Android耗电原理及飞书耗电治理

依据上图,他们的联系总结如下

  1. 当设备亮屏或许处于正常运用状况时其就为ACTIVE状况;
  1. ACTIVE状况下不充电且灭屏设备就会切换到INACTIVE状况;
  1. INACTIVE状况经过30分钟,期间检测没有打断状况的行为Doze就切换到IDLE_PENDING的状况;
  1. 然后再经过30分钟以及一系列的判别,状况切换到SENSING;
  1. 在SENSING状况下会去检测是否有地理方位改变,没有的话就切到LOCATION状况;
  1. LOCATION状况下再经过30s的检测时刻之后就进入了Doze的中心状况IDLE;
  1. 在IDLE形式下每隔一段时刻就会进入一次IDLE_MAINTANCE,此间用来处理之前被挂起的一些使命,这个时刻段为一个小时,两个小时,四个小时,最终稳定为最长为六个小时
  1. IDLE_MAINTANCE状况持续5分钟之后会从头回到IDLE状况;
  1. 在除ACTIVE以外的一切状况中,检测到打断的行为如亮屏、刺进充电器,方位的改动等状况就会回到ACTIVE,从头开端下一个轮回。

Light Doze

从上面能够看到想要进入Doze形式的条件是很苛刻,需求在手机息屏而且没有移动的状况下才干进入,所以Android7.0开端引进了Light Doze,处于息屏状况,但仍处于移动状况可进入Light Doze,LightDoze有7个状况,别离如下:

//mLightState状况值,表明设备处于活动状况
private static final int LIGHT_STATE_ACTIVE = 0;
//mLightState状况值,表明设备处于不活动状况
private static final int LIGHT_STATE_INACTIVE = 1;
//mLightState状况值,表明设备进入闲暇状况前,需求等候完成必要操作
private static final int LIGHT_STATE_PRE_IDLE = 3;
//mLightState状况值,表明设备处于闲暇状况,该状况内将进行优化
private static final int LIGHT_STATE_IDLE = 4;
//mLightState状况值,表明设备处于闲暇状况,要进入保护状况,先等候网络连接
private static final int LIGHT_STATE_WAITING_FOR_NETWORK = 5;
//mLightState状况值,表明设备处于保护状况
private static final int LIGHT_STATE_IDLE_MAINTENANCE = 6;

这个6个状况的转化联系如下

Android耗电原理及飞书耗电治理

依据上图,他们的转化联系总结如下

  1. 当设备亮屏或许处于正常运用状况时其就为ACTIVE状况;
  1. ACTIVE状况下不充电且灭屏设备就会切换到INACTIVE状况;
  1. INACTIVE状况经过3分钟,期间检测没有打断状况的行为就切换到PRE_IDLE的状况;
  1. PRE_IDLE状况经过5分钟,期间无打断就进入到IDLE状况
  1. 进入IDLE状况会依据是否有网络连接选择进入WAITING_FOR_NETWORK仍是进入MAINTENANCE窗口期,进入窗口期的时刻为:5分钟,10分钟,最终稳定最长为15分钟
  1. 进入WAITING_FOR_NETWORK会持续5分钟后从头进入到IDLE状况
  1. 进入MAINTENANCE会解除耗电战略的约束,并在1分钟后从头进入到IDLE状况

Doze形式的优化战略

了解了Doze形式的进入和退出战略,咱们再来看一下在Doze形式中,会做哪些战略来优化耗电

Deep Doze

当系统处于Doze形式下,系统和白名单之外的运用将遭到以下约束:

  • 无法拜访网络
  • Wake Locks被疏忽
  • AlarmManager闹铃会被推迟到下一个maintenance window响应

    • 运用setAndAllowWhileIdle或SetExactAndAllowWhileIdle设置闹铃的闹钟则不会遭到Doze形式的影响
    • setAlarmClock设置的闹铃在Doze形式下依然生效,但系统会在闹铃生效前退出Doze
  • 系统不履行Wi-Fi/GPS扫描;
  • 系统不允许同步适配器运转;
  • 系统不允许JobScheduler运转;

Deep Doze也供给了白名单,位于白名单中的运用能够:

  • 继续运用网络并保存部分wake lock
  • Job和同步依然会被推迟
  • 惯例的AlarmManager闹铃也不会被触发

Light Doze

Light Doze的约束没有Deep Doze这么严厉,首要有下面几种

  • 不允许进行网络拜访
  • 不允许同步适配器运转
  • 不允许JobScheduler运转

Deep Doze和Light Doze的总结比照方下:

Deep Doze和Light Doze都需求达到一定条件后才干进入,而且进入后会定期供给窗口期来解除约束。

Android耗电原理及飞书耗电治理

他们的比照方下

操作 Deep Doze Light Doze
触发要素 屏幕封闭,无充电,停止 屏幕封闭,无充电
坚持时刻 不断增长,最长为6个小时 不断增长,最长为15分钟
约束 无法进行网络拜访,唤醒锁疏忽,GPS/WIFI/蓝牙等无法扫描,闹钟/SyncAdapter/JobScheduler推迟 无法进行网络拜访,SyncAdapter/JobScheduler推迟
退出 设备移动,和用户有交互,屏幕敞开 屏幕敞开

Doze形式完成原理

前面现已了解了Doze形式了,下面就在经过Android中的Doze机制的源码,深入了解Doze的完成原理。Doze机制相关的源码都在DeviceIdleController这个类中

DeviceIdleController.java

进入INACTIVE状况

从ACTIVIE进入到INACTIVE的进口办法是becomeInactiveIfAppropriateLocked中,当充电状况发生改动,屏幕息屏等条件触发时,都会调用该办法判别是否可进入INACTIVE状况。

//deep doze进入INACTIVE后的延时时刻,这儿的COMPRESS_TIME默认为false
long inactiveTimeoutDefault = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
INACTIVE_TIMEOUT = mParser.getDurationMillis(KEY_INACTIVE_TIMEOUT,
                !COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getDurationMillis(
        KEY_LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
        !COMPRESS_TIME ? 3 * 60 * 1000L : 15 * 1000L);
void becomeInactiveIfAppropriateLocked() {
    final boolean isScreenBlockingInactive =
            mScreenOn && (!mConstants.WAIT_FOR_UNLOCK || !mScreenLocked);
    //判别是否是灭屏且非充电状况
    if (!mForceIdle && (mCharging || isScreenBlockingInactive)) {
        return;
    }
    if (mDeepEnabled) {
        if (mQuickDozeActivated) {
            //1. QuickDoze是Android 10新引进的低电量的情况下,快速进入Doze的机制,会缩短进入Doze的耗时
            if (mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE
                    || mState == STATE_IDLE_MAINTENANCE) {
                return;
            }
            mState = STATE_QUICK_DOZE_DELAY;
            resetIdleManagementLocked();
            scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
            EventLogTags.writeDeviceIdle(mState, "no activity");
        } else if (mState == STATE_ACTIVE) {
            mState = STATE_INACTIVE;
            resetIdleManagementLocked();
            long delay = mInactiveTimeout;
            if (shouldUseIdleTimeoutFactorLocked()) {
                delay = (long) (mPreIdleFactor * delay);
            }
            //2. 履行时刻为mInactiveTimeout延时的使命,这儿是30分钟
            scheduleAlarmLocked(delay, false);
            EventLogTags.writeDeviceIdle(mState, "no activity");
        }
    }
    if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
        mLightState = LIGHT_STATE_INACTIVE;
        resetLightIdleManagementLocked();
        //3. 履行时刻为LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT延时的使命,这儿是3分钟
        scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
        EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
    }
}

从源码中能够看到Deep Doze,Light Doze的处理都在这儿,而且这儿还有一个Quick Doze,它是Android 10引进,能在低电量情况下快速进入Doze的机制。

咱们接着看INACTIVE向下一个状况的改动

  • Deep Doze经过scheduleAlarmLocked(delay, false)向下一个状况改变,在这个时刻过程中,有开屏,充电等操作,都会导致状况转化失利
  • Light Doze经过scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT)向下一个状况改动,同样在开屏和充电状况下,都会导致进入下一个状况失利

从INACTIVE状况开端,Light Doze和Deep Doze转化的进口就不相同了,所以下面会分开讲解

Deep Doze

1. 从INACTIVE进入STATE_IDLE_PENDING

becomeInactiveIfAppropriateLocked函数中将mState设置为STATE_INACTIVE,然后调用scheduleAlarmLocked设置了一个30分钟的守时使命,它的逻辑完成如下。

void scheduleAlarmLocked(long delay, boolean idleUntil) {
    if (mMotionSensor == null) {
    //假如没有运动传感器,则回来,由于无法判别设备是否坚持停止
    if (mMotionSensor == nullr) {
        return;
    }
    //设置DeepDoze的守时Alarm
    mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
    if (idleUntil) {
        mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextAlarmTime, "DeviceIdleController.deep", 
                mDeepAlarmListener, mHandler);
    } else {
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextAlarmTime, "DeviceIdleController.deep", 
                mDeepAlarmListener, mHandler);
    }
}
private final AlarmManager.OnAlarmListener mDeepAlarmListener
        = new AlarmManager.OnAlarmListener() {
    @Override
    public void onAlarm() {
        synchronized (DeviceIdleController.this) {
            ///每次Doze状况转化都会在该办法中进行
            stepIdleStateLocked("s:alarm");
        }
    }
};

Deep Doze的scheduleAlarmLocked守时使命触发后,会回调onAlarm,履行stepIdleStateLocked函数。

void stepIdleStateLocked(String reason) {
    final long now = SystemClock.elapsedRealtime();
    //阐明1小时内有Alarm守时时刻到,暂不进入IDLE状况,30min后再进入
    if ((now+mConstants.MIN_TIME_TO_ALARM) > 
               mAlarmManager.getNextWakeFromIdleTime()) {
        if (mState != STATE_ACTIVE) {
            //将当时设备变为活动状况,LightDoze和DeepDoze都为Active状况
            becomeActiveLocked("alarm", Process.myUid());
            becomeInactiveIfAppropriateLocked();
        }
        return;
    }
    switch (mState) {
        case STATE_INACTIVE:
            //发动Sensor
            startMonitoringMotionLocked();
            //设置STATE_IDLE_PENDING状况时长的守时Alarm,30mins
            scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT,
                   false);
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;//5mins
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;//60mins
            //此刻状况变为PENDING状况
            mState = STATE_IDLE_PENDING;
            break;
        case STATE_IDLE_PENDING:
            //此刻状况变为SENSING状况
            mState = STATE_SENSING;
            //设置STATE_SENSING状况超时时长的守时Alarm,DEBUG?1:4mins
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
            //撤销通用方位更新和GPS方位更新
            cancelLocatingLocked();
            mNotMoving = false;
            mLocated = false;
            mLastGenericLocation = null;
            mLastGpsLocation = null;
            //开端检测是否有移动
            mAnyMotionDetector.checkForAnyMotion();
            break;
        case STATE_SENSING:
            //撤销用于STATE_SENSING状况超时时长的Alarm
            cancelSensingTimeoutAlarmLocked();
            //此刻状况变为LOCATING
            mState = STATE_LOCATING;
            //设置STATE_LOCATING状况时长的Alarm
            scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, 
                     false);//DEBUG?15:30
            //恳求通用方位
            if (mLocationManager != null
                    && mLocationManager.getProvider(LocationManager.
                     NETWORK_PROVIDER) != null) {
                mLocationManager.requestLocationUpdates(mLocationRequest,
                        mGenericLocationListener, mHandler.getLooper());
                mLocating = true;
            } else {
                mHasNetworkLocation = false;
            }
            //恳求GPS方位
            if (mLocationManager != null
                    && mLocationManager.getProvider(LocationManager.
                    GPS_PROVIDER) != null) {
                mHasGps = true;
                mLocationManager.requestLocationUpdates(LocationManager.
                        GPS_PROVIDER, 1000, 5,
                        mGpsLocationListener, mHandler.getLooper());
                mLocating = true;
            } else {
                mHasGps = false;
            }
            //假如true,则break,由于在Location的Listener中会进入下一个状况,
            //不然进入下一步状况
            if (mLocating) {
                break;
            }
        case STATE_LOCATING:
            //撤销DeepDoze的Alarm
            cancelAlarmLocked();
            //撤销方位更新
            cancelLocatingLocked();
            //Sensor停止检测
            mAnyMotionDetector.stop();
        case STATE_IDLE_MAINTENANCE:
            //设置STATE_IDLE状况时长的守时Alarm,届时后将退出IDLE状况
            scheduleAlarmLocked(mNextIdleDelay, true);
            //设置下次IDLE时刻
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            }
            mState = STATE_IDLE;
            //进入DeepDoze的IDLE后,掩盖LightDoze
            if (mLightState != LIGHT_STATE_OVERRIDE) {
                mLightState = LIGHT_STATE_OVERRIDE;
                //撤销LightDoze的守时Alarm
                cancelLightAlarmLocked();
            }
            //恳求wakelock坚持CPU唤醒
            mGoingIdleWakeLock.acquire();
            //handler中处理idle状况后各个模块的约束作业
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
            break;
        case STATE_IDLE:
            mActiveIdleOpCount = 1;//表明现在有正在活动的操作
            //恳求wakelock锁坚持cpu唤醒
            mActiveIdleWakeLock.acquire();
            //设置STATE_IDLE_MAINTENANCE状况时长的守时Alarm,
            //届时后将退出保护状况
            scheduleAlarmLocked(mNextIdlePendingDelay, false);
            mMaintenanceStartTime = SystemClock.elapsedRealtime();
            mNextIdlePendingDelay = 
                 Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                    (long)(mNextIdlePendingDelay * 
                    mConstants.IDLE_PENDING_FACTOR));
            if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
                mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            }
            mState = STATE_IDLE_MAINTENANCE;
            //Handler中处理退出idle状况进入保护状况后撤销约束的作业
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            break;
    }
}

能够看到,Deep Doze的状况转化都是经过scheduleAlarmLocked和stepIdleStateLocked这两个函数进行的。 在case为STATE_INACTIVE的逻辑中,将mState设置成了STATE_IDLE_PENDING,发动Sensor监听,并设置了一个30分钟的延时使命。

2. 从STATE_DLE_PENDING进入STATE_SENSING

当30分钟无中止,state就从PENDING进入到了SENSING状况中。

case STATE_IDLE_PENDING:
    //此刻状况变为SENSING状况
    mState = STATE_SENSING;
    //设置STATE_SENSING状况超时时长的守时Alarm,4分钟
    scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
    //撤销通用方位更新和GPS方位更新
    cancelLocatingLocked();
    mNotMoving = false;
    mLocated = false;
    mLastGenericLocation = null;
    mLastGpsLocation = null;
    //开端检测是否有运动
    mAnyMotionDetector.checkForAnyMotion();
    break;

在这个状况中,会开端运动检测,并持续4分钟。

3. 从STATE_SENSING进入到STATE_LOCATING
4. 从STATE_LOCATING进入到STATE_IDLE
5. 从STATE_IDLE_MAINTENANCE进入到STATE_IDLE

SENSING的下一个状况是STATE_LOCATING,STATE_LOCATING和STATE_IDLE_MAINTENANCE的下一个状况都是STATE_IDLE,这儿一同讲

case STATE_SENSING:
    //撤销用于STATE_SENSING状况超时时长的Alarm
    cancelSensingTimeoutAlarmLocked();
    //此刻状况变为LOCATING
    mState = STATE_LOCATING;
    //设置STATE_LOCATING状况时长的Alarm,
    scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, 
             false);
    //恳求通用方位
    if (mLocationManager != null
            && mLocationManager.getProvider(LocationManager.
             NETWORK_PROVIDER) != null) {
        mLocationManager.requestLocationUpdates(mLocationRequest,
                mGenericLocationListener, mHandler.getLooper());
        mLocating = true;
    } else {
        mHasNetworkLocation = false;
    }
    //恳求GPS方位
    if (mLocationManager != null
            && mLocationManager.getProvider(LocationManager.
            GPS_PROVIDER) != null) {
        mHasGps = true;
        mLocationManager.requestLocationUpdates(LocationManager.
                GPS_PROVIDER, 1000, 5,
                mGpsLocationListener, mHandler.getLooper());
        mLocating = true;
    } else {
        mHasGps = false;
    }
    //假如true,则break,由于在Location的Listener中会进入下一个状况,
    //不然进入下一步状况
    if (mLocating) {
        break;
    }
case STATE_LOCATING:
        //撤销DeepDoze的Alarm
        cancelAlarmLocked();
        //撤销方位更新
        cancelLocatingLocked();
        //Sensor停止检测
        mAnyMotionDetector.stop();
case STATE_IDLE_MAINTENANCE:
    //设置STATE_IDLE状况时长的守时Alarm,届时后将退出IDLE状况
    scheduleAlarmLocked(mNextIdleDelay, true);
    //设置下次IDLE时刻
    mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
    mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
    if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
        mNextIdleDelay = mConstants.IDLE_TIMEOUT;
    }
    mState = STATE_IDLE;
    //进入DeepDoze的IDLE后,掩盖LightDoze
    if (mLightState != LIGHT_STATE_OVERRIDE) {
        mLightState = LIGHT_STATE_OVERRIDE;
        //撤销LightDoze的守时Alarm
        cancelLightAlarmLocked();
    }
    //恳求wakelock坚持CPU唤醒
    mGoingIdleWakeLock.acquire();
    //handler中处理idle状况后各个模块的约束作业
    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
    break;

在这个过程中检测是否有gps以及是否有方位移动,假如有gps,则经过break跳出循环,并进行30S的方位移动检测;没有gps,则进入到case为STATE_IDLE_MAINTENANCE的处理中,并将state设置为STATE_IDLE。

进入到STATE_IDLE后,会恳求wakelock,一起调用MSG_REPORT_IDLE_ON的handler使命来进行耗电战略的约束,这儿和light doze的idle状况处理都是同一个进口,所以MSG_REPORT_IDLE_ON在下面light doze中在具体将。

一起,咱们能够看到,进入STATE_IDLE后,会设置一个时刻为:

IDLE_TIMEOUT = mParser.getDurationMillis(KEY_IDLE_TIMEOUT,
        !COMPRESS_TIME ? 60 * 60 * 1000L : 6 * 60 * 1000L);
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
    mNextIdleDelay = mConstants.IDLE_TIMEOUT;
}

的延时使命,IDLE_FACTOR为2,mNextIdleDelay初始值为60分钟,MAX_IDLE_TIMEOUT为6个小时,所以这个时刻为1个小时,2个小时,4个小时,最终稳定为6个小时

6. 从STATE_IDLE进入到STATE_IDLE_MAINTENANCE
case STATE_IDLE:
        mActiveIdleOpCount = 1;//表明现在有正在活动的操作
        //恳求wakelock锁坚持cpu唤醒
        mActiveIdleWakeLock.acquire();
        //设置STATE_IDLE_MAINTENANCE状况时长的守时Alarm,
        //届时后将退出保护状况
        scheduleAlarmLocked(mNextIdlePendingDelay, false);
        mMaintenanceStartTime = SystemClock.elapsedRealtime();
        mNextIdlePendingDelay = 
             Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                (long)(mNextIdlePendingDelay * 
                mConstants.IDLE_PENDING_FACTOR));
        if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
        }
        mState = STATE_IDLE_MAINTENANCE;
        //Handler中处理退出idle状况进入保护状况后撤销约束的作业
        mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
        break;

进入MAINTENANCE状况后,会在MSG_REPORT_IDLE_OFF的handler中撤销各种约束,并方位mNextIdlePendingDelay时刻段

mNextIdlePendingDelay =
         Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
            (long)(mNextIdlePendingDelay * 
            mConstants.IDLE_PENDING_FACTOR));
if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
    mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
}

IDLE_PENDING_TIMEOUT为5分钟

Light Doze

1. 从INACTIVE进入LIGHT_STATE_PRE_IDLE

scheduleLightAlarmLocked抵达时刻后,会触发下面的回调

void scheduleLightAlarmLocked(long delay) {
    mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
    //抵达时刻后,回调mLightAlarmListener.onAlarm()
    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            mNextLightAlarmTime, "DeviceIdleController.light", 
            mLightAlarmListener, mHandler);
}
private final AlarmManager.OnAlarmListener mLightAlarmListener
        = new AlarmManager.OnAlarmListener() {
    @Override
    public void onAlarm() {
        synchronized (DeviceIdleController.this) {
            //每次LightDoze的状况改动,都会调用该办法进行处理
            stepLightIdleStateLocked("s:alarm");
        }
    }
};

Light Doze的状况改动也都是在stepLightIdleStateLocked函数中处理

void stepLightIdleStateLocked(String reason) {
    //假如mLigthSate为LIGHT_STATE_OVERRIDE,阐明DeepDoze处于Idle状况,由
    // DeepDoze将LightDoze掩盖了,因而不需求进行LightDoze了
    if (mLightState == LIGHT_STATE_OVERRIDE) {
        return;
    }
    switch (mLightState) {
        case LIGHT_STATE_INACTIVE:
            //当时最小预算时刻
            mCurIdleBudget = 
              mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;//1min
            //表明LightDoze 进入闲暇(Idle)状况的时刻
            mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;//5mins
            //LightDoze进入保护状况(maintenance)的开端时刻
            mMaintenanceStartTime = 0;
            if (!isOpsInactiveLocked()) {
                //将状况置为LIGHT_STATE_PRE_IDLE状况
                mLightState = LIGHT_STATE_PRE_IDLE;
                //设置一个3分钟的守时器
                scheduleLightAlarmLocked(mConstants.LIGHT_PRE_
                  IDLE_TIMEOUT);
                break;
            }
        case LIGHT_STATE_PRE_IDLE:
        case LIGHT_STATE_IDLE_MAINTENANCE:
            if (mMaintenanceStartTime != 0) {
            //保护状况的时长
                long duration = SystemClock.elapsedRealtime() - 
                 mMaintenanceStartTime;
                if (duration < 
                 mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                    mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE
                       _MIN_BUDGET-duration);
                } else {
                    mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_
                      MAINTENANCE_MIN_BUDGET);
                }
            }
            mMaintenanceStartTime = 0;//重置保护开端时刻
            //设置一个守时器,抵达时刻后用来处理LightDoze处于IDLE状况的操作
            scheduleLightAlarmLocked(mNextLightIdleDelay);
           //核算下次进入Idle状况的
            mNextLightIdleDelay = 
            Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                    (long)(mNextLightIdleDelay * 
                mConstants.LIGHT_IDLE_FACTOR));
            if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
            }
            //将LightDoze形式置为IDLE状况,开端进行一些约束
            mLightState = LIGHT_STATE_IDLE;
            addEvent(EVENT_LIGHT_IDLE);
            //恳求一个wakelock锁,坚持CPU唤醒
            mGoingIdleWakeLock.acquire();
            //处理LightDoze进入Idle状况后的操作
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
            break;
        case LIGHT_STATE_IDLE:
        case LIGHT_STATE_WAITING_FOR_NETWORK:
            if (mNetworkConnected || mLightState == 
            LIGHT_STATE_WAITING_FOR_NETWORK) {
                //假如网络有链接或许当时LightDoze形式为等候网络状况,则进行保护,
                // 并将LightDoze形式退出IDLE状况,进入保护状况
                mActiveIdleOpCount = 1;
                mActiveIdleWakeLock.acquire();
                mMaintenanceStartTime = SystemClock.elapsedRealtime();
            // 确保10<=mCurIdleBudget<=30mins ,mCurIdleBudget是保护状况的时刻
                if (mCurIdleBudget < 
                mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                    mCurIdleBudget = 
                     mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
                } else if (mCurIdleBudget > 
                mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
                    mCurIdleBudget = 
                    mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
                }
                //设置一个守时器,抵达时刻后用来处理LightDoze处于保护状况的操作
                scheduleLightAlarmLocked(mCurIdleBudget);
                mLightState = LIGHT_STATE_IDLE_MAINTENANCE;//进入保护状况
                addEvent(EVENT_LIGHT_MAINTENANCE);
                //处理LightDoze进入Maintenance状况后的操作
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            } else {
                //将LightDoze形式置为LIGHT_STATE_WAITING_FOR_NETWORK,
            //在进入保护状况前需求获取网络
                //设置一个守时器,抵达时刻后用来处理LightDoze处于
            //WAITING_FOR_NETWORK状况的操作
                scheduleLightAlarmLocked(mNextLightIdleDelay);//600000,5mins
                mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
                EventLogTags.writeDeviceIdleLight(mLightState, reason);
            }
            break;
    }
}

从代码中能够看到,case为LIGHT_STATE_INACTIVE的处理逻辑中,做了这几件事

  1. 将当时状况设置为LIGHT_STATE_PRE_IDLE,
  1. 并发送一个3分钟的闹钟,预备进入下一个状况。

后续状况也全部是经过scheduleLightAlarmLocked来设置守时使命,然后在stepLightIdleStateLocked函数中处理状况的转化和对应状况的逻辑

2. 从LIGHT_STATE_PRE_IDLE进入LIGHT_STATE_IDLE
3. 从LIGHT_STATE_IDLE_MAINTENANCE进入LIGHT_STATE_IDLE

LIGHT_STATE_PRE_IDLE和LIGHT_STATE_IDLE_MAINTENANCE的下一个状况都是LIGHT_STATE_IDLE,所以他们的处理也在同一个进口

LIGHT_IDLE_TIMEOUT = mParser.getDurationMillis(KEY_LIGHT_IDLE_TIMEOUT,
        !COMPRESS_TIME ? 5 * 60 * 1000L : 15 * 1000L);
LIGHT_MAX_IDLE_TIMEOUT = mParser.getDurationMillis(KEY_LIGHT_MAX_IDLE_TIMEOUT,
                !COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
void stepLightIdleStateLocked(String reason) {
    //假如mLigthSate为LIGHT_STATE_OVERRIDE,阐明DeepDoze处于Idle状况,由
    // DeepDoze将LightDoze掩盖了,因而不需求进行LightDoze了
    if (mLightState == LIGHT_STATE_OVERRIDE) {
        return;
    }
    switch (mLightState) {
        ……
        case LIGHT_STATE_PRE_IDLE:
        case LIGHT_STATE_IDLE_MAINTENANCE:
            if (mMaintenanceStartTime != 0) {
            //保护状况的时长
                long duration = SystemClock.elapsedRealtime() - 
                 mMaintenanceStartTime;
                if (duration < 
                 mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                    mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE
                       _MIN_BUDGET-duration);
                } else {
                    mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_
                      MAINTENANCE_MIN_BUDGET);
                }
            }
            mMaintenanceStartTime = 0;//重置保护开端时刻
            //设置一个守时器,抵达时刻后用来处理LightDoze处于IDLE状况的操作
            scheduleLightAlarmLocked(mNextLightIdleDelay);
           //核算下次进入Idle状况的
            mNextLightIdleDelay = 
            Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                    (long)(mNextLightIdleDelay * 
                mConstants.LIGHT_IDLE_FACTOR));
            if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
            }
            //将LightDoze形式置为IDLE状况,开端进行一些约束
            mLightState = LIGHT_STATE_IDLE;
            addEvent(EVENT_LIGHT_IDLE);
            //恳求一个wakelock锁,坚持CPU唤醒
            mGoingIdleWakeLock.acquire();
            //处理LightDoze进入Idle状况后的操作
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
            break;
        ……
    }
}

这儿会将state设置成LIGHT_STATE_IDLE,并设置一个mNextLightIdleDelay的计时使命,以便进入下一个状况,mNextLightIdleDelay的初始值是5分钟。

这儿咱们能够看到LIGHT_STATE_PRE_IDLE和LIGHT_STATE_IDLE_MAINTENANCE是同一个case处理逻辑,这两个状况的下一个状况都是LIGHT_STATE_IDLE。

假如上一个状况是LIGHT_STATE_IDLE_MAINTENANCE,则

mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,(long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR)),LIGHT_MAX_IDLE_TIMEOUT为15分钟,LIGHT_IDLE_FACTOR为2

所以light doze的IDLE时刻为5分钟,10分钟,最终稳定为15分钟。

当state的状况转化成IDLE后,这儿会恳求wakelock锁,让cpu唤醒,然后经过MSG_REPORT_IDLE_ON_LIGHT的Handler使命进行逻辑处理,然后再开释wakelock锁,让cpu休眠。

剩余的几种状况函数转化都在上面的函数中有注释,就不具体讲解了。

Doze约束逻辑

咱们接着看MSG_REPORT_IDLE_ON_LIGHT中做了哪些作业

case MSG_REPORT_IDLE_ON:
case MSG_REPORT_IDLE_ON_LIGHT:: {
    final boolean deepChanged;
    final boolean lightChanged;
    if (msg.what == MSG_REPORT_IDLE_ON) {
        //告诉PMS设置Deep Doze形式处于IDLE状况
        deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
        //告诉PMS为Light Doze形式不处于IDLE状况
        lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
    } else {
        //告诉PMS设置Deep Doze形式不处于IDLE状况
        deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
        //告诉PMS为Light Doze形式处于IDLE状况
        lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
    }
    try {
        //告诉NetworkPolicyManager进入IDLE状况,进行网络拜访的约束
        mNetworkPolicyManager.setDeviceIdleMode(true);
        //告诉BatteryStatsService核算Light Doze或许Deep Doze进入IDLE状况
        mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
                ? BatteryStats.DEVICE_IDLE_MODE_DEEP
                : BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
    } catch (RemoteException e) {
    }
    //发送DeepDoze形式改动的播送
    if (deepChanged) {
        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
    }
    //发送Light形式改动的播送
    if (lightChanged) {
        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
    }
    //开释wakelock
    mGoingIdleWakeLock.release();
} break;

能够看到,Deep Doze和Light Doze在进入IDLE状况后的逻辑处理在同一个地方。这儿依据形式的不同,告诉PowerServiceManager,NetworkPolicyManager,BatteryStats等进行不同的优化战略。这儿首要做的作业有这几件

  1. 调用mLocalPowerManager.setDeviceIdleMode设置是否是Deep Doze的Idle状况,假如为Idle,这一步会将运用设置成疏忽WakeLock的状况
  1. 调用mLocalPowerManager.setLightDeviceIdleMode设置是否是Light Doze的Idle状况
  1. 调用mNetworkPolicyManager.setDeviceIdleMode(true),经过添加防火墙规矩,来进行网络拜访约束
  1. 调用BatteryStats.noteDeviceIdleMode进行状况变更及耗时核算
  1. 调用sendBroadcastAsUser发送播送,进入Deep Doze或许Light Doze的Idle状况
  1. 开释WakeLock

Doze约束逻辑撤销

Light Doze和Deep Doze进入MAINTENCANCE后都会撤销各种约束,撤销的逻辑在MSG_REPORT_IDLE_OFF的handler使命中处理。

case MSG_REPORT_IDLE_OFF: {
    // mActiveIdleWakeLock is held at this point
    EventLogTags.writeDeviceIdleOffStart("unknown");
    final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
    final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
    try {
        mNetworkPolicyManager.setDeviceIdleMode(false);
        mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
                null, Process.myUid());
    } catch (RemoteException e) {
    }
    if (deepChanged) {
        incActiveIdleOps();
        getContext().sendOrderedBroadcastAsUser(mIdleIntent, UserHandle.ALL,
                null, mIdleStartedDoneReceiver, null, 0, null, null);
    }
    if (lightChanged) {
        incActiveIdleOps();
        getContext().sendOrderedBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
                null, mIdleStartedDoneReceiver, null, 0, null, null);
    }
    decActiveIdleOps();
} break;

Standby形式

Doze形式是针对整个系统的耗电优化形式,而Standby形式,即运用群组待机形式是针对单个运用的耗电优化形式,它是Android7.0引进的,当运用处于搁置状况时,系统会依据运用运用最近运用的时刻和频率,设置成对应的群组,不同的群组下,jobs,alarm和network的运用约束程度不相同。

Standby形式的进入和退出

当用户有一段时刻未触摸运用时,系统便会判别进入Standby形式,以下条件下不适用或许会退出Standby形式:

  1. 用户自动发动该App;
  1. 该App当时有一个前台进程(或包括一个活动的前台服务,或被另一个activity或前台service运用);
  1. 在确定屏幕或告诉栏中看到的告诉
  1. 系统运用
  1. 充电状况

Standby形式优化战略

运用在进入Standby后,会依据该运用所属的状况,对Jobs,Alarms和Network进行相应的约束,运用的状况分为五个等级

  1. Activie:假如用户当时正在运用运用,运用将被归到“atcitive”状况中
  1. WORKING_SER:假如运用经常运转(12至24小时内运用过),但当时未处于活跃状况,它将被归到“作业集”群组中。 例如,用户在大部分时刻都发动的某个社交媒体运用或许就归于“作业集”群组。 假如运用被直接运用,它们也会被升级到“作业集”群组中 。
  1. FREQUENT:假如运用会定期运用,但不是每天都必须运用 (亮屏时刻差超越1小时、运用时刻差超越24小时) ,它将被归到“常用”群组中。 例如,用户在健身房运转的某个训练跟踪运用或许就归于“常用”群组。
  1. RARE:假如运用不经常运用 (亮屏时刻差超越2小时、运用时刻差超越48小时) ,那么它归于“很少运用”群组。 例如,用户仅在入住酒店期间运转的酒店运用就或许归于“很少运用”群组。假如运用处于“很少运用”群组,系统将对它运转作业、触发警报和接纳高优先级 FCM 音讯的才能施加严厉约束。系统还会约束运用连接到网络的才能。
  1. NEVER:装置可是从未运转过的运用会被归到“从未运用”群组中。 系统会对这些运用施加极强的约束。

下面是对这个五个等级的运用的约束情况

developer.android.com/topic/perfo…

Android耗电原理及飞书耗电治理

Standby形式完成原理

Standby形式的逻辑完成在AppStandbyController方针中,该方针供给了reportEvent,来让外部进行app行为改变的告诉,如ams,NotificationManagerService等都会调用reportEvent来奉告app有行为改变并更新Bucket

AppStandbyController.java

更新Bucket

void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
    if (!mAppIdleEnabled) return;
    synchronized (mAppIdleLock) {
        // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
        // about apps that are on some kind of whitelist anyway.
        final boolean previouslyIdle = mAppIdleHistory.isIdle(
                event.mPackage, userId, elapsedRealtime);
        // Inform listeners if necessary
        if ((event.mEventType == UsageEvents.Event.ACTIVITY_RESUMED
                || event.mEventType == UsageEvents.Event.ACTIVITY_PAUSED
                || 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
                || event.mEventType == UsageEvents.Event.FOREGROUND_SERVICE_START)) {
            final AppUsageHistory appHistory = mAppIdleHistory.getAppUsageHistory(
                    event.mPackage, userId, elapsedRealtime);
            final int prevBucket = appHistory.currentBucket;
            final int prevBucketReason = appHistory.bucketingReason;
            final long nextCheckTime;
            final int subReason = usageEventToSubReason(event.mEventType);
            final int reason = REASON_MAIN_USAGE | subReason;
            //依据运用行为更新bucket
            if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN
                    || event.mEventType == UsageEvents.Event.SLICE_PINNED) {
                mAppIdleHistory.reportUsage(appHistory, event.mPackage,
                        STANDBY_BUCKET_WORKING_SET, subReason,
                        0, elapsedRealtime + mNotificationSeenTimeoutMillis);
                nextCheckTime = mNotificationSeenTimeoutMillis;
            } else if (event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION) {
                mAppIdleHistory.reportUsage(appHistory, event.mPackage,
                        STANDBY_BUCKET_ACTIVE, subReason,
                        0, elapsedRealtime + mSystemInteractionTimeoutMillis);
                nextCheckTime = mSystemInteractionTimeoutMillis;
            } else if (event.mEventType == UsageEvents.Event.FOREGROUND_SERVICE_START) {
                // Only elevate bucket if this is the first usage of the app
                if (prevBucket != STANDBY_BUCKET_NEVER) return;
                mAppIdleHistory.reportUsage(appHistory, event.mPackage,
                        STANDBY_BUCKET_ACTIVE, subReason,
                        0, elapsedRealtime + mInitialForegroundServiceStartTimeoutMillis);
                nextCheckTime = mInitialForegroundServiceStartTimeoutMillis;
            } else {
                mAppIdleHistory.reportUsage(appHistory, event.mPackage,
                        STANDBY_BUCKET_ACTIVE, subReason,
                        elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
                nextCheckTime = mStrongUsageTimeoutMillis;
            }
            //设置延时音讯,依据运用时刻更新bucket
            mHandler.sendMessageDelayed(mHandler.obtainMessage
                    (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, event.mPackage),
                    nextCheckTime);
            final boolean userStartedInteracting =
                    appHistory.currentBucket == STANDBY_BUCKET_ACTIVE &&
                    prevBucket != appHistory.currentBucket &&
                    (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
            maybeInformListeners(event.mPackage, userId, elapsedRealtime,
                    appHistory.currentBucket, reason, userStartedInteracting);
            if (previouslyIdle) {
                notifyBatteryStats(event.mPackage, userId, false);
            }
        }
    }
}

reportEvent会依据mEventType进行一次Bucket更新,并依据mEventType设置一次延时使命,这个延时使命中会再次依据运用的运用行为再次更新Bucket。其间Notification类型的音讯的延迟时刻为12小时,SYSTEM_INTERACTION为10分钟,其他的mStrongUsageTimeoutMillis为1小时

MSG_CHECK_PACKAGE_IDLE_STATE的handler音讯首要依据运用时长更新Bucket

static final int[] THRESHOLD_BUCKETS = {
        STANDBY_BUCKET_ACTIVE,
        STANDBY_BUCKET_WORKING_SET,
        STANDBY_BUCKET_FREQUENT,
        STANDBY_BUCKET_RARE
};
static final long[] SCREEN_TIME_THRESHOLDS = {
        0,
        0,
        COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
        COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR
};
static final long[] ELAPSED_TIME_THRESHOLDS = {
        0,
        COMPRESS_TIME ?  1 * ONE_MINUTE : 12 * ONE_HOUR,
        COMPRESS_TIME ?  4 * ONE_MINUTE : 24 * ONE_HOUR,
        COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR
};
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
@StandbyBuckets int getBucketForLocked(String packageName, int userId,
        long elapsedRealtime) {
    int bucketIndex = mAppIdleHistory.getThresholdIndex(packageName, userId,
            elapsedRealtime, mAppStandbyScreenThresholds, mAppStandbyElapsedThresholds);
    return THRESHOLD_BUCKETS[bucketIndex];
}

AppIdleHistory.java

int getThresholdIndex(String packageName, int userId, long elapsedRealtime,
        long[] screenTimeThresholds, long[] elapsedTimeThresholds) {
    ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
    AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
            elapsedRealtime, false);
    if (appUsageHistory == null) return screenTimeThresholds.length - 1;
    //app最终一次亮屏运用到现在,现已有多久的亮屏时刻
    long screenOnDelta = getScreenOnTime(elapsedRealtime) - appUsageHistory.lastUsedScreenTime;
    //app最终一次运用到现在的时刻点
    long elapsedDelta = getElapsedTime(elapsedRealtime) - appUsageHistory.lastUsedElapsedTime;
    for (int i = screenTimeThresholds.length - 1; i >= 0; i--) {
        if (screenOnDelta >= screenTimeThresholds[i]
            && elapsedDelta >= elapsedTimeThresholds[i]) {
            return i;
        }
    }
    return 0;
}

App耗电剖析

Battery Historian

Android官方供给了Battery Historian来进行电量运用的剖析,Battery Historian 图表会显现一段时刻内与电源相关的事件。

Android耗电原理及飞书耗电治理

从上面的图也能够看到,进入到Doze后,BLE scanning,GPS等就无行为了,而且cpu,wakelock等活动的频率也变低了

咱们还能经过Battery Historian获取运用的

  • 在设备上的估量耗电量。
  • 网络信息。
  • 唤醒确定次数。
  • 服务
  • 进程信息

Android耗电原理及飞书耗电治理

官方文档现已讲的十分具体,就不在这儿细说了

developer.android.com/topic/perfo…

Slardar

Slardar电量相关的核算方针项包括

  • app处于前台时,供给电流作为耗电方针
  • 经过采集app的cpu、流量和gps等的运用,来核算出一个加权和作为耗电方针
  • 电池温度,作为衡量耗电的辅佐参考

归因项有

  • 高CPU能够经过cpu菜单查看高耗CPU的仓库
  • gps(location),alarm和wakelock运用在超越指定持有时刻和频次后,会上报当时的采集仓库

虽然Slardar有上报许多功耗相关方针,可是目前还只能作为全体功耗的参考,而且许多方针波动起伏大,无法对更细化的管理供给帮助

飞书耗电管理

管理方针

  1. 消除主流手机的高功耗提醒
  1. 建立健全的功耗监控及防劣化系统

管理计划

在前面咱们现已知道耗电=模块功率✖️模块耗时,所以管理实质便是在不影响功能和功用的情况下,削减飞书中所运用到的模块的耗时,而且咱们了解了系统进行耗电优化的战略,在飞书的耗电管理中,也能够同样的参考对应的战略。

管理计划首要分为监控的完善和耗电的管理

功耗管理

为了能系统化的进行功耗管理,这儿分为了针对耗电模块进行管理和针对状况进行履行两大类

分模块管理

模块的耗电管理首要体现在下面几个方面

  • 1.CPU
    • 死循环函数,高频函数,高耗时函数,无效函数等不必要的cpu耗费或耗费较多的函数管理
    • cpu运用率较高的场景及事务管理
  • 2.GPU和Display
    • 过度制作,过多的动画,不可见区域的动画等浪费GPU的场景管理
    • 自动下降屏幕亮度,运用深色UI等计划下降屏幕电量耗费
  • 3.网络
    • 不影响事务和功能前提下,下降网络拜访频率
    • Doze状况时削减无效的网络恳求
  • 4.GPS
    • 对运用GPS的场景,如小程序等,合理的下降精度,削减恳求频率
  • 5.Audio、Camera、Video等项

除了分模块管理,还针对状况进行管理,首要状况有这几种

分状况管理

1.前台状况
  • 烘托场景优化
  • 音视频等场景优化
  • ……
2.后台状况
  • task使命降频或许丢掉
  • 网络拜访降频,适配Doze形式
  • 削减cpu耗费较多的函数履行
  • 削减gps等高功耗场景

完善功耗剖析和监控系统

为了能更好的进行管理,完善的功耗剖析和监控系统是不可避免的,不然就会出现无的放矢的状况。在这一块首要建设的点有

  • 1. 完善的CPU耗费监控

    • 前后台高cpu耗费场景监控,高cpu耗费线程监控(slardar已有)
    • 高频task,高耗时task,后台task监控(已有)
    • 耗费较高,耗时较高的函数监控
  • 2. GPU和Display耗费监控

    • 动画场景,过度制作检测,View层级检测,屏幕电量耗费监控等
  • 3. 网络

    • Rust,OkHttp及其他网络恳求场景,频率,耗费监控
    • 后台网络拜访监控
  • 4. GPS

    • GPS运用场景,时长,电量耗费监控
  • 5. Audio、Camera、Video

    • 运用场景,时长,电量耗费监控
  • 6. 全体和场景的电量耗费

    • 飞书全体的电量耗费和不同场景的电量耗费,用来度量版别功耗的质量