Android 8.1 Doze模式分析

 

概述

Doze模式可以简单概括为:

若判断用户在连续的一段时间内没有使用手机,就延缓终端中APP后台的CPU和网络活动,以达到减少电量消耗的目的。


Doze模式(低电耗模式),是Andoriod6.0增加的一项系统服务,主要目的是为了优化电池性能,增加电池续航时间,Doze模式又分两种模式:深度Doze模式(Deep Doze)和轻度Doze模式(Light Doze),如果用户长时间没有主动使用其设备,处于静止状态且屏幕已关闭,则系统会使设备进入Doze模式,也就是深度Doze模式。如果用户关闭设备屏幕但仍处于移动状态时,则设备进入轻度Doze模式,此外,轻度Doze模式只适合Android7.0及以上版本。

当用户长时间未使用设备时,设备进入Doze模式,Doze模式会延迟应用后台 CPU 和网络活动,从而延长电池续航时间。处于Doze模式的设备会定期进入维护时段,在此期间,应用可以完成待进行的活动。然后,Doze模式会使设备重新进入较长时间的休眠状态,接着进入下一个维护时段。在达到几个小时的休眠时间上限之前,平台会周而复始地重复Doze模式休眠/维护的序列,且每一次都会延长Doze模式时长。处于Doze模式的设备始终可以感知到动作,且会在检测到动作时立即退出Doze模式。整个Doze图示如下: 

上面这张图比较经典,基本上说明了Doze模式的含义。 
 图中的横轴表示时间,红色部分表示终端处于唤醒的运行状态,绿色部分就是Doze模式定义的休眠状态。

从图中的描述,我们可以看到:如果一个用户停止充电(on battery: 利用电池供电),关闭屏幕(screen off),手机处于静止状态(stationary: 位置没有发生相对移动),保持以上条件一段时间之后,终端就会进入Doze模式。一旦进入Doze模式,系统就减少(延缓)应用对网络的访问、以及对CPU的占用,来节省电池电量。

如图所示,Doze模式还定义了maintenance window。 
 在maintenance window中,系统允许应用完成它们被延缓的动作,即可以使用CPU资源及访问网络。 
 从图中我们可以看出,当进入Doze模式的条件一直满足时,Doze模式会定期的进入到maintenance window,但进入的间隔越来越长。 
 通过这种方式,Doze模式可以使终端处于较长时间的休眠状态。

需要注意的是:一旦Doze模式的条件不再满足,即用户充电、或打开屏幕、或终端的位置发生了移动,终端就恢复到正常模式。 
 因此,当用户频繁使用手机时,Doze模式几乎是没有什么实际用处的。

具体来讲,当终端处于Doze模式时,进行了以下操作: 
1、暂停网络访问。 
2、系统忽略所有的WakeLock。 
3、标准的AlarmManager alarms被延缓到下一个maintenance window。 
 但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock时,alarms定义事件仍会启动。
 在这些alarms启动前,系统会短暂地退出Doze模式。 
4、系统不再进行WiFi扫描。 
5、系统不允许sync adapters运行。 
6、系统不允许JobScheduler运行。

Deep Doze 和Light Doze模式对比如下:

DeviceIdleController的启动流程

开机时

SystemServer.java-->startOtherServices()

private void startOtherServices() {
    .............
    mSystemServiceManager.startService(DeviceIdleController.class);
    ............
}

构造方法:

    public DeviceIdleController(Context context) {
        super(context);
        mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
        mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());

        // NOTE: Bug #627645 low power Feature BEG-->
        mPowerControllerHelper = new PowerControllerHelper();
        // <--NOTE: Bug #627645 low power Feature END

        mAbsDeviceIdleController = (AbsDeviceIdleController)PMSFactory.getInstance().createExtraDeviceIdleController(context);
    }

构造方法中,首先创建在/data/sytem下创建了deviceidle.xml文件,然后创建了一个Handler,并将BackgroundThread中Handler的Looper和该Handler进行绑定。BackgroundThread是一个单例模式实现的后台线程,可以用于任何一个进程。


DeviceIdleController.java-->onStart()

@Override
public void onStart() {
    final PackageManager pm = getContext().getPackageManager();

    synchronized (this) {
        //Light doze模式和Deep Doze是否可用,默认不可用
        mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);
        //获取系统全局配置信息,如权限
        SystemConfig sysConfig = SystemConfig.getInstance();
        //从配置文件中读取省电模式下的白名单列表且不在在idle状态的白名单列表,即列表中的app能够在省电模式下在后台运行,
        ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
        for (int i=0; i<allowPowerExceptIdle.size(); i++) {
            String pkg = allowPowerExceptIdle.valueAt(i);
            try {
                //获取白名单列表中的系统应用
                ApplicationInfo ai = pm.getApplicationInfo(pkg,
                        PackageManager.MATCH_SYSTEM_ONLY);
                int appid = UserHandle.getAppId(ai.uid);
                //添加到Map中,表示这些应用在省电模式下可后台运行,但在Doze下后台不可运行
                mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                //添加到SpareArray中,表示这是处于powersave白名单但不处于doze白名单的系统应用                
                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        //从配置文件中读取时能够在省电模式白名单列表,也可以在DOZE模式下
        ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
        for (int i=0; i<allowPower.size(); i++) {
            String pkg = allowPower.valueAt(i);
            try {
                ApplicationInfo ai = pm.getApplicationInfo(pkg,
                        PackageManager.MATCH_SYSTEM_ONLY);
                int appid = UserHandle.getAppId(ai.uid);
                mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
                //添加到DIC中的白名单列表中
                mPowerSaveWhitelistApps.put(ai.packageName, appid);
                //添加到DIC中的系统应用白名单列表中
                mPowerSaveWhitelistSystemAppIds.put(appid, true);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }
        //设置Contants内容观察者
        mConstants = new Constants(mHandler, getContext().getContentResolver());
        //读取/data/system/deviceidle.xml文件,将读取的app添加到表示用户设置的白名单中
        readConfigFileLocked();
        //更新白名单列表
        updateWhitelistAppIdsLocked();
        //网络是否连接
        mNetworkConnected = true;
        //屏幕是否保持常亮
        mScreenOn = true;
        // Start out assuming we are charging.  If we aren't, we will at least get
        // a battery update the next time the level drops.
        //是否充电
        mCharging = true;
        mState = STATE_ACTIVE;//设备保持活动状态,深度Doze的初始值
        mLightState = LIGHT_STATE_ACTIVE;//设备保持活动状态,轻度Doze的初始值
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
    }
    mBinderService = new BinderService();
    //发布远程服务
    publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
    //发布本地服务
    publishLocalService(LocalService.class, new LocalService());
}

在onStart()方法中,首先通过SystemConfig读取了两类白名单列表:在低电量模式下后台允许运行的应用的白名单、在低电量模式和Doze模式下都允许后台运行的应用白名单; 

    public ArraySet<String> getAllowInPowerSaveExceptIdle() {
        return mAllowInPowerSaveExceptIdle;
    }

    public ArraySet<String> getAllowInPowerSave() {
        return mAllowInPowerSave;
    }
             } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
                                + permFile + " at " + parser.getPositionDescription());
                    } else {
                        mAllowInPowerSaveExceptIdle.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("allow-in-power-save".equals(name) && allowAll) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mAllowInPowerSave.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

我们再看下platform.xml文件

    <!-- These are the standard packages that are white-listed to always have internet
         access while in power save mode, even if they aren't in the foreground. -->
    <allow-in-power-save package="com.android.providers.downloads" />
    <allow-in-power-save package="com.sprd.ImsConnectionManager" />
    <allow-in-power-save package="com.android.phone" />
    <allow-in-power-save package="com.spreadtrum.vowifi" />

onStart()调用updateWhitelistAppIdsLocked()更新白名单列表

private void updateWhitelistAppIdsLocked() {
    //处于省电模式白名单但不处于Idle状态白名单的app
    mPowerSaveWhitelistExceptIdleAppIdArray = 
            buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
    //处于所有的白名单的app,包括用户添加的
    mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
    //用户添加的白名单列表应用
    mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
    if (mLocalActivityManager != null) {
        //将适用于所有情况的白名单列表通知给AMS
        mLocalActivityManager.setDeviceIdleWhitelist
               (mPowerSaveWhitelistAllAppIdArray);
    }
    if (mLocalPowerManager != null) {
        //将适用于所有情况的白名单列表通知给PMS
        mLocalPowerManager.setDeviceIdleWhitelist(
                   mPowerSaveWhitelistAllAppIdArray);
    }
    if (mLocalAlarmManager != null) {
        //将用户添加到白名单列表中的应用通知给AlarmManagerService
       mLocalAlarmManager.setDeviceIdleUserWhitelist
                    (mPowerSaveWhitelistUserAppIdArray);
    }
}

执行完onStart()方法后,就开始执行最后一个生命周期方法onBootPhase()

@Override
public void onBootPhase(int phase) {
    if (phase == PHASE_SYSTEM_SERVICES_READY) {
        synchronized (this) {
            mAlarmManager = (AlarmManager) getContext().getSystemService
                    (Context.ALARM_SERVICE);
            mBatteryStats = BatteryStatsService.getService();
            mLocalActivityManager = getLocalService(ActivityManagerInternal.class);
            mLocalPowerManager = getLocalService(PowerManagerInternal.class);
            mPowerManager = getContext().getSystemService(PowerManager.class);
            //申请一个wakelock锁保持CPU唤醒
            mActiveIdleWakeLock = mPowerManager.newWakeLock
                     (PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_maint");
            //设置wakelock锁为非计数锁,只要执行一次release()就能释所有非计数锁
            mActiveIdleWakeLock.setReferenceCounted(false);
            mGoingIdleWakeLock = mPowerManager.newWakeLock
                   (PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_going_idle");
            //设置wakelock锁为计数锁,一次申请对应一次释放
            mGoingIdleWakeLock.setReferenceCounted(true);
            mConnectivityService = (ConnectivityService)ServiceManager.getService(
                    Context.CONNECTIVITY_SERVICE);
            mLocalAlarmManager = getLocalService(AlarmManagerService.
                    LocalService.class);
            mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                    ServiceManager.getService
                    (Context.NETWORK_POLICY_SERVICE));
            mSensorManager = (SensorManager)getContext().getSystemService
                   (Context.SENSOR_SERVICE);
            //可用于自动省电模式时的传感器id,0表示没有可用传感器
            int sigMotionSensorId = getContext().getResources().getInteger(
                    com.android.internal.R.integer.config_
                    autoPowerModeAnyMotionSensor);
            if (sigMotionSensorId > 0) {
                //根据传感器id获取传感器
                mMotionSensor = mSensorManager.getDefaultSensor
                     (sigMotionSensorId, true);
            }
            //如果没有指定任一传感器&&在没有指定传感器情况下首选WristTilt
            //传感器配置为true
            if (mMotionSensor == null && getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_
                    autoPowerModePreferWristTilt)) {
                //获取一个WristTilt传感器(手腕抖动)
                mMotionSensor = mSensorManager.getDefaultSensor(
                        Sensor.TYPE_WRIST_TILT_GESTURE, true);
            }
            if (mMotionSensor == null) {
                //如果以上条件都不满足,则获取一个SMD传感器
                mMotionSensor = mSensorManager.getDefaultSensor(
                        Sensor.TYPE_SIGNIFICANT_MOTION, true);
            }
            //是否在进入Doze模式时预先获取位置
            if (getContext().getResources().getBoolean(
                    com.android.internal.R.bool
                    .config_autoPowerModePrefetchLocation)) {
                mLocationManager = (LocationManager) getContext().
                        getSystemService(Context.LOCATION_SERVICE);
                mLocationRequest = new LocationRequest()
                    .setQuality(LocationRequest.ACCURACY_FINE)
                    .setInterval(0)
                    .setFastestInterval(0)
                    .setNumUpdates(1);
            }
            //自动省电模式下传感器检测的阈值度
            float angleThreshold = getContext().getResources().getInteger(
                    com.android.internal.R.integer.config_
                    autoPowerModeThresholdAngle) / 100f;
            //用于检测设备是否已静止
            mAnyMotionDetector = new AnyMotionDetector(
                    (PowerManager) getContext().getSystemService
                    (Context.POWER_SERVICE),
                    mHandler, mSensorManager, this, angleThreshold);
            //用于Doze状态发生改变时发送广播
            mIdleIntent = new Intent(PowerManager.ACTION_DEVICE
                      _IDLE_MODE_CHANGED);
            mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            //用于当轻度Doze状态发生改变时发送广播
            mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_
                    DEVICE_IDLE_MODE_CHANGED);
            mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            //注册监听电池状态改变的广播
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
            getContext().registerReceiver(mReceiver, filter);
            //注册监听卸载应用的广播
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addDataScheme("package");
            getContext().registerReceiver(mReceiver, filter);
            //注册监听网络连接改变的广播
            filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            getContext().registerReceiver(mReceiver, filter);
            //注册监听亮灭屏的广播
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_SCREEN_OFF);
            filter.addAction(Intent.ACTION_SCREEN_ON);
            getContext().registerReceiver(mInteractivityReceiver, filter);
            //将适用于所有情况的白名单列表通知给AMS、PMS、AlarmManagerService
            mLocalActivityManager.setDeviceIdleWhitelist(
                    mPowerSaveWhitelistAllAppIdArray);
            mLocalPowerManager.setDeviceIdleWhitelist(
                    mPowerSaveWhitelistAllAppIdArray);
            mLocalAlarmManager.setDeviceIdleUserWhitelist(
                     mPowerSaveWhitelistUserAppIdArray)
            //更新交互状态
            updateInteractivityLocked();
        }
        //更新网络连接状态
        updateConnectivityState(null);
    } else if (phase == PHASE_BOOT_COMPLETED) {
    }
}

updateInteractivityLocked()用于更新交互状态

void updateInteractivityLocked() {
    // The interactivity state from the power manager tells us whether the display is
    // in a state that we need to keep things running so they will update at a normal
    // frequency.
    //获取设备是否处于交互状态
    boolean screenOn = mPowerManager.isInteractive();
    if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);
    //表示当前不处于交互状态且上次处于交互状态
    if (!screenOn && mScreenOn) {
        mScreenOn = false;
        if (!mForceIdle) {//是否强制进入Idle
            //进入Idle模式的入口方法
            becomeInactiveIfAppropriateLocked();
        }
    } else if (screenOn) {
        mScreenOn = true;
        if (!mForceIdle) {
            //退出Idle模式
            becomeActiveLocked("screen", Process.myUid());
        }
    }
}

updateConnectivityState()用于更新网络状态

void updateInteractivityLocked() {
    // The interactivity state from the power manager tells us whether the display is
    // in a state that we need to keep things running so they will update at a normal
    // frequency.
    //获取设备是否处于交互状态
    boolean screenOn = mPowerManager.isInteractive();
    if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);
    //表示当前不处于交互状态且上次处于交互状态
    if (!screenOn && mScreenOn) {
        mScreenOn = false;
        if (!mForceIdle) {//是否强制进入Idle
            //进入Idle模式的入口方法
            becomeInactiveIfAppropriateLocked();
        }
    } else if (screenOn) {
        mScreenOn = true;
        if (!mForceIdle) {
            //退出Idle模式
            becomeActiveLocked("screen", Process.myUid());
        }
    }
}

DeviceIdleController的状态变化

对于充电状态,在onBootPhase函数中已经提到,DeviceIdleController监听了ACTION_BATTERY_CHANGED广播:

............  
IntentFilter filter = new IntentFilter();  
filter.addAction(Intent.ACTION_BATTERY_CHANGED);  
getContext().registerReceiver(mReceiver, filter);  
...........  
                case Intent.ACTION_BATTERY_CHANGED: {
                    synchronized (DeviceIdleController.this) {
                        int plugged = intent.getIntExtra("plugged", 0);
                        updateChargingLocked(plugged != 0);
                    }
                } break;

DeviceIdleController.java-->updateChargingLocked()

void updateChargingLocked(boolean charging) {  
    .........  
    if (!charging && mCharging) {  
        //从充电状态变为不充电状态  
        mCharging = false;  
        //mForceIdle值一般为false,是通过dumpsys命令将mForceIdle改成true的  
        if (!mForceIdle) {  
            //判断是否进入Doze模式  
            becomeInactiveIfAppropriateLocked();  
        }  
    } else if (charging) {  
        //进入充电状态  
        mCharging = charging;  
        if (!mForceIdle) {  
            //手机退出Doze模式  
            becomeActiveLocked("charging", Process.myUid());  
        }  
    }  
}  

我们先看下becomeInactiveIfAppropriateLocked()开始进入Doze模式

void becomeInactiveIfAppropriateLocked() {  
    .................  
    //屏幕熄灭,未充电  
    if ((!mScreenOn && !mCharging) || mForceIdle) {  
        // Screen has turned off; we are now going to become inactive and start  
        // waiting to see if we will ultimately go idle.  
        if (mState == STATE_ACTIVE && mDeepEnabled) {  
            mState = STATE_INACTIVE;  
            ...............  
            //重置事件  
            resetIdleManagementLocked();  
  
            //开始检测是否可以进入Doze模式的Idle状态  
            //若终端没有watch feature, mInactiveTimeout时间为30min  
            scheduleAlarmLocked(mInactiveTimeout, false);  
            ...............  
        }  
  
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {  
            mLightState = LIGHT_STATE_INACTIVE;  
            .............  
            resetLightIdleManagementLocked();//重置事件  
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);  
        }  
    }  
}  

要进入Doze流程,就是调用这个函数,首先要保证屏幕灭屏然后没有充电。这里还有mDeepEnable和mLightEnable前面说过是在配置中定义的,一般默认是关闭(也就是不开Doze模式)。这里mLightEnabled是对应禁止wakelock持锁的,禁止网络。而mDeepEnabled对应是检测设备是否静止,除了禁止wakelock、禁止网络、还会机制alarm。

ight idle 和deep idle根据不同条件进入


void becomeInactiveIfAppropriateLocked() {
        if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
        if ((!mScreenOn && !mCharging) || mForceIdle) {
            // Screen has turned off; we are now going to become inactive and start
            // waiting to see if we will ultimately go idle.
            if (mState == STATE_ACTIVE && mDeepEnabled) {
                mState = STATE_INACTIVE;
                if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                resetIdleManagementLocked();
                scheduleAlarmLocked(mInactiveTimeout, false);
                EventLogTags.writeDeviceIdle(mState, "no activity");
            }
            if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
                mLightState = LIGHT_STATE_INACTIVE;
                if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
                resetLightIdleManagementLocked();
                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
                EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
            }
        }

light idle模式

light idle模式下会禁止网络、wakelock,但是不会禁止alarm

我们先看scheduleLightAlarmLocked函数,这里设置了一个alarm,delay是5分钟。到时间后调用mLightAlarmListener回调。

void scheduleLightAlarmLocked(long delay) {  
    if (DEBUG) Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + ")");  
    mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;  
    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,  
            mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);  
}  

mLightAlarmListener就是进入lightidle,调用stepLightIdleStateLocked函数

    private final AlarmManager.OnAlarmListener mLightAlarmListener
            = new AlarmManager.OnAlarmListener() {
        @Override
        public void onAlarm() {
            synchronized (DeviceIdleController.this) {
                stepLightIdleStateLocked("s:alarm");
            }
        }
    };

我们来看stepLightIdleStateLocked函数,这个函数会处理mLightState不同状态,会根据不同状态,然后设置alarm,到时间后继续处理下个状态。到LIGHT_STATE_IDLE_MAINTENANCE状态处理时,会发送MSG_REPORT_IDLE_ON_LIGHT。这个消息的处理会禁止网络、禁止wakelock。然后到LIGHT_STATE_WAITING_FOR_NETWORK,会先退出Doze状态(这个时候网络、wakelock恢复)。然后设置alarm,alarm时间到后,还是在LIGHT_STATE_IDLE_MAINTENANCE状态。和之前一样(禁止网络、wakelock)。只是设置的alarm间隔会越来越大,也就是只要屏幕灭屏后,时间越长。设备会隔越来越长的时间才会退出Doze状态,这也符合一个实际情况,但是会有一个上限值。

 void stepLightIdleStateLocked(String reason) {
        if (mLightState == LIGHT_STATE_OVERRIDE) {
            // If we are already in deep device idle mode, then
            // there is nothing left to do for light mode.
            return;
        }

        if (DEBUG) Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + mLightState);
        EventLogTags.writeDeviceIdleLightStep();

        switch (mLightState) {
            case LIGHT_STATE_INACTIVE:
                mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
                // Reset the upcoming idle delays.
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
                mMaintenanceStartTime = 0;
                if (!isOpsInactiveLocked()) {
                    // We have some active ops going on...  give them a chance to finish
                    // before going in to our first idle.
                    mLightState = LIGHT_STATE_PRE_IDLE;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                    scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);
                    break;
                }
                // Nothing active, fall through to immediately idle.
            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) {
                        // We didn't use up all of our minimum budget; add this to the reserve.
                        mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
                    } else {
                        // We used more than our minimum budget; this comes out of the reserve.
                        mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
                    }
                }
                mMaintenanceStartTime = 0;
                scheduleLightAlarmLocked(mNextLightIdleDelay);
                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;
                }
                if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
                mLightState = LIGHT_STATE_IDLE;
                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                addEvent(EVENT_LIGHT_IDLE);
                mGoingIdleWakeLock.acquire();
//发送消息,这个消息处理就会关闭网络,禁止wakelock  
                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) {
                    // We have been idling long enough, now it is time to do some work.
                    mActiveIdleOpCount = 1;
                    mActiveIdleWakeLock.acquire();
                    mMaintenanceStartTime = SystemClock.elapsedRealtime();
                    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;
                    }
                    scheduleLightAlarmLocked(mCurIdleBudget);
                    if (DEBUG) Slog.d(TAG,
                            "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                    mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                    addEvent(EVENT_LIGHT_MAINTENANCE);
                    //醒一下(开启网络、恢复wakelock)
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                } else {
                    // We'd like to do maintenance, but currently don't have network
                    // connectivity...  let's try to wait until the network comes back.
                    // We'll only wait for another full idle period, however, and then give up.
                    scheduleLightAlarmLocked(mNextLightIdleDelay);
                    if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
                    mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                }
                break;
        }
    }

deep idle模式

下面我们再来看deep idle模式,这个模式除了禁止网络、wakelock还会禁止alarm。

我们再来看becomeInactiveIfAppropriateLocked函数中下面代码。是关于deep idle的设置 这里的mInactiveTimeout是半小时

void becomeInactiveIfAppropriateLocked() {  
    if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");  
    if ((!mScreenOn && !mCharging) || mForceIdle) {  
        // Screen has turned off; we are now going to become inactive and start  
        // waiting to see if we will ultimately go idle.  
        if (mState == STATE_ACTIVE && mDeepEnabled) {  
            mState = STATE_INACTIVE;  
            if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");  
            resetIdleManagementLocked();  
            scheduleAlarmLocked(mInactiveTimeout, false);  
            EventLogTags.writeDeviceIdle(mState, "no activity");  
        }  

scheduleAlarmLocked(),注意如果这里参数idleUntil是true会调用AlarmManager的setIdleUntil函数,调用这个函数后普通应用设置alarm将失效。

void scheduleAlarmLocked(long delay, boolean idleUntil) {  
    if (mMotionSensor == null) {  
        //在onBootPhase时,获取过位置检测传感器  
        //如果终端没有配置位置检测传感器,那么终端永远不会进入到真正的Doze ilde状态  
        // If there is no motion sensor on this device, then we won't schedule  
        // alarms, because we can't determine if the device is not moving.  
        return;  
    }  
  
    mNextAlarmTime = SystemClock.elapsedRealtime() + delay;  
    if (idleUntil) {  
        //此时IdleUtil的值为false  
        mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,  
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);  
    } else {  
        //30min后唤醒,调用mDeepAlarmListener的onAlarm函数  
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,  
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);  
    }  
}  

需要注意的是,DeviceIdleController一直在监控屏幕状态和充电状态,一但不满足Doze模式的条件,前面提到的becomeActiveLocked函数就会被调用。mAlarmManager设置的定时唤醒事件将被取消掉,mDeepAlarmListener的onAlarm函数不会被调用。

因此,我们知道了终端必须保持Doze模式的入口条件长达30min,才会进入mDeepAlarmListener.onAlarm:

private final AlarmManager.OnAlarmListener mDeepAlarmListener  
        = new AlarmManager.OnAlarmListener() {  
    @Override  
    public void onAlarm() {  
        synchronized (DeviceIdleController.this) {  
            //进入到stepIdleStateLocked函数  
            stepIdleStateLocked("s:alarm");  
        }  
    }  
};  

下面我们就来看下stepIdleStateLocked方法:

void stepIdleStateLocked(String reason) {  
    ..........  
    final long now = SystemClock.elapsedRealtime();  
    //个人觉得,下面这段代码,是针对Idle状态设计的  
    //如果在Idle状态收到Alarm,那么将先唤醒终端,然后重新判断是否需要进入Idle态  
    //在介绍Doze模式原理时提到过,若应用调用AlarmManager的一些指定接口,仍然可以在Idle状态进行工作  
    if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {  
        // Whoops, there is an upcoming alarm.  We don't actually want to go idle.  
        if (mState != STATE_ACTIVE) {  
            becomeActiveLocked("alarm", Process.myUid());  
            becomeInactiveIfAppropriateLocked();  
        }  
        return;  
    }  
  
    //以下是Doze模式的状态转变相关的代码  
    switch (mState) {  
        case STATE_INACTIVE:  
            // We have now been inactive long enough, it is time to start looking  
            // for motion and sleep some more while doing so.  
            //保持屏幕熄灭,同时未充电达到30min,进入此分支  
  
            //注册一个mMotionListener,检测是否移动  
            //如果检测到移动,将重新进入到ACTIVE状态  
            //相应代码比较直观,此处不再深入分析  
            startMonitoringMotionLocked();  
  
            //再次调用scheduleAlarmLocked函数,此次的时间仍为30min  
            //也就说如果不发生退出Doze模式的事件,30min后将再次进入到stepIdleStateLocked函数  
            //不过届时的mState已经变为STATE_IDLE_PENDING  
            scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);  
  
            // Reset the upcoming idle delays.  
            //mNextIdlePendingDelay为5min  
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;  
            //mNextIdleDelay为60min  
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;  
  
            //状态变为STATE_IDLE_PENDING   
            mState = STATE_IDLE_PENDING;  
            ............  
            break;  
        case STATE_IDLE_PENDING:  
            //保持息屏、未充电、静止状态,经过30min后,进入此分支  
            mState = STATE_SENSING;  
  
            //保持Doze模式条件,4min后再次进入stepIdleStateLocked  
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);  
  
            //停止定位相关的工作  
            cancelLocatingLocked();  
            mNotMoving = false;  
            mLocated = false;  
            mLastGenericLocation = null;  
            mLastGpsLocation = null;  
  
            //开始检测手机是否发生运动(这里应该是更细致的侧重于角度的变化)  
            //若手机运动过,则重新变为active状态  
            mAnyMotionDetector.checkForAnyMotion();  
            break;  
        case STATE_SENSING:  
            //上面的条件满足后,进入此分支,开始获取定位信息  
            cancelSensingTimeoutAlarmLocked();  
            mState = STATE_LOCATING;  
            ............  
            //保持条件30s,再次调用stepIdleStateLocked  
            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;  
            }  
  
            // If we have a location provider, we're all set, the listeners will move state  
            // forward.  
            if (mLocating) {  
                //无法定位则直接进入下一个case  
                break;  
            }  
        case STATE_LOCATING:  
            //停止定位和运动检测,直接进入到STATE_IDLE_MAINTENANCE  
            cancelAlarmLocked();  
            cancelLocatingLocked();  
            mAnyMotionDetector.stop();  
  
        case STATE_IDLE_MAINTENANCE:  
            //进入到这个case后,终端开始进入Idle状态,也就是真正的Doze模式  
  
            //定义退出Idle的时间此时为60min  
            scheduleAlarmLocked(mNextIdleDelay, true);  
            ............  
            //退出周期逐步递增,每次乘2  
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);  
            ...........  
            //周期有最大值6h  
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);  
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {  
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;  
            }  
  
            mState = STATE_IDLE;  
            ...........  
            //通知PMS、NetworkPolicyManagerService等Doze模式开启,即进入Idle状态  
            //此时PMS disable一些非白名单WakeLock;NetworkPolicyManagerService开始限制一些应用的网络访问  
            //消息处理的具体流程比较直观,此处不再深入分析  
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);  
            break;  
  
        case STATE_IDLE:  
            //进入到这个case时,本次的Idle状态暂时结束,开启maintenance window  
  
            // We have been idling long enough, now it is time to do some work.  
            mActiveIdleOpCount = 1;  
            mActiveIdleWakeLock.acquire();  
  
            //定义重新进入Idle的时间为5min (也就是手机可处于Maintenance window的时间)  
            scheduleAlarmLocked(mNextIdlePendingDelay, false);  
  
            mMaintenanceStartTime = SystemClock.elapsedRealtime();  
            //调整mNextIdlePendingDelay,乘2(最大为10min)  
            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;  
            ...........  
            //通知PMS等暂时退出了Idle状态,可以进行一些工作  
            //此时PMS enable一些非白名单WakeLock;NetworkPolicyManagerService开始允许应用的网络访问  
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);  
            break;  
    }  
}  

上面的流程在注释里面已经很明白了,而我们在进入Deep idle时,发送了一个MSG_REPORT_IDLE_ON消息,我们看下面这个消息的处理和之前的MSG_REPORT_IDLE_ON_LIGHT一样的,关闭网络,禁止wakelock。

case MSG_REPORT_IDLE_ON:  
case MSG_REPORT_IDLE_ON_LIGHT: {  
    EventLogTags.writeDeviceIdleOnStart();  
    final boolean deepChanged;  
    final boolean lightChanged;  
    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 {  
        mNetworkPolicyManager.setDeviceIdleMode(true);  
        mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON  
                ? BatteryStats.DEVICE_IDLE_MODE_DEEP  
                : BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());  
    } catch (RemoteException e) {  
    }  
    if (deepChanged) {  
        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);  
    }  
    if (lightChanged) {  
        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);  
    }  
    EventLogTags.writeDeviceIdleOnComplete();  
} break;  

而禁止alarm是通过调用如下函数,注意参数是true。参数是true会调用mAlarmManager.setIdleUntil函数。这样其他的alarm会被滞后(除非在白名单中)

  1. scheduleAlarmLocked(mNextIdleDelay, true);  

而每隔一段时间会进入Maintenance window的时间,此时是通过发送MSG_REPORT_IDLE_OFF消息,来恢复网络和wakelock。而这个时候之前设置的mAlarmManager.setIdleUntil的alarm也到期了,因此其他alarm也恢复了。但是这个时间只有5分钟,重新设置了alarm再次进入deep idle状态。

猜你喜欢

转载自blog.csdn.net/liu362732346/article/details/85290519