Android7.0 Doze模式
来源:互联网 发布:手机录入软件 编辑:程序博客网 时间:2024/06/02 02:31
在Android M中,Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。
在该状态,后台只有部分任务被允许运行,其它任务都被强制停止。
本篇博客中,我们就来分析一下Android 7.0中Doze模式相关的流程。
一、基本原理
Doze模式可以简单概括为:
若判断用户在连续的一段时间内没有使用手机,就延缓终端中APP后台的CPU和网络活动,以达到减少电量消耗的目的。
上面这张图比较经典,基本上说明了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运行。
更多基本信息的描述可以参考:
What is Doze mode in android 6.0 Marshmallow?
二、DeviceIdleController的初始化
Android中的Doze模式主要由DeviceIdleController来控制。
public class DeviceIdleController extends SystemService implements AnyMotionDetector.DeviceIdleCallback { ....................}
可以看出DeviceIdleController继承自SystemService,是一个系统级的服务。
同时,继承了AnyMotionDetector定义的接口,便于检测到终端位置变化后进行回调。
接下来我们看看它的初始化过程。
private void startOtherServices() { ......... mSystemServiceManager.startService(DeviceIdleController.class); .........}
如上代码所示,SystemServer在startOtherServices中启动了DeviceIdleController,将先后调用DeviceIdleController的构造函数和onStart函数。
1、构造函数
public DeviceIdleController(Context context) { super(context); //deviceidle.xml用于定义idle模式也能正常工作的非系统应用 //一般终端似乎并没有定义deviceidle.xml mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml")); mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());}
DeviceIdleController的构造函数比较简单,就是在创建data/system/deviceidle.xml对应的file文件,同时创建一个对应于后台线程的handler。
2、onStart
public void onStart() { final PackageManager pm = getContext().getPackageManager(); synchronized (this) { //读取配置文件,判断Doze模式是否允许被开启 mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean( com.android.internal.R.bool.config_enableAutoPowerModes); //分析PKMS时提到过,PKMS扫描系统目录的xml,将形成SystemConfig SystemConfig sysConfig = SystemConfig.getInstance(); //获取除了device Idle模式外,都可以运行的系统应用白名单 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); mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid); mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true); } catch (PackageManager.NameNotFoundException e) { } } //获取device Idle模式下,也可以运行的系统应用白名单 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); // These apps are on both the whitelist-except-idle as well // as the full whitelist, so they apply in all cases. mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid); mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true); mPowerSaveWhitelistApps.put(ai.packageName, appid); mPowerSaveWhitelistSystemAppIds.put(appid, true); } catch (PackageManager.NameNotFoundException e) { } } //Constants为deviceIdleController中的内部类,继承ContentObserver //监控数据库变化,同时得到Doze模式定义的一些时间间隔 mConstants = new Constants(mHandler, getContext().getContentResolver()); //解析deviceidle.xml,并将其中定义的package对应的app,加入到mPowerSaveWhitelistUserApps中 readConfigFileLocked(); //将白名单的内容给AlarmManagerService和PowerMangerService //例如:DeviceIdleController判断开启Doze模式时,会通知PMS //此时除去白名单对应的应用外,PMS会将其它所有的WakeLock设置为Disable状态 updateWhitelistAppIdsLocked(); //以下的初始化,都是假设目前处在进入Doze模式相反的条件上 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; //Doze模式定义终端初始时为ACTIVE状态 mState = STATE_ACTIVE; //屏幕状态初始时为ACTIVE状态 mLightState = LIGHT_STATE_ACTIVE; mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; } //发布服务 //BinderService和LocalService均为DeviceIdleController的内部类 mBinderService = new BinderService(); publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService); publishLocalService(LocalService.class, new LocalService());}
除去发布服务外,DeviceIdleController在onStart函数中,主要是读取配置文件更新自己的变量,思路比较清晰。
在这里我们仅跟进一下updateWhitelistAppIdsLocked函数:
private void updateWhitelistAppIdsLocked() { //构造出除去idle模式外,可运行的app id数组 (可认为是系统和普通应用的集合) //mPowerSaveWhitelistAppsExceptIdle从系统目录下的xml得到 //mPowerSaveWhitelistUserApps从deviceidle.xml得到,或调用接口加入; //mPowerSaveWhitelistExceptIdleAppIds并未使用 mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds); //构造不受Doze限制的app id数组 (可认为是系统和普通应用的集合) //mPowerSaveWhitelistApps从系统目录下的xml得到 //mPowerSaveWhitelistAllAppIds并未使用 mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds); //构造不受Doze限制的app id数组(仅普通应用的集合)、 //mPowerSaveWhitelistUserAppIds并未使用 mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null, mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds); if (mLocalPowerManager != null) { ........... //PMS拿到的是:系统和普通应用组成的不受Doze限制的app id数组 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); } if (mLocalAlarmManager != null) { .......... //AlarmManagerService拿到的是:普通应用组成的不受Doze限制的app id数组 mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); }}
updateWhitelistAppIdsLocked主要是将白名单交给PMS和AlarmManagerService。
注意Android区分了系统应用白名单、普通应用白名单等,因此上面进行了一些合并操作。
3、onBootPhase
与PowerManagerService一样,DeviceIdleController在初始化的最后一个阶段需要调用onBootPhase函数:
public void onBootPhase(int phase) { //在系统PHASE_SYSTEM_SERVICES_READY阶段,进一步完成一些初始化 if (phase == PHASE_SYSTEM_SERVICES_READY) { synchronized (this) { //初始化一些变量 mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); .............. mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); //根据配置文件,利用SensorManager获取对应的传感器,保存到mMotionSensor中 .............. //如果配置文件表明:终端需要预获取位置信息 //则构造LocationRequest 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; //创建一个AnyMotionDetector,同时将DeviceIdleController注册到其中 //当AnyMotionDetector检测到手机变化角度超过门限时,就会回调DeviceIdleController的接口 mAnyMotionDetector = new AnyMotionDetector( (PowerManager) getContext().getSystemService(Context.POWER_SERVICE), mHandler, mSensorManager, this, angleThreshold); //创建两个常用的Intent,用于通知Doze模式的变化 mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED); mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); //监听ACTION_BATTERY_CHANGED广播(电池信息发生改变) IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); getContext().registerReceiver(mReceiver, filter); //监听ACTION_PACKAGE_REMOVED广播(包被移除) filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); getContext().registerReceiver(mReceiver, filter); //监听CONNECTIVITY_ACTION广播(连接状态发生改变) filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); getContext().registerReceiver(mReceiver, filter); //重新将白名单信息交给PowerManagerService和AlarmManagerService //这个工作在onStart函数中,已经调用updateWhitelistAppIdsLocked进行过了 //到onBootPhase时,重新进行一次,可能:一是为了保险;二是,其它进程可能调用接口,更改了对应数据,于是进行更新 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); //监听屏幕显示相关的变化 mDisplayManager.registerDisplayListener(mDisplayListener, null); //更新屏幕显示相关的信息 updateDisplayLocked(); } //更新连接状态相关的信息 updateConnectivityState(null); } }
从代码可以看出,onBootPhase方法:
主要创建一些本地变量,然后根据配置文件初始化一些传感器,同时注册了一些广播接收器和回到接口,
最后更新屏幕显示和连接状态相关的信息。
三、DeviceIdleController定义的状态变化
根据前面提到的Doze模式的原理,我们知道手机进入Doze模式的条件是:未充电、手机位置不发生变化、屏幕熄灭。
因此,在DeviceIdleController中监听了这三个条件对应的状态,以决定终端是真正否进入到Doze模式。
对这三个条件的分析,最终都会进入到DeviceIdleController定义的状态变化流程。
因此我们就以充电状态的变化为例,看看DeviceIdleController进行了哪些处理。其余条件的分析,基本类似。
1、充电状态的处理
对于充电状态,在onBootPhase函数中已经提到,DeviceIdleController监听了ACTION_BATTERY_CHANGED广播:
............IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_BATTERY_CHANGED);getContext().registerReceiver(mReceiver, filter);...........
我们看看receiver中对应的处理:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { ......... case Intent.ACTION_BATTERY_CHANGED: { synchronized (DeviceIdleController.this) { //从广播中得到是否在充电的消息 int plugged = intent.getIntExtra("plugged", 0); updateChargingLocked(plugged != 0); } } break; } }};
根据上面的代码,可以看出当收到电池信息改变的广播后,DeviceIdleController将得到电源是否在充电的消息,然后调用updateChargingLocked函数进行处理。
void updateChargingLocked(boolean charging) { ......... if (!charging && mCharging) { //从充电状态变为不充电状态 mCharging = false; //mForceIdle值一般为false if (!mForceIdle) { //判断是否进入Doze模式 becomeInactiveIfAppropriateLocked(); } } else if (charging) { //进入充电状态 mCharging = charging; if (!mForceIdle) { //手机退出Doze模式 becomeActiveLocked("charging", Process.myUid()); } }}
2、becomeActiveLocked
我们先看看becomeActiveLocked函数:
//activeReason记录的终端变为active的原因void becomeActiveLocked(String activeReason, int activeUid) { ........... if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) { ............ //1、通知PMS等Doze模式结束 scheduleReportActiveLocked(activeReason, activeUid); //更新DeviceIdleController本地维护的状态 //在DeviceIdleController的onStart函数中,我们已经知道了 //初始时,mState和mLightState均为Active状态 mState = STATE_ACTIVE; mLightState = LIGHT_STATE_ACTIVE; mInactiveTimeout = mConstants.INACTIVE_TIMEOUT; mCurIdleBudget = 0; mMaintenanceStartTime = 0; //2、重置一些事件 resetIdleManagementLocked(); resetLightIdleManagementLocked(); addEvent(EVENT_NORMAL); }}
2.1 scheduleReportActiveLocked
void scheduleReportActiveLocked(String activeReason, int activeUid) { //发送MSG_REPORT_ACTIVE消息 Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason); mHandler.sendMessage(msg);}
对应的处理流程:
.........case MSG_REPORT_ACTIVE: { ......... //通知PMS Doze模式结束, //于是PMS将一些Doze模式下,disable的WakeLock重新enable //然后调用updatePowerStateLocked函数更新终端的状态 final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false); final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false); try { //通过NetworkPolicyManagerService更改Ip-Rule,不再限制终端应用上网 mNetworkPolicyManager.setDeviceIdleMode(false); //BSS做好对应的记录 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); }}........
从上面的代码可以看出,scheduleReportActiveLocked函数最主要的工作是:
通知PMS等重新更新终端的状态;
通知NetworkPolicyManagerService不再限制应用上网。
发送Doze模式改变的广播。
2.2 resetIdleManagementLocked
void resetIdleManagementLocked() { //复位一些状态变量 mNextIdlePendingDelay = 0; mNextIdleDelay = 0; mNextLightIdleDelay = 0; //停止一些工作,主要是位置检测相关的 cancelAlarmLocked(); cancelSensingTimeoutAlarmLocked(); cancelLocatingLocked(); stopMonitoringMotionLocked(); mAnyMotionDetector.stop();}
从上面的代码可以看出,resetIdleManagementLocked的工作相对简单,就是停止进入Doze模式时启动的一些任务。
3、becomeInactiveIfAppropriateLocked
与becomeActiveLocked函数相比,becomeInactiveIfAppropriateLocked函数较为复杂。
因为调用becomeInactiveIfAppropriateLocked函数时,终端可能只是满足进入Doze模式的条件,离进入真正的Doze模式还有很长的“一段路”需要走。
我们看看becomeInactiveIfAppropriateLocked的代码:
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); } }}
从上面的代码可以看出,在DeviceIdleState中,用mState和mLightState来衡量终端是否真的进入了Doze模式。
我们目前仅关注mState变量的改变情况,mLightState的变化流程可类似分析。
此时,mState的状态为INACTIVE。
3.1 scheduleAlarmLocked
我们跟进一下scheduleAlarmLocked函数:
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函数。
需要注意的是stepIdleStateLocked将决定DeviceIdleController状态之间的转移。
这种通过AlarmManager设定唤醒时间,然后通过回调接口来调用stepIdleStateLocked的方式,将被多次使用。
3.2 stepIdleStateLocked
此处没有什么多说的,直接来看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; }}
至此,stepIdleStateLocked的流程介绍完毕。
我们知道了,在DeviceIdleController中,为终端定义了7中状态,如下图所示:
手机被操作的时候为Active状态。
当手机关闭屏幕或者拔掉电源的时候,手机开始判断是否进入Doze模式。
经过一系列的状态后,最终会进入到IDLE状态,此时才算进入到真正的Doze模式,系统进入到了深度休眠状态。
此时,系统中非白名单的应用将被禁止访问网络,它们申请的Wakelock也会被disable。
从上面的代码可以看出,系统会周期性的退出Idle状态,进入到MAINTENANCE状态,集中处理相关的任务。
一段时间后,会重新再次回到IDLE状态。每次进入IDLE状态,停留的时间都会是上次的2倍,最大时间限制为6h。
当手机运动,或者点亮屏幕,插上电源等,系统都会重新返回到ACTIVIE状态。
四、总结
本篇博客中,我们分析了Doze模式对应的服务DeviceIdleController。
在了解DeviceIdleController的初始化过程后,我们重点分析了其定义的状态转移过程。
当然这些分析集中在了框架的源码分析上,至于Doze模式对App的影响,建议阅读下面的文章:
Android M新特性Doze and App Standby模式详解
- Android7.0 Doze模式
- Android7.0 Doze模式流程
- Android7.0 Doze模式分析(一)Doze介绍 & DeviceIdleController
- Android7.0 Doze模式分析 Doze介绍 & DeviceIdleController
- Android7.0 Doze模式分析(一)Doze介绍 & DeviceIdleController
- Android7.0 Doze模式分析(二)wakelock
- Android7.0 Doze模式分析(三)alarm
- Android Doze模式分析
- Android Doze模式
- Android 6.0 Doze模式
- Android Doze模式调试
- Android doze模式分析
- android6.0系统 Doze模式(DeviceIdle)实现与控制逻辑
- android6.0系统 Doze模式(DeviceIdle)实现与控制逻辑
- Android 7.0 Doze模式分析
- Android7.0多窗口模式初探
- (原创)android6.0系统 Doze模式(DeviceIdle)实现与控制逻辑
- Android 6.0新特性之Doze模式
- Nexus 登录用户名和密码重置
- UE4 4.13.1 引擎本身bug
- JavaBean
- springMVC 拦截器 HandlerInterceptor 用法
- Linux 简议简单修改配置解决高频率的服务器写操作
- Android7.0 Doze模式
- 15 个 Android 通用流行框架大全梳理
- 1508-张晨曦总结《2016年-10月-30日》【连续9天总结】
- 磁盘连接
- 拨云见日,理解软件发展中的这些概念
- JQuery中Ajax的操作
- Android 小項目之--消息、線程、動畫顯示圖片
- UC/OSII源码阅读知识点(第一章)
- 第8天 静态函数、main方法、单例模式、instanceof