Android 8.1 Doze模式分析(二)——Light Doze模式

概述

LightDoze表示轻度doze模式,如果设备处于未充电且屏幕关闭状态,但未处于静止状态时,就会进入Light Doze模式,在LightDoze模式中,会定期进行维护,这种维护会持续N分钟,在维护状态(maintenance)时,会进行网络的访问,和同步、JobScheduler的操作,然后又会进入Idle状态,持续多次。之后如果设备仍旧保持静止,则会进入Deep Doze模式,因此,如果没有运动传感器,则无法检测设备是否处于静止,因此无法进入Deep Doze模式。

原理

在DeviceIdleController(以下简称DIC)中,定义了几个用来表示Light Doze模式有关的值,这些值及其意义如下:


//表示轻度doze模式
private int mLightState;
//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;
//mLightState状态值,表示设备轻度Doze被覆盖,开始进入深度Doze模式
private static final int LIGHT_STATE_OVERRIDE = 7;

从上述代码中可知,共有7个状态类表示Light Doze模式的不同状态。接下来开始分析Light Doze的实现以及其七个状态之间的转换。

在第一部分中分析启动流程时,当DCI启动流程的最后,会调用becomeInactiveIfAppropriateLocked()方法和becomeActiveLocked()方法,这两个方法用来切换交互/不可交互状态,这里先看第一个方法:

void becomeInactiveIfAppropriateLocked() {
    //仅当屏幕灭屏且没充电时,才能进入Doze模式,mForceIdle是shell设置相关
    if ((!mScreenOn && !mCharging) || mForceIdle) {
        //处理深度Doze,该状态表示当前设备处于交互状态,即亮屏、有动作移动
        if (mState == STATE_ACTIVE && mDeepEnabled) {
            //该状态表示DeepDoze的不可交互状态
            mState = STATE_INACTIVE;
            //重置标记值、取消原有的用于DeepDoze的alarm等
            resetIdleManagementLocked();
            //设置DeepAlarm,时间为30mins
            scheduleAlarmLocked(mInactiveTimeout, false);//30mins
        }
        //进入轻度Doze模式
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            //处理LightDoze模式进入不可交互状态
            mLightState = LIGHT_STATE_INACTIVE;
            //取消用于LightDoze的定时Alarm,重置标记值
            resetLightIdleManagementLocked();
            //设置用于LightDoze模式的Alarm,到达时间后回调
            //mLightAlarmListener,时间为5mins
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_
                 AFTER_INACTIVE_TIMEOUT);//5mins
        }
    }
}

如果要执行该方法,首要条件必须是灭屏且放电,或者mForceIdle为true,mForceIdle是用于在adb shell下使用命令设置的,因此分析时可忽略。
如果满足灭屏且放电,则分别进行DeepDoze和LightDoze的判断.这里先分析Light Doze的判断,如果当前LightDoze处于LIGHT_STATE_ACTIVE状态,且mLightEnabled为true,则进行如下的处理:

  • 1.将mLightState变为LIGHT_STATE_INACTIVE状态,表示不可交互状态(灭屏);
  • 2.重置LightDoze相关的工作和值;
  • 3.发送一个5mins的定时Alarm。

在DIC启动时,将mLightState设置为了LIGHT_STATE_ACTIVE,所以这个值是一个默认值。先来看第二点,调用了resetLightIdleManagementLocked(),该方法如下:

void resetLightIdleManagementLocked() {
    //取消mLightAlarmListener要接受的Alarm
    cancelLightAlarmLocked();
}
void cancelLightAlarmLocked() {
    if (mNextLightAlarmTime != 0) {
        mNextLightAlarmTime = 0;
        mAlarmManager.cancel(mLightAlarmListener);
    }
}

如果设有用于LightDoze的定时Alarm,则进行取消。
再来看看第三点,在scheduleLightAlarmLocked()方法中:

void scheduleLightAlarmLocked(long delay) {
    mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
    //到达时间后,回调mLightAlarmListener.onAlarm()
    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            mNextLightAlarmTime, "DeviceIdleController.light", 
            mLightAlarmListener, mHandler);
}

结合第二点和第三点,可以认为重置了一个用于LightDoze的Alarm定时器。mLightAlarmListener用来接受Alarm的回调,定时器到达指定时间后,会触发mLightAlarmListener接口的onAlarm()方法,从而在onAlarm()中完成接下来的工作,该接口如下:

private final AlarmManager.OnAlarmListener mLightAlarmListener
        = new AlarmManager.OnAlarmListener() {
    @Override
    public void onAlarm() {
        synchronized (DeviceIdleController.this) {
            //每次LightDoze的状态改变,都会调用该方法进行处理
            stepLightIdleStateLocked("s:alarm");
        }
    }
};

因此,当定时器到达时间后,通过onAlarm()方法调用stepLightIdleStateLocked()方法。stepLightIdleStateLocked()方法负责LightDoze模式的改变,在这个方法中根据不同的状态发送不同的Alarm定时器来改变状态。可以这样理解,LightDoze模式改变就是通过scheduleLightAlarmLocked()---onAlarm()---stepLightIdleStateLocked()---scheduleLightAlarmLocked()不断的循环来进行的,这样说可能不太明白,还是从代码进行分析,最后对它进行总结,该方法代码如下:

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;
            //如果有活动的操作或JobScheduler、Alarm处于活动状态,
        //再给他们一次机会进行关闭,break返回,
            //否则由于没有break,会进入下条case语句
            if (!isOpsInactiveLocked()) {
                //将状态置为LIGHT_STATE_PRE_IDLE状态
                mLightState = LIGHT_STATE_PRE_IDLE;
                //设置一个定时器,到达时间后用来处理LightDoze处于PRE_IDLE
            //状态的操作
                scheduleLightAlarmLocked(mConstants.LIGHT_PRE_
                  IDLE_TIMEOUT);//10mins
                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,//900000,15mins
                    (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;
    }
}

在这个方法中,处理了LightDoze所有的状态改变时的工作,同时也会在这个方法中进行状态的转换,各个case分析如下:

  • case LIGHT_STATE_INACTIVE:

这部分主要处理由INACTIVEIDLE/PRE_IDLE的过程。
第一次调用该方法时,LightDoze模式处于INACTIVE(不可交互状态)状态,因此会进入第一个case语句,在这个语句中,如果当前还有为处理完的任务,则会先进入IDLE状态之前的一个状态——PRE_IDLE状态,如果没有需要处理的任务,由于这里没有break,因此会继续执行到下一条case语句,将状态由INACTIVE设置为IDLE状态,并开始限制网络访问、Job延迟等处理;PRE_IDLE状态是为了再等待一次未处理完成的工作,这个状态持续5mins,直到下次Alarm时间到再次进入该方法,直接走第二条case语句。因此,在执行第二条语句之前,LightDoze处于PRE_IDLE或者INACTIVE状态。

  • case LIGHT_STATE_PRE_IDLE:
  • case LIGHT_STATE_IDLE_MAINTENANCE:

这部分主要处理由PRE_IDLE/INACTIVEIDLEMAINTENANCEIDLE的过程。在执行这两个case语句之前时,LightDoze处于PRE_IDLE或者INACTIVE状态,此时会变为IDLE状态;或者LightDoze处于MAINTENANCE,此时会变为IDLE状态。当LightDoze状态转为IDLE后,会通过Handler发送广播,通知IDLE状态的改变,开始做优化工作(无法访问网络、Job、同步延迟)。

  • case LIGHT_STATE_IDLE:
  • case LIGHT_STATE_WAITING_FOR_NETWORK:

这部分主要处理由IDLEWAITING_FOR_NETWORKWAITING_FOR_NETWORKMAINTENANCE的过程。当LightDoze状态由IDLE转换为MAINTENANCE后,会通过Handler发送广播,通知IDLE状态的改变,说明进入维护状态,开始做维护工作。
LightDoze的状态转换图如下:
Android 8.1 Doze模式分析(二)——Light Doze模式_第1张图片

下面我们就看当LightDoze状态转换为IDLEMAINTENANCE后,是如何进行功耗的优化和维护工作的。

在上面我们分析了,当由INACTIVE/PRE_IDLE状态进入IDLE状态时,和由IDLE/WAIT_FOR_NETWORK状态时,会通过Handler发送一个LightDoze模式改变的广播,通知接收器进行相应的处理,这里进行详细分析,进入IDLE状态时和进入维护状态时Handler中的相关操作如下:


    @Override public void handleMessage(Message msg) {
        .............................
        switch (msg.what) {
            case MSG_REPORT_IDLE_ON:
            //LightDoze模式进入IDLE状态
            case MSG_REPORT_IDLE_ON_LIGHT: {
                if (msg.what == MSG_REPORT_IDLE_ON) {
                   //DeepDoze相关,这里略去
                   ........
                } else {
                    //通知PMS设置DeepDoze模式不处于IDLE状态
                    deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
                    //通知PMS设置轻度Doze模式处于IDLE状态
                    lightChanged = 
                          mLocalPowerManager.setLightDeviceIdleMode(true);
                }
                try {
                    //通知NetworkPolicyManager LightDoze进入IDLE状态
                    mNetworkPolicyManager.setDeviceIdleMode(true);
                    //通知BatteryStatsService统计LightDoze进入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);
                }
                if (lightChanged) {
                    //发送LightDoze模式改变的广播
                    getContext().sendBroadcastAsUser(mLightIdleIntent,
                            UserHandle.ALL);
                }
                //释放wakelock锁
                mGoingIdleWakeLock.release();
            } break;
            //退出IDLE状态时,包括DeepDoze
            case MSG_REPORT_IDLE_OFF: {
                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;
    }
}

从这部分代码可以知道,在LightDoze进入IDLE状态或退出IDLE状态时时,首先通知PMS、NetworkPolicyManager设置DeviceIdleMode为true或false,然后发送一个Intent为mLightIdleIntent的广播,该mLightIdleIntent是在启动时onBootPhase()中定义:

mLightIdleIntent = new 
        Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
        | Intent.FLAG_RECEIVER_FOREGROUND);

因此,当LightDoze进入IDLE状态时或退出IDLE状态进入MAINTENANCE状态时,都会发送该广播,其他模块如果注册了该广播接受器,则会触发onReceive()方法进行处理。当前版本中注册了mLightIdleIntent的广播接收器的模块只有一处:DeviceIdleJobsController,该类是一个用于根据Doze模式状态控制”Job”的控制类,当Doze(LightDoze和DeepDoze)模式状态为IDLE时,该类中会限制所有除白名单外的应用的”Job”,当退出IDLE状态时,该类中会解除这些应用的”Job”限制,这里所指的”Job”,时Android L后实现的一个JobScheduler机制,允许开应用在符合某些条件时创建执行在后台的任务。接受广播方法如下:

private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        //当DeepDoze或LightDoze的IDLE状态改变时,都会执行updateIdleMode()
        if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE
                _CHANGED.equals(action)|| PowerManager.ACTION_DEVICE
                _IDLE_MODE_CHANGED.equals(action)) {
            updateIdleMode(mPowerManager != null
                    ? (mPowerManager.isDeviceIdleMode()
                            || mPowerManager.isLightDeviceIdleMode())
                    : false);
        } else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_
            CHANGED.equals(action)) {
            updateWhitelist();
        }
    }
};

再来看下DeviceIdleJobsController中updateIdleMode()方法:

void updateIdleMode(boolean enabled) {
    boolean changed = false;
    // Need the whitelist to be ready when going into idle
    if (mDeviceIdleWhitelistAppIds == null) {
        updateWhitelist();//更新白名单列表
    }
    synchronized (mLock) {
        if (mDeviceIdleMode != enabled) {
            changed = true;
        }
        mDeviceIdleMode = enabled;
        //遍历所有的Job
        mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
    }
    if (changed) {
        //回调到JobSchedulerService中,停止或开始Job
        mStateChangedListener.onDeviceIdleStateChanged(enabled);
    }
}

在这个方法中,通过JobSchedulerService回调onDeviceIdleStateChanged()方法对后台Jobs进行限制或运行,至于如何限制,涉及到JobScheduler相关内容,不再往下分析。
再此对上面分析内容进行一次总结:
当LightDoze进入IDLE/MAINTENANCE状态时,在Handler中:

  • 1.通知NetworkPolicyManagerService限制/打开网络;
  • 2.发送广播,DeviceIdleJobsController中进行接受,限制/运行JobService.

如此反复多次,如果设备依旧保持静止且灭屏,则30分钟后,DeepDoze的Alarm时间到,因此会进入DeepDoze模式。LightDoze模式转换图示如下:

Android 8.1 Doze模式分析(二)——Light Doze模式_第2张图片

核心

1.如何限制网络?

DeviceIdleController中仅仅调用NetworkPolicyManagerService提供的接口,具体如何限制在NetworkPolicyManagerService内部实现:

mNetworkPolicyManager.setDeviceIdleMode(true);

2.如何延迟Job?

进入退出LightDoze后发送广播,DeviceIdleJobController中进行了接收,接收后也是内部进行实现:

    // onReceive
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED.equals(action)
                    || PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
                updateIdleMode(mPowerManager != null
                        ? (mPowerManager.isDeviceIdleMode()
                                || mPowerManager.isLightDeviceIdleMode())
                        : false);
            } else if (PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED.equals(action)) {
                updateWhitelist();
            }
        }
    };

你可能感兴趣的:(Android系统开发)