Android Doze状态源码分析

1、概述

 Doze模式也称低功耗模式,是google在Android6.0开始引入的,目的是为了帮助用户延长电池的使用时间,当处于该模式下的时候系统会限制应用Alarm、wakelock、wifi以及网络等系统资源的使用。固然系统会按期退出Doze模式一小段时间,让应用完成其延迟的活动。在此维护期内,系统会运行全部待处理的Alarm、Job以及wakelock等,并容许应用访问网络。这里借助官方文档的一张图。android

 从这张图能够发现,在系统灭屏一段时间以后,若是手机状态没有被改变,那么系统会进入到Doze状态(idle),并在一段时间以后退出Doze状态进入到mantenance期,在每一个维护期结束时,系统会再次进入低电耗模式,暂停网络访问并推迟做业、wakelock和闹钟。随着时间的推移,系统安排维护期的次数愈来愈少,处于Doze状态的时间会愈来愈长,这有助于设备在未充电的状况下长期处于不活动状态时下降系统功耗。

 一旦用户经过移动设备、打开屏幕或链接至充电器唤醒设备,系统就会当即退出低电耗模式,而且全部应用都会恢复正常活动。shell

一、分类

 即便是Doze模式,google也将其分为了两个大类:api

 (1)lightDoze模式:也就是轻度Doze状态,系统处于灭屏而且没有充电,可是设备可能处于移动;网络

 (2)DeepDoze模式:系统处于灭屏状态而且没有充电同时还须要系统保持静止状态。并发

 其实根据Doze模式的状态会发现和人睡觉的状态很相似,当人处于睡眠状态的时候(1)不会吃东西;(2)身体会保持必定的静止状态;(3)退出Doze状态就相似于人在睡眠过程的呼吸了。ide

二、限制

 当系统处于Doze状态的时候,会对应用进行以下的限制:函数

 (1)暂停网络访问;oop

 (2)系统忽略唤醒类wakelock;ui

 (3)标准的Alarm(包括setExact()和setWindow())将会被推迟到下一个维护阶段。若是须要设置在设备处于低电耗模式时触发的闹钟,可使用 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle()。使用 setAlarmClock() 设置的闹钟将继续正常触发,系统会在这些闹钟触发以前不久退出低电耗模式;this

 (4)系统不执行wifi扫描;

 (5)系统不容许运行同步适配器;

 (6)系统不容许运行Job。

2、源码讲解

一、DeviceidleController介绍

 Doze模式是由DeviceIdleController这个类进行控制的,它和PowerManagerService等同样都是属于系统服务,而且在SystemServer中进行启动注册。因为它不像PowerMaangerService等系统服务会提供一个代理类诸如PowerManager给应用使用;所以,在理解该类的过程当中则须要从SystemServer启动DeviceIdleController开始。相关流程图以下所示

1.一、onStart

 在onstart方法中主要作了以下事情:

(1)初始化部分变量;

(2)将配置文件中的白名单应用读取出来存储到列表中;

(3)注册LocalService和Binder供系统使用,若是外界应用想要使用到注册的Binder对象则必须经过反射的方式进行使用。

1.二、onBootPhase

 在onBootPhase方法中主要作了以下事情:

(1)首先判断系统Service是否已经启动完毕;

(2)初始化诸如AlarmManager、PowerManager以及ConnectivityService等系统服务;用于设置Alarm,获取wakelock以及获取是否有网络等操做;

(3)初始化lightDoze以及Doze相关Intent,当这两种状态发生变化的时候用于通知系统中其余模块;

(4)初始化广播接收器,其中包括亮灭屏、网络变化、包移除以及电池状态变化等广播,用于改变系统当前所处状态;

(5)更新当前网路状态以及系统状态。

二、核心源码介绍

2.1 相关变量讲解

 在正式介绍lightDoze模式和Doze模式下各个状态之间的转换以前,首先须要去了解一下其中所涉及到的一些变量的含义,否则看得过程当中老是云里雾里的。

2.1.1 lightDoze相关变量介绍

在DeviceIdleController中定义了以下几个和lightDoze模式相关联的变量值:

boolean mForceIdle;//该变量值主要是经过adb shell方式进行赋值,例如adb shell dumpsys deviceidle force-idle deep/light,让系统强制进入到Doze模式
LIGHT_STATE_ACTIVE = 0;//当前系统处于活动状态,好比亮屏;
LIGHT_STATE_INACTIVE = 1;//当前系统处于非活动状态,好比灭屏;
LIGHT_STATE_PRE_IDLE = 3;//当前系统还有没有完成的任务,须要等待任务完成才能进入到idle状态;
LIGHT_STATE_IDLE = 4;//系统当前处于lightDoze状态,此时网络、wakelock等都会受到限制;
LIGHT_STATE_WAITING_FOR_NETWORK = 5;//当前系统仍然处于lightDoze状态,可是须要等待系统有网络以后才能进入到维护状态;
LIGHT_STATE_IDLE_MAINTENANCE = 6;//系统处于到了维护阶段;
LIGHT_STATE_OVERRIDE = 7;//表示lightDoze状态被覆盖了,开始进入DeepDoze状态
复制代码

2.1.1 lightDoze相关变量介绍

在DeviceIdleController中定义了以下几个和deepDoze模式相关联的变量值:

//当前系统处于active状态
private static final int STATE_ACTIVE = 0;
//系统处于inactive状态,也就是灭屏且未充电状态,这个时候系统等待进入deepIdle
private static final int STATE_INACTIVE = 1;
//表示系统刚结束inactive状态,准备进入deepidle状态
private static final int STATE_IDLE_PENDING = 2;
//表示系统正在感应设备是否被移动
private static final int STATE_SENSING = 3;
//表示设备正在定位,并获取当前定位精度
private static final int STATE_LOCATING = 4;
//系统当前处于deepidle状态
private static final int STATE_IDLE = 5;
//系统处于维护状态
private static final int STATE_IDLE_MAINTENANCE = 6;
复制代码

2.2 代码讲解

 对lightDoze和DeepDoze两种模式下不一样变量的含义有了清楚的认识以后那么就能够正式开始对系统是如何退出和进入Doze模式的代码分析工做了。首先,咱们须要明白Doze模式的退出和进入都是根据系统自身行为来进行控制的,而在前面咱们也了解到若是想要进入到lightDoze模式则须要系统灭屏而且未充电,进入DeepDoze模式除了灭屏和未充电以外还须要保持设备静止。所以,咱们的入手点就是亮灭屏广播接收器和电池变化广播接受器了。

 在亮灭屏广播接收器中会调用到updateInteractivityLocked函数中,在该函数中首先会获取到屏幕状态,而后根据屏幕状态判断是否须要开始进入Doze状态,相关源码以下(省略非必要代码):

void updateInteractivityLocked() {
    //从PowerManager中获取当前是否处于可交互状态(亮屏或者屏保状态)
    boolean screenOn = mPowerManager.isInteractive();
    //若是系统从亮屏状态变为灭屏状态
    if (!screenOn && mScreenOn) {
        mScreenOn = false;
        //若是没有强制进入到Doze状态,
        if (!mForceIdle) {
            becomeInactiveIfAppropriateLocked();
        }
    } else if (screenOn) {
        mScreenOn = true;
        //若是没有强制进入Doze状态而且屏幕没有被锁定则将全部到数据从新初始化并cancel所设置到Alarm以及中止设备是否移动检测
        if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) {
            becomeActiveLocked("screen", Process.myUid());
        }
    }
}
复制代码

 该方式是将系统变为inactive和active的入口而已,真正idle的各个状态切换须要继续看becomeInactiveIfAppropriateLocked函数;相关源码以下:

/**
* 在讲解方法以前首先了解一下下面两个变量:
* (1)mDeepEnabled:是否容许进入到DeepDoze状态;
* (2)mLightEnabled:是否容许进入到lightDoze状态;
*  这两个值均可以经过dump到方式进行修改,其实也就是至关于控制Doze模式到开关
**/
void becomeInactiveIfAppropriateLocked() {
    //若是系统灭屏而且没有充电
    if ((!mScreenOn && !mCharging) || mForceIdle) {
        //系统上一个状态处于可交互状态而且容许进入深度睡眠
        if (mState == STATE_ACTIVE && mDeepEnabled) {
            //将系统状态设置为不可交互状态
            mState = STATE_INACTIVE;
            //从新初始化各个标志、取消所设置到相关Alarm以及取消sensing状态、位置状态、motion状态检测
            resetIdleManagementLocked();
            //设置Alarm,在30min以后检测是否可以进入到Doze模式(idle状态)
            scheduleAlarmLocked(mInactiveTimeout, false);
        }
        //系统上一个状态处于可交互状态,而且容许进入lightDoze状态
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            //将系统状态设置为不可交互状态
            mLightState = LIGHT_STATE_INACTIVE;
            //cancel掉和lightDoze相关到Alarm
            resetLightIdleManagementLocked();
            //设置Alarm,在5min以后检测是否可以进入到lightDoze模式
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
        }
    }
}
复制代码

 在该函数中,首先判断系统是否处于灭屏且未充电状态,若是是则:

(1)首先判断系统是否知足进入DeepDoze模式,若是知足条件则将DeepDoze相关变量从新初始化并cancel掉原有Alarm,而后设置一个30min后触发的Alarm用于检测系统是否可以进入到DeepDoze状态;

(2)而后还会判断系统是否知足进入到lightDoze模式,若是知足条件则会cancel掉原油lightDoze相关Alarm并设置一个5min后触发到Alarm用于检测系统是否可以进入到lightDoze状态;

 从该方法到实现来看,lightDoze的进入时间确定早于DeepDoze的进入时间,因此接下来首先对lightDoze相关代码进行讲解。

2.2.1 lightDoze代码讲解

 在系统灭屏5min以后,进入到lightDoze模式的Alarm将会被触发,并在AlarmManagerService中回调到mLightAlarmListener中,继而调用到stepLightIdleStateLocked函数中,stepLightIdleStateLocked函数相关源码以下(省略掉部分可有可无代码):

/**
*  该函数中涉及到了不少相关时间,能够经过adb shell dumpsys
*  deviceidle进行查看
**/
void stepLightIdleStateLocked(String reason) {
    if (mLightState == LIGHT_STATE_OVERRIDE) {
        return;
    }
    switch (mLightState) {
        case LIGHT_STATE_INACTIVE:
            //维护时间段的预算时间1min
            mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
            //lightidle状态的最小时间段5min
            mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
            //开始维护的时间
            mMaintenanceStartTime = 0;
            //若是当前有正在进行的任务(Alarm、job以及wakelock)
            if (!isOpsInactiveLocked()) {
                //表示当前有任务须要完成才能进入到lightidle状态
                mLightState = LIGHT_STATE_PRE_IDLE;
                //设置10min后触发到Alarm
                scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);
                break;
            }
        case LIGHT_STATE_PRE_IDLE:
        case LIGHT_STATE_IDLE_MAINTENANCE:
            //若是为true,当前处于maintenance状态即将退出该状态
            if (mMaintenanceStartTime != 0) {
                //当前处于维护状态的实际时间
                long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
                //若是处于维护状态的实际时间小于1min,则下次的维护时间段将加上小于1min的时间段
                if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                    mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
                //若是大于等于1min,则下次的维护时间段将减去大于1min的时间段
                } else {
                    mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
                }
            }
            //从新初始化维护开始时间
            mMaintenanceStartTime = 0;
            //设置退出lightidle的alarm
            scheduleLightAlarmLocked(mNextLightIdleDelay);
            //保持lightidle状态的最大时间为15min
            mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                    (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
            //保持lightidle的时间最小为5min
            if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
            }
            //表示系统当前处于lightidle状态
            mLightState = LIGHT_STATE_IDLE;
            //系统处于idle状态,开始对wakelock、job以及网络等资源进行限制
            mGoingIdleWakeLock.acquire();
            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) {
                mActiveIdleOpCount = 1;
                //系统即将进入到维护状态,所以须要持有wakelock锁防止系统休眠
                mActiveIdleWakeLock.acquire();
                //记录维护的开始时间
                mMaintenanceStartTime = SystemClock.elapsedRealtime();
                //系统处于维护状态的时间最大为5min最小为1min
                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;
                }
                //设置定时alarm以退出维护状态
                scheduleLightAlarmLocked(mCurIdleBudget);
                //标识当前系统处于维护阶段
                mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
                //释放掉wakelock、job以及网络相关限制
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            } else {
               //表示当前须要等待网络才能进入维护阶段
                scheduleLightAlarmLocked(mNextLightIdleDelay);
                mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
            }
            break;
    }
}
复制代码

 该方法实现了lighDoze模式相关各个状态之间的相互转换,以及处于维护阶段和lightIdle状态的时间计算。各个状态之间关系的状态转换图以下:

 处于维护阶段的预算时间(mCurIdleBudget)最大值为5min最小值为1min,默认值为1min。若是实际处于维护阶段的时间(duration)不是1min,那么下次处于维护阶段的预算时间将加上1min-duration的值。

 处于lightidle状态时间(mNextLightIdleDelay)的最大值为15min最小值为5min,默认值为5min。下次处于lightidle状态时间=上次处于lightidle状态时间*2并与15min取最小值。

 也就是随着处于lightidle状态的时间愈来愈长,那么处于lightIdle状态的时间也会愈来愈长可是最大值为15min,而处于maintenance状态的时间基本就是在1min左右。

2.2.2 资源限制、放开代码讲解

 当进入退出idle状态的时候会对系统资源诸如wakelock、alarm以及网络等进行限制和放开,在该类中经过调用对应模块所提供的api或者经过广播的方式通知到相应的模块,具体的资源限制实现并无在该类中。代码(省略掉部分代码)以下:

@Override public void handleMessage(Message msg) {
        switch (msg.what) {
            //将列表中的白名单应用信息写入到文件中
            case MSG_WRITE_CONFIG: {
                handleWriteConfigFile();
            } break;
            //idle和lightidle状态资源处理
            case MSG_REPORT_IDLE_ON:
            case MSG_REPORT_IDLE_ON_LIGHT: {
                final boolean deepChanged;
                final boolean lightChanged;
                //用于通知PowerManagerService如今系统已经处于idle状态,并限制不符合要求的wakelock
                if (msg.what == MSG_REPORT_IDLE_ON) {
                    deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
                    lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                } else {
                    deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                    lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
                }
                try { 
                    //通知NetwaorkPolicyManagerService如今系统已经处于idle状态,须要从新更新规则
                    mNetworkPolicyManager.setDeviceIdleMode(true);
                } catch (RemoteException e) {
                }
                //发送广播给须要知道Doze模式状态改变的模块
                if (deepChanged) {
                    getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                }
                if (lightChanged) {
                    getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
                }
                //资源限制已经处理完成,释放掉wakelock锁
                mGoingIdleWakeLock.release();
            } break;
            //退出idle状态进入到maintenance阶段
            case MSG_REPORT_IDLE_OFF: {
                //放开wakelock资源限制
                final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                try {
                    //放开网路限制
                    mNetworkPolicyManager.setDeviceIdleMode(false);
                } catch (RemoteException e) {
                }
                //发送广播给须要知道Doze模式状态改变的模块
                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;
            //进入到active状态
            case MSG_REPORT_ACTIVE: {
                //获取进入active的缘由以及唤醒系统的uid
                String activeReason = (String)msg.obj;
                int activeUid = msg.arg1;
                //放开全部的资源限制并发送doze状态变化广播
                final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
                try {
                    mNetworkPolicyManager.setDeviceIdleMode(false);
                    mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
                            activeReason, activeUid);
                } catch (RemoteException e) {
                }
                if (deepChanged) {
                    getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
                }
                if (lightChanged) {
                    getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
                }
            } break;
          .................
    }
复制代码

 对于lightDoze模式中各个状态的切换流程以及是如何通知到系统对wakelock、job以及网络等资源进行管控的大体代码是讲解的差很少。大体的文字叙述流程总结以下:

(1)当手机处于灭屏和未充电而且当前系统处于active状态的时候,则会将系统状态变动为inactive状态,同时将数据从新初始化为原始数据并清空上次设置的相关Alarm,继而设置一个5min中的alarm来判断系统是否能够进入到lightidle状态;

(2)当系统处于灭屏状态5min,而且在这之间没有亮灭屏以及充电等操做,那么系统就会经过调用stepLightIdleStateLocked方法来判断系统是否能够进入到lightidle状态;

(3)首先会初始化处于mantenance状态和ligthidle状态的时间,而后会判断当前是否有alarm、job等活动正在进行,若是存在则会10min(设置Alarm)以后再进入到lightidle状态,不然直接进入到lightidle状态;

(4)当进入到lightidle状态以后,会经过发送广播和调用相关服务api的方式来进行通知,各个模块在接收到通知以后会对相关的资源作出必定的限制;

(5)若是当前时间是进入到mantenance状态,可是并无网络链接,那么系统就会进入到等到网络链接状态(其实仍是lightidle状态,只是将mLightstate变动为了LIGHT_STATE_WAITING_FOR_NETWORK),在mNextLightIdleDelay(处于idle状态到时间)以后无论是否有网络链接都会进入到mantenance状态;

(6)放开在lightidle状态所限制到资源,通知各个模块的方式也是调用对应模块的api和发送广播,固然在系统处于mantenance状态的时候会持有wakelock锁以防止系统休眠。当维护状态的时间到达以后又会走到步骤(4)以此往复。

2.2.2 deepDoze代码讲解

 deepDoze相对于lightDoze而言,总体的状态变化流程大体类似只是增长了位置变化监听而已。

 在讲解进入到lightDoze状态过程当中讲到了方法becomeInactiveIfAppropriateLocked,在该方法中首先会判断系统是否处于灭屏而且未充电,若是知足条件则会先判断系统是否知足进入到deepDoze状态而后再判断是否可以进入到lightDoze状态,而当系统知足进入到deepDoze的条件时,首先会将相关变量从新初始化并cancel掉上次设置的alarm并从新设置一个30min后执行的alarm,当相关alarm时间到达以后会调用到方法stepIdleStateLocked,所以下面则围绕该方法进行讲解。

 在讲解该方法以前首先须要了解一下关于设备位置变化监听相关知识讲解。在Android中有四种方式来获取设备的位置相关信息,下面只介绍当前所使用到的两种方式(一般也是这两种方式配合使用来获取设备的地理位置信息):

(1)经过GPS的方式获取位置的经纬度信息,该方法获取的地理位置信息精度高,可是速度慢而且只能在户外使用;

(2)经过移动网路基站或者wifi来获取地理位置信息,该方法定位速度快可是精确度不高;

 相关源码(省略部分源码)以下:

void stepIdleStateLocked(String reason) {
    final long now = SystemClock.elapsedRealtime();
    //若是在1h以内存在唤醒系统的Alarm,则重置进入到deepIdle状态的时间
    if ((now+mConstants.MIN_TIME_TOLARM) > mAlarmManager.getNextWakeFromIdleTime()) {
        if (mState != STATE_ACTIVE) {
            becomeActiveLocked("alarm", Process.myUid());
            becomeInactiveIfAppropriateLocked();
        }
        return;
    }
    switch (mState) {
        case STATE_INACTIVE:
            //注册sensor以监听设备是否发生了位置变化
            startMonitoringMotionLocked();
            //系统处于STATE_IDLE_PENDING状态为30min
            scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
            //初始化处于维护状态时间为5min
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            //初始化处于deepidle状态的时间1h
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            //当前系统处于STATE_IDLE_PENDING状态
            mState = STATE_IDLE_PENDING;
            break;
        case STATE_IDLE_PENDING:
            //当前处于STATE_SENSING状态,用于检测当前设备是否有移动
            mState = STATE_SENSING;
            //设置一个4min后到alarm检测当前是否仍然处于STATE_SENSING状态,若是是则会再次调用becomeInactiveIfAppropriateLocked方法
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
            //取消监听设备位置更新以及GPS位置更新
            cancelLocatingLocked();
            mNotMoving = false;
            mLocated = false;
            mLastGenericLocation = null;
            mLastGpsLocation = null;
            //开始检测是否移动
            mAnyMotionDetector.checkForAnyMotion();
            break;
        case STATE_SENSING:
            //清除掉sensing状态相关的alarm
            cancelSensingTimeoutAlarmLocked();
            //用于获取当前定位精度
            mState = STATE_LOCATING;
            //获取定位精度的时间为30s,若是30s以后没有获取到定位精度或者获取到的定位精度小于20m而且没有移动则会直接进入到deepIdle状态
            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;
            }
            //若是当前没有任何定位则直接中止检测并进入到deepIdle状态,否在在30s以后再中止检测
            if (mLocating) {
                break;
            }
        //中止定位和设备是否处于运动状态检测,可是并无中止sensing的检测
        case STATE_LOCATING:
            cancelAlarmLocked();
            cancelLocatingLocked();
            mAnyMotionDetector.stop();
        //进入到idle状态
        case STATE_IDLE_MAINTENANCE:
            //在1h以后退出deepidle状态
            scheduleAlarmLocked(mNextIdleDelay, true);
            //下次处于deepidle状态的时间是上次处于idle状态时间的2倍
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
            //处于deepidle状态的最大时间为6h
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
            //处于deepidle状态的最小时间为1h
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;
            }
            //当前处于deepidle状态
            mState = STATE_IDLE;
            //lightidle状态已经被deepidle状态覆盖了,所以清除掉lightidle相关的alarm
            if (mLightState != LIGHT_STATE_OVERRIDE) {
                mLightState = LIGHT_STATE_OVERRIDE;
                cancelLightAlarmLocked();
            }
            //限制系统wakelock、job、alarm以及网络等资源
            mGoingIdleWakeLock.acquire();
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
            break;
        //系统进入到mantenance状态
        case STATE_IDLE:
            mActiveIdleOpCount = 1;
            mActiveIdleWakeLock.acquire();
            //处于mantenance状态为5min
            scheduleAlarmLocked(mNextIdlePendingDelay, false);
            mMaintenanceStartTime = SystemClock.elapsedRealtime();
            //下次处于维护状态的时间为上次的2倍,可是最大值为10min
            mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                    (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
            //处于维护状态的最小时间为5min
            if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
                mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
            }
            //当前系统处于维护状态
            mState = STATE_IDLE_MAINTENANCE;
            //释放因为处于idle状态而被限制的相关资源
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
            break;
    }
}
复制代码

 呼~,终于把进入到deepIdle的整个流程走完了,固然里面还涉及到了一些方法在后面还须要继续分析,在这里对deepIdle的整个大体流程简单的总结一下。

(1)在系统灭屏以后而且手机没有充电那么系统将清除上次进入到deepIdle相关的数据,而且设置一个30min以后的Alarm来检测是否能够进入到deepIdle状态;

(2)若是系统灭屏了30min而且没有充电,则会调用到stepIdleStateLocked方法判断是否能够进入到deepIdle状态;

(3)若是在1h之内系统中存在能够唤醒系统的Alarm则会直接return当前方法并从新初始化相关条件进入deepDoze状态;若是没有则会继续走如下步骤;

(4)进入到STATE_IDLE_PENDING状态,并设置Sensor传感器检测,用于判断手机是否被移动,同时设置一个30min后触发的alarm进入到STATE_SENSING状态;

(5)在STATE_SENSING状态检测设备是否被移动,若是被移动了则会将当前状态变动为active状态,并从新调用becomeInactiveIfAppropriateLocked方法,从头开始进行状态检测;若是没有被移动则会进入到STATE_LOCATING状态;

(6)在STATE_LOCATING状态系统经过LocationManager用于检测移动网络定位和gps定位的精度是否在20m之内,若是精度在20m之内而且在步骤(5)也没有检测到设备位置发生了变化,则清除移动数据网络定位和gps定位以及中止设备移动检测;并在接下来进入到deepIdle状态;

(7)进入到deepidle状态,并计算下一次处于deepIdle的时间,同时显示job、wakelock、alarm以及网络等资源限制。若是没有其它行为打断这种状态,那么在设定的时间以后退出该状态进入到mantenance状态;

(8)进入到mantenance状态,并释放掉由于进入到deepIdle状态而被限制的资源,在设定的时间以后退出维护状态并重复步骤(7)。

 随着处于deepIdle的次数愈来愈多,那么处于deepIdle状态的时间也会愈来愈长,最长为6h;处于维护阶段的时间也会愈来愈长,最长为10min。而当sensor检测到设备被移动了以后就会打破这种平衡,并返回到步骤(1)从新进行状态转换。也就是说当检测到设备运动到时候就会打破全部到平衡已经状态检测过程,并从新开始。

2.2.3 运动检测代码讲解

 当系统处于STATE_SENSING状态的时候会调用AnyMotionDetector中的checkForAnyMotion方法,最后会回调到onAnyMotionResult方法中,代码以下:

public void onAnyMotionResult(int result) {
    //若是返回-1,也就是不清楚设备是否移动,则会清除在sensing状态所设置到alarm
    if (result != AnyMotionDetector.RESULT_UNKNOWN) {
        synchronized (this) {
            cancelSensingTimeoutAlarmLocked();
        }
    }
    //若是设备被移动了或者不知道是否被移动,则都会被看成设备被移动处理,具体的处理细节后面会讲到
    if ((result == AnyMotionDetector.RESULT_MOVED) ||
        (result == AnyMotionDetector.RESULT_UNKNOWN)) {
        synchronized (this) {
            handleMotionDetectedLocked(mConstants.INACTIVE_TIMEOUT, "non_stationary");
        }
    //若是设备没有被移动,好比一直被放在桌子上面
    } else if (result == AnyMotionDetector.RESULT_STATIONARY) {
        //判断当前系统所处的状态
        if (mState == STATE_SENSING) {
            synchronized (this) {
                mNotMoving = true;
                //继续保持系统状态并将系统转换为STATE_LOCATING状态
                stepIdleStateLocked("s:stationary");
            }
        } else if (mState == STATE_LOCATING) {
            synchronized (this) {
                mNotMoving = true;
                //若是没有移动设备,而且location位置检测精度在20m之内
                if (mLocated) {
                    stepIdleStateLocked("s:stationary");
                }
            }
        }
    }
}
复制代码

2.2.4 设备若是被移动代码讲解

 不论在检测设备检测到设备被移动仍是所设置到sensor检测到了设备被移动了,最终都会调用到handleMotionDetectedLocked方法中,相关代码以下:

void handleMotionDetectedLocked(long timeout, String type) {
    boolean becomeInactive = false;
    //判断当前系统是否处于active状态
    if (mState != STATE_ACTIVE) {
        //若是设备被移动了并不会影响到lightDoze状态,所以须要排除lightDoze状态
        boolean lightIdle = mLightState == LIGHT_STATE_IDLE
                || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK
                || mLightState == LIGHT_STATE_IDLE_MAINTENANCE;
        //若是当前不处于lightDoze状态,则会释放掉因deepDoze状态而被限制到资源
        if (!lightIdle) {
            scheduleReportActiveLocked(type, Process.myUid());
        }
        //从新初始化数据
        mState = STATE_ACTIVE;
        mInactiveTimeout = timeout;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;
        becomeInactive = true;
    }
    //若是已经进入到了deepDoze状态则须要从新初始化lightDoze状态为active状态
    if (mLightState == LIGHT_STATE_OVERRIDE) {
        mLightState = LIGHT_STATE_ACTIVE;
        becomeInactive = true;
    }
    //从新设置alarm以进入到doze状态
    if (becomeInactive) {
        becomeInactiveIfAppropriateLocked();
    }
}
复制代码

2.2.5 获取定位精度相关代码讲解

 当系统处于STATE_LOCATING状态的时候会注册移动数据网络定位以及gps定位相关listener到LocationManagerService中,当有位置变化的时候会回调到相关listener中,它们的实现方式相似,这里只讲解移动数据网络定位回调函数,代码以下:

void receivedGenericLocationLocked(Location location) {
    //当前设备不处于STATE_LOCATING状态,则取消掉相关alarm并直接返回
    if (mState != STATE_LOCATING) {
        cancelLocatingLocked();
        return;

    mLastGenericLocation = new Location(location);
    //若是定位的精度大于20m而且存在gps定位则直接返回,等待30s以后自动进入到deepDoze状态
    if (location.getAccuracy() > mConstants.LOCATION_ACCURACY && mHasGps) {
        return;
    }
    mLocated = true;
    //若是在STATE_SENSING没有检测到设备移动下一步则让系统进入到deepIdle状态
    if (mNotMoving) {
        stepIdleStateLocked("s:location");
    }
}
复制代码

二、退出Doze状态源码讲解

 在文章开篇讲到,若是系统要进入到Doze状态必须知足三个条件,(1)灭屏(2)未充电(3)设备处于静止不动;所以想要退出Doze模式只须要破坏这三种条件之一也就能够了。当设备处于非静止状态是如何破坏deepDoze相关状态在讲解deepDoze相关代码已经讲解到了,所以这里只须要关注条件(1)和(2)两种状况就好了。

 在文章开篇已经讲到了,在onBootPhase函数中注册了亮灭屏广播以及电池变化接收广播以监听系统亮灭屏以及是否充电状态,当监听到系统亮屏或者充电到时候都会调用到becomeActiveLocked方法用于退出Doze模式;相关源码以下:

void becomeActiveLocked(String activeReason, int activeUid) {
    if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
        //放开因Doze模式而被限制到wakelock、alarm、job以及网络等资源
        scheduleReportActiveLocked(activeReason, activeUid);
        //将状态变动为active状态
        mState = STATE_ACTIVE;
        mLightState = LIGHT_STATE_ACTIVE;
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;
        //从新初始化相关变量而且取消掉注册到Alarm以及相关listener
        resetIdleManagementLocked();
        resetLightIdleManagementLocked();
    }
}
复制代码

3、总结

 历经一周左右的时间,系统是如何从active状态进入到lightDoze模式,又是如何在lightDoze模式的基础上进入到Doze模式的总体流程基本上来讲是理完了,固然里面还有不少细节部分并无讲到好比说是如何添加Doze白名单的,都还须要去慢慢琢磨才行。文章中可能存在不少理解不对的地方或者没有叙述清楚的地方,还但愿你们多多指正。

官方文档:developer.android.com/training/mo…

参考博客:blog.csdn.net/FightFightF…

google源码:androidxref.com/

相关文章
相关标签/搜索