当前位置:   article > 正文

Android7.0 Doze模式分析(一)Doze介绍 & DeviceIdleController

deviceidle



参考:http://blog.csdn.net/gaugamela/article/details/52981984

在Android M中,Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。
 在该状态,后台只有部分任务被允许运行,其它任务都被强制停止。

在之前的博客中分析过Doze模式,就是device idle状态。可能有的地方分析的不是很详细,现在在android7.0上重新分析下。

一、基本原理

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运行。

另外我在另一篇博客中:http://blog.csdn.net/kc58236582/article/details/50554174也详细介绍了Doze模式,可以参考下,上面有一些命令使用等。

 

二、DeviceIdleController

Android中的Doze模式主要由DeviceIdleController来控制。

 

  1. public class DeviceIdleController extends SystemService
  2. implements AnyMotionDetector.DeviceIdleCallback

 

可以看出DeviceIdleController继承自SystemService,是一个系统级的服务。
同时,继承了AnyMotionDetector定义的接口,便于检测到终端位置变化后进行回调。

2.1 DeviceIdleController的初始化

接下来我们看看它的初始化过程。

  1. private void startOtherServices() {
  2. .........
  3. mSystemServiceManager.startService(DeviceIdleController.class);
  4. .........
  5. }

如上代码所示,SystemServer在startOtherServices中启动了DeviceIdleController,将先后调用DeviceIdleController的构造函数和onStart函数。

构造函数

  1. public DeviceIdleController(Context context) {
  2. super(context);
  3. //deviceidle.xml用于定义idle模式也能正常工作的非系统应用
  4. mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
  5. mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
  6. }

DeviceIdleController的构造函数比较简单,就是在创建data/system/deviceidle.xml对应的file文件,同时创建一个对应于后台线程的handler。这里的deviceidle.xml可以在设置中的电池选项那里。有电池优化,可以将一些应用放到白名单中,调用DeviceIdleController的addPowerSaveWhitelistApp方法,最后会写入deviceidle.xml文件,然后在下次开机的时候DeviceIdleController会重新读取deviceidle.xml文件然后放入白名单mPowerSaveWhitelistUserApps中。

onStart函数

  1. public void onStart() {
  2. final PackageManager pm = getContext().getPackageManager();
  3. synchronized (this) {
  4. //读取配置文件,判断Doze模式是否允许被开启
  5. mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
  6. com.android.internal.R.bool.config_enableAutoPowerModes);
  7. //分析PKMS时提到过,PKMS扫描系统目录的xml,将形成SystemConfig
  8. SystemConfig sysConfig = SystemConfig.getInstance();
  9. //获取除了device Idle模式外,都可以运行的系统应用白名单
  10. ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
  11. for (int i=0; i<allowPowerExceptIdle.size(); i++) {
  12. String pkg = allowPowerExceptIdle.valueAt(i);
  13. try {
  14. ApplicationInfo ai = pm.getApplicationInfo(pkg,
  15. PackageManager.MATCH_SYSTEM_ONLY);
  16. int appid = UserHandle.getAppId(ai.uid);
  17. mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
  18. mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
  19. } catch (PackageManager.NameNotFoundException e) {
  20. }
  21. }
  22. //获取device Idle模式下,也可以运行的系统应用白名单
  23. ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
  24. for (int i=0; i<allowPower.size(); i++) {
  25. String pkg = allowPower.valueAt(i);
  26. try {
  27. ApplicationInfo ai = pm.getApplicationInfo(pkg,
  28. PackageManager.MATCH_SYSTEM_ONLY);
  29. int appid = UserHandle.getAppId(ai.uid);
  30. // These apps are on both the whitelist-except-idle as well
  31. // as the full whitelist, so they apply in all cases.
  32. mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
  33. mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
  34. mPowerSaveWhitelistApps.put(ai.packageName, appid);
  35. mPowerSaveWhitelistSystemAppIds.put(appid, true);
  36. } catch (PackageManager.NameNotFoundException e) {
  37. }
  38. }
  39. //Constants为deviceIdleController中的内部类,继承ContentObserver
  40. //监控数据库变化,同时得到Doze模式定义的一些时间间隔
  41. mConstants = new Constants(mHandler, getContext().getContentResolver());
  42. //解析deviceidle.xml,并将其中定义的package对应的app,加入到mPowerSaveWhitelistUserApps中
  43. readConfigFileLocked();
  44. //将白名单的内容给AlarmManagerService和PowerMangerService
  45. //例如:DeviceIdleController判断开启Doze模式时,会通知PMS
  46. //此时除去白名单对应的应用外,PMS会将其它所有的WakeLock设置为Disable状态
  47. updateWhitelistAppIdsLocked();
  48. //以下的初始化,都是假设目前处在进入Doze模式相反的条件上
  49. mNetworkConnected = true;
  50. mScreenOn = true;
  51. // Start out assuming we are charging. If we aren't, we will at least get
  52. // a battery update the next time the level drops.
  53. mCharging = true;
  54. //Doze模式定义终端初始时为ACTIVE状态
  55. mState = STATE_ACTIVE;
  56. //屏幕状态初始时为ACTIVE状态
  57. mLightState = LIGHT_STATE_ACTIVE;
  58. mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
  59. }
  60. //发布服务
  61. //BinderService和LocalService均为DeviceIdleController的内部类
  62. mBinderService = new BinderService();
  63. publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
  64. publishLocalService(LocalService.class, new LocalService());
  65. }

除去发布服务外,DeviceIdleController在onStart函数中,主要是读取配置文件更新自己的变量,思路比较清晰。

在这里我们仅跟进一下updateWhitelistAppIdsLocked函数:

  1. private void updateWhitelistAppIdsLocked() {
  2. //构造出除去idle模式外,可运行的app id数组 (可认为是系统和普通应用的集合)
  3. //mPowerSaveWhitelistAppsExceptIdle从系统目录下的xml得到
  4. //mPowerSaveWhitelistUserApps从deviceidle.xml得到,或调用接口加入;
  5. //mPowerSaveWhitelistExceptIdleAppIds并未使用
  6. mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
  7. mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
  8. //构造不受Doze限制的app id数组 (可认为是系统和普通应用的集合)
  9. //mPowerSaveWhitelistApps从系统目录下的xml得到
  10. //mPowerSaveWhitelistAllAppIds并未使用
  11. mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
  12. mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
  13. //构造不受Doze限制的app id数组(仅普通应用的集合)、
  14. //mPowerSaveWhitelistUserAppIds并未使用
  15. mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
  16. mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
  17. if (mLocalPowerManager != null) {
  18. ...........
  19. //PMS拿到的是:系统和普通应用组成的不受Doze限制的app id数组
  20. mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
  21. }
  22. if (mLocalAlarmManager != null) {
  23. ..........
  24. //AlarmManagerService拿到的是:普通应用组成的不受Doze限制的app id数组
  25. mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
  26. }
  27. }

updateWhitelistAppIdsLocked主要是将白名单交给PMS和AlarmManagerService。
注意Android区分了系统应用白名单、普通应用白名单等,因此上面进行了一些合并操作。这里我们有没有发现,systemConfig的app不会加入alarm的白名单,而在Settings中电池那边设置的白名单,会加入Power wakelock的白名单。

onBootPhase函数

与PowerManagerService一样,DeviceIdleController在初始化的最后一个阶段需要调用onBootPhase函数:

  1. public void onBootPhase(int phase) {
  2. //在系统PHASE_SYSTEM_SERVICES_READY阶段,进一步完成一些初始化
  3. if (phase == PHASE_SYSTEM_SERVICES_READY) {
  4. synchronized (this) {
  5. //初始化一些变量
  6. mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
  7. ..............
  8. mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
  9. //根据配置文件,利用SensorManager获取对应的传感器,保存到mMotionSensor中
  10. ..............
  11. //如果配置文件表明:终端需要预获取位置信息
  12. //则构造LocationRequest
  13. if (getContext().getResources().getBoolean(
  14. com.android.internal.R.bool.config_autoPowerModePrefetchLocation)) {
  15. mLocationManager = (LocationManager) getContext().getSystemService(
  16. Context.LOCATION_SERVICE);
  17. mLocationRequest = new LocationRequest()
  18. .setQuality(LocationRequest.ACCURACY_FINE)
  19. .setInterval(0)
  20. .setFastestInterval(0)
  21. .setNumUpdates(1);
  22. }
  23. //根据配置文件,得到角度变化的门限
  24. float angleThreshold = getContext().getResources().getInteger(
  25. com.android.internal.R.integer.config_autoPowerModeThresholdAngle) / 100f;
  26. //创建一个AnyMotionDetector,同时将DeviceIdleController注册到其中
  27. //当AnyMotionDetector检测到手机变化角度超过门限时,就会回调DeviceIdleController的接口
  28. mAnyMotionDetector = new AnyMotionDetector(
  29. (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
  30. mHandler, mSensorManager, this, angleThreshold);
  31. //创建两个常用的Intent,用于通知Doze模式的变化
  32. mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
  33. mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
  34. | Intent.FLAG_RECEIVER_FOREGROUND);
  35. mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
  36. mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
  37. | Intent.FLAG_RECEIVER_FOREGROUND);
  38. //监听ACTION_BATTERY_CHANGED广播(电池信息发生改变)
  39. IntentFilter filter = new IntentFilter();
  40. filter.addAction(Intent.ACTION_BATTERY_CHANGED);
  41. getContext().registerReceiver(mReceiver, filter);
  42. //监听ACTION_PACKAGE_REMOVED广播(包被移除)
  43. filter = new IntentFilter();
  44. filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
  45. filter.addDataScheme("package");
  46. getContext().registerReceiver(mReceiver, filter);
  47. //监听CONNECTIVITY_ACTION广播(连接状态发生改变)
  48. filter = new IntentFilter();
  49. filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
  50. getContext().registerReceiver(mReceiver, filter);
  51. //重新将白名单信息交给PowerManagerService和AlarmManagerService
  52. //这个工作在onStart函数中,已经调用updateWhitelistAppIdsLocked进行过了
  53. //到onBootPhase时,重新进行一次,可能:一是为了保险;二是,其它进程可能调用接口,更改了对应数据,于是进行更新
  54. mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
  55. mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
  56. //监听屏幕显示相关的变化
  57. mDisplayManager.registerDisplayListener(mDisplayListener, null);
  58. //更新屏幕显示相关的信息
  59. updateDisplayLocked();
  60. }
  61. //更新连接状态相关的信息
  62. updateConnectivityState(null);
  63. }
  64. }

从代码可以看出,onBootPhase方法:
 主要创建一些本地变量,然后根据配置文件初始化一些传感器,同时注册了一些广播接收器和回到接口,
 最后更新屏幕显示和连接状态相关的信息。

2.2 DeviceIdleController的状态变化

充电状态的处理

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

 

  1. ............
  2. IntentFilter filter = new IntentFilter();
  3. filter.addAction(Intent.ACTION_BATTERY_CHANGED);
  4. getContext().registerReceiver(mReceiver, filter);
  5. ...........

 

我们看看receiver中对应的处理:

 

  1. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  2. @Override public void onReceive(Context context, Intent intent) {
  3. switch (intent.getAction()) {
  4. .........
  5. case Intent.ACTION_BATTERY_CHANGED: {
  6. synchronized (DeviceIdleController.this) {
  7. //从广播中得到是否在充电的消息
  8. int plugged = intent.getIntExtra("plugged", 0);
  9. updateChargingLocked(plugged != 0);
  10. }
  11. } break;
  12. }
  13. }
  14. };

根据上面的代码,可以看出当收到电池信息改变的广播后,DeviceIdleController将得到电源是否在充电的消息,然后调用updateChargingLocked函数进行处理。

 

  1. void updateChargingLocked(boolean charging) {
  2. .........
  3. if (!charging && mCharging) {
  4. //从充电状态变为不充电状态
  5. mCharging = false;
  6. //mForceIdle值一般为false,是通过dumpsys命令将mForceIdle改成true的
  7. if (!mForceIdle) {
  8. //判断是否进入Doze模式
  9. becomeInactiveIfAppropriateLocked();
  10. }
  11. } else if (charging) {
  12. //进入充电状态
  13. mCharging = charging;
  14. if (!mForceIdle) {
  15. //手机退出Doze模式
  16. becomeActiveLocked("charging", Process.myUid());
  17. }
  18. }
  19. }

becomeInactiveIfAppropriateLocked函数是开始进入Doze模式,而becomeActiveLocked是退出Doze模式。

显示状态处理

DeviceIdleController中注册了显示变化的回调

 

                mDisplayManager.registerDisplayListener(mDisplayListener, null);

回调会调用updateDisplayLocked函数

 

  1. private final DisplayManager.DisplayListener mDisplayListener
  2. = new DisplayManager.DisplayListener() {
  3. @Override public void onDisplayAdded(int displayId) {
  4. }
  5. @Override public void onDisplayRemoved(int displayId) {
  6. }
  7. @Override public void onDisplayChanged(int displayId) {
  8. if (displayId == Display.DEFAULT_DISPLAY) {
  9. synchronized (DeviceIdleController.this) {
  10. updateDisplayLocked();
  11. }
  12. }
  13. }
  14. };

updateDisplayLocked函数和更新充电状态的函数updateChargingLocked类似

 

  1. void updateDisplayLocked() {
  2. mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
  3. // We consider any situation where the display is showing something to be it on,
  4. // because if there is anything shown we are going to be updating it at some
  5. // frequency so can't be allowed to go into deep sleeps.
  6. boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;
  7. if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
  8. if (!screenOn && mScreenOn) {
  9. mScreenOn = false;
  10. if (!mForceIdle) {//开始进入Doze模式
  11. becomeInactiveIfAppropriateLocked();
  12. }
  13. } else if (screenOn) {//屏幕点亮,退出Doze模式
  14. mScreenOn = true;
  15. if (!mForceIdle) {
  16. becomeActiveLocked("screen", Process.myUid());
  17. }
  18. }
  19. }

 

becomeActiveLocked函数退出Doze模式

我们先来看看becomeActiveLocked函数

  1. //activeReason记录的终端变为active的原因
  2. void becomeActiveLocked(String activeReason, int activeUid) {
  3. ...........
  4. if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
  5. ............
  6. //1、通知PMS等Doze模式结束
  7. scheduleReportActiveLocked(activeReason, activeUid);
  8. //更新DeviceIdleController本地维护的状态
  9. //在DeviceIdleController的onStart函数中,我们已经知道了
  10. //初始时,mState和mLightState均为Active状态
  11. mState = STATE_ACTIVE;//state是指设备通过传感器判断进入idle
  12. mLightState = LIGHT_STATE_ACTIVE;//mLight是背光的状态
  13. mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
  14. mCurIdleBudget = 0;
  15. mMaintenanceStartTime = 0;
  16. //2、重置一些事件
  17. resetIdleManagementLocked();
  18. resetLightIdleManagementLocked();
  19. addEvent(EVENT_NORMAL);
  20. }
  21. }

scheduleReportActiveLocked函数就是发送MSG_REPORT_ACTIVE消息

  1. void scheduleReportActiveLocked(String activeReason, int activeUid) {
  2. //发送MSG_REPORT_ACTIVE消息
  3. Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
  4. mHandler.sendMessage(msg);
  5. }

我们再看下消息的处理,主要调用了PowerManagerService的setDeviceIdleMode函数来退出Doze状态,然后重新更新wakelock的enable状态, 以及通知NetworkPolicyManagerService不再限制应用上网,还有发送Doze模式改变的广播。

  1. .........
  2. case MSG_REPORT_ACTIVE: {
  3. .........
  4. //通知PMS Doze模式结束,
  5. //于是PMS将一些Doze模式下,disable的WakeLock重新enable
  6. //然后调用updatePowerStateLocked函数更新终端的状态
  7. final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
  8. final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
  9. try {
  10. //通过NetworkPolicyManagerService更改Ip-Rule,不再限制终端应用上网
  11. mNetworkPolicyManager.setDeviceIdleMode(false);
  12. //BSS做好对应的记录
  13. mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
  14. activeReason, activeUid);
  15. } catch (RemoteException e) {
  16. }
  17. //发送广播
  18. if (deepChanged) {
  19. getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
  20. }
  21. if (lightChanged) {
  22. getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
  23. }
  24. }
  25. ........

resetIdleManagementLocked函数就是取消alarm,检测等。

  1. void resetIdleManagementLocked() {
  2. //复位一些状态变量
  3. mNextIdlePendingDelay = 0;
  4. mNextIdleDelay = 0;
  5. mNextLightIdleDelay = 0;
  6. //停止一些工作,主要是位置检测相关的
  7. cancelAlarmLocked();
  8. cancelSensingTimeoutAlarmLocked();
  9. cancelLocatingLocked();
  10. stopMonitoringMotionLocked();
  11. mAnyMotionDetector.stop();
  12. }


becomeInactiveIfAppropriateLocked函数开始进入Doze模式

becomeInactiveIfAppropriateLocked函数就是我们开始进入Doze模式的第一个步骤,下面我们就详细分析这个函数

  1. void becomeInactiveIfAppropriateLocked() {
  2. .................
  3. //屏幕熄灭,未充电
  4. if ((!mScreenOn && !mCharging) || mForceIdle) {
  5. // Screen has turned off; we are now going to become inactive and start
  6. // waiting to see if we will ultimately go idle.
  7. if (mState == STATE_ACTIVE && mDeepEnabled) {
  8. mState = STATE_INACTIVE;
  9. ...............
  10. //重置事件
  11. resetIdleManagementLocked();
  12. //开始检测是否可以进入Doze模式的Idle状态
  13. //若终端没有watch feature, mInactiveTimeout时间为30min
  14. scheduleAlarmLocked(mInactiveTimeout, false);
  15. ...............
  16. }
  17. if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
  18. mLightState = LIGHT_STATE_INACTIVE;
  19. .............
  20. resetLightIdleManagementLocked();//重置事件
  21. scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
  22. }
  23. }
  24. }

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

 

light idle模式

我们先看light idle模式,这个模式下、会禁止网络、wakelock,但是不会禁止alarm。

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

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

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

  1. private final AlarmManager.OnAlarmListener mLightAlarmListener
  2. = new AlarmManager.OnAlarmListener() {
  3. @Override
  4. public void onAlarm() {
  5. synchronized (DeviceIdleController.this) {
  6. stepLightIdleStateLocked("s:alarm");
  7. }
  8. }
  9. };

我们来看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状态,这也符合一个实际情况,但是会有一个上限值。

  1. void stepLightIdleStateLocked(String reason) {
  2. if (mLightState == LIGHT_STATE_OVERRIDE) {
  3. // If we are already in deep device idle mode, then
  4. // there is nothing left to do for light mode.
  5. return;
  6. }
  7. EventLogTags.writeDeviceIdleLightStep();
  8. switch (mLightState) {
  9. case LIGHT_STATE_INACTIVE:
  10. mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
  11. // Reset the upcoming idle delays.
  12. mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
  13. mMaintenanceStartTime = 0;
  14. if (!isOpsInactiveLocked()) {
  15. // We have some active ops going on... give them a chance to finish
  16. // before going in to our first idle.
  17. mLightState = LIGHT_STATE_PRE_IDLE;
  18. EventLogTags.writeDeviceIdleLight(mLightState, reason);
  19. scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);//设置alarm,时间到后到下个步骤
  20. break;
  21. }
  22. // Nothing active, fall through to immediately idle.
  23. case LIGHT_STATE_PRE_IDLE:
  24. case LIGHT_STATE_IDLE_MAINTENANCE:
  25. if (mMaintenanceStartTime != 0) {
  26. long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
  27. if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
  28. // We didn't use up all of our minimum budget; add this to the reserve.
  29. mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
  30. } else {
  31. // We used more than our minimum budget; this comes out of the reserve.
  32. mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
  33. }
  34. }
  35. mMaintenanceStartTime = 0;
  36. scheduleLightAlarmLocked(mNextLightIdleDelay);
  37. mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
  38. (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
  39. if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
  40. mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
  41. }
  42. if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
  43. mLightState = LIGHT_STATE_IDLE;
  44. EventLogTags.writeDeviceIdleLight(mLightState, reason);
  45. addEvent(EVENT_LIGHT_IDLE);
  46. mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);//发送消息,这个消息处理就会关闭网络,禁止wakelock
  47. break;
  48. case LIGHT_STATE_IDLE:
  49. case LIGHT_STATE_WAITING_FOR_NETWORK:
  50. if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
  51. // We have been idling long enough, now it is time to do some work.
  52. mActiveIdleOpCount = 1;
  53. mActiveIdleWakeLock.acquire();
  54. mMaintenanceStartTime = SystemClock.elapsedRealtime();
  55. if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
  56. mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
  57. } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
  58. mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
  59. }
  60. scheduleLightAlarmLocked(mCurIdleBudget);
  61. if (DEBUG) Slog.d(TAG,
  62. "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
  63. mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
  64. EventLogTags.writeDeviceIdleLight(mLightState, reason);
  65. addEvent(EVENT_LIGHT_MAINTENANCE);
  66. mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);//醒一下(开启网络、恢复wakelock)
  67. } else {
  68. // We'd like to do maintenance, but currently don't have network
  69. // connectivity... let's try to wait until the network comes back.
  70. // We'll only wait for another full idle period, however, and then give up.
  71. scheduleLightAlarmLocked(mNextLightIdleDelay);
  72. if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
  73. mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
  74. EventLogTags.writeDeviceIdleLight(mLightState, reason);
  75. }
  76. break;
  77. }
  78. }

但是这里只是一个light idle,一旦进入deep idle,light idle设置的alarm会无效的(这个后面细说),也就是说light idle一旦进入deep idle后无效了(因为idle step主要靠alarm驱动,而alarm无效后自然就驱动不了)。
 

deep idle模式

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

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

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

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

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

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

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

  1. private final AlarmManager.OnAlarmListener mDeepAlarmListener
  2. = new AlarmManager.OnAlarmListener() {
  3. @Override
  4. public void onAlarm() {
  5. synchronized (DeviceIdleController.this) {
  6. //进入到stepIdleStateLocked函数
  7. stepIdleStateLocked("s:alarm");
  8. }
  9. }
  10. };

下面我们就来看下stepIdleStateLocked函数:

  1. void stepIdleStateLocked(String reason) {
  2. ..........
  3. final long now = SystemClock.elapsedRealtime();
  4. //个人觉得,下面这段代码,是针对Idle状态设计的
  5. //如果在Idle状态收到Alarm,那么将先唤醒终端,然后重新判断是否需要进入Idle态
  6. //在介绍Doze模式原理时提到过,若应用调用AlarmManager的一些指定接口,仍然可以在Idle状态进行工作
  7. if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
  8. // Whoops, there is an upcoming alarm. We don't actually want to go idle.
  9. if (mState != STATE_ACTIVE) {
  10. becomeActiveLocked("alarm", Process.myUid());
  11. becomeInactiveIfAppropriateLocked();
  12. }
  13. return;
  14. }
  15. //以下是Doze模式的状态转变相关的代码
  16. switch (mState) {
  17. case STATE_INACTIVE:
  18. // We have now been inactive long enough, it is time to start looking
  19. // for motion and sleep some more while doing so.
  20. //保持屏幕熄灭,同时未充电达到30min,进入此分支
  21. //注册一个mMotionListener,检测是否移动
  22. //如果检测到移动,将重新进入到ACTIVE状态
  23. //相应代码比较直观,此处不再深入分析
  24. startMonitoringMotionLocked();
  25. //再次调用scheduleAlarmLocked函数,此次的时间仍为30min
  26. //也就说如果不发生退出Doze模式的事件,30min后将再次进入到stepIdleStateLocked函数
  27. //不过届时的mState已经变为STATE_IDLE_PENDING
  28. scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);
  29. // Reset the upcoming idle delays.
  30. //mNextIdlePendingDelay为5min
  31. mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
  32. //mNextIdleDelay为60min
  33. mNextIdleDelay = mConstants.IDLE_TIMEOUT;
  34. //状态变为STATE_IDLE_PENDING
  35. mState = STATE_IDLE_PENDING;
  36. ............
  37. break;
  38. case STATE_IDLE_PENDING:
  39. //保持息屏、未充电、静止状态,经过30min后,进入此分支
  40. mState = STATE_SENSING;
  41. //保持Doze模式条件,4min后再次进入stepIdleStateLocked
  42. scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);
  43. //停止定位相关的工作
  44. cancelLocatingLocked();
  45. mNotMoving = false;
  46. mLocated = false;
  47. mLastGenericLocation = null;
  48. mLastGpsLocation = null;
  49. //开始检测手机是否发生运动(这里应该是更细致的侧重于角度的变化)
  50. //若手机运动过,则重新变为active状态
  51. mAnyMotionDetector.checkForAnyMotion();
  52. break;
  53. case STATE_SENSING:
  54. //上面的条件满足后,进入此分支,开始获取定位信息
  55. cancelSensingTimeoutAlarmLocked();
  56. mState = STATE_LOCATING;
  57. ............
  58. //保持条件30s,再次调用stepIdleStateLocked
  59. scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
  60. //网络定位
  61. if (mLocationManager != null
  62. && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
  63. mLocationManager.requestLocationUpdates(mLocationRequest,
  64. mGenericLocationListener, mHandler.getLooper());
  65. mLocating = true;
  66. } else {
  67. mHasNetworkLocation = false;
  68. }
  69. //GPS定位
  70. if (mLocationManager != null
  71. && mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
  72. mHasGps = true;
  73. mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
  74. mGpsLocationListener, mHandler.getLooper());
  75. mLocating = true;
  76. } else {
  77. mHasGps = false;
  78. }
  79. // If we have a location provider, we're all set, the listeners will move state
  80. // forward.
  81. if (mLocating) {
  82. //无法定位则直接进入下一个case
  83. break;
  84. }
  85. case STATE_LOCATING:
  86. //停止定位和运动检测,直接进入到STATE_IDLE_MAINTENANCE
  87. cancelAlarmLocked();
  88. cancelLocatingLocked();
  89. mAnyMotionDetector.stop();
  90. case STATE_IDLE_MAINTENANCE:
  91. //进入到这个case后,终端开始进入Idle状态,也就是真正的Doze模式
  92. //定义退出Idle的时间此时为60min
  93. scheduleAlarmLocked(mNextIdleDelay, true);
  94. ............
  95. //退出周期逐步递增,每次乘2
  96. mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
  97. ...........
  98. //周期有最大值6h
  99. mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
  100. if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
  101. mNextIdleDelay = mConstants.IDLE_TIMEOUT;
  102. }
  103. mState = STATE_IDLE;
  104. ...........
  105. //通知PMS、NetworkPolicyManagerService等Doze模式开启,即进入Idle状态
  106. //此时PMS disable一些非白名单WakeLock;NetworkPolicyManagerService开始限制一些应用的网络访问
  107. //消息处理的具体流程比较直观,此处不再深入分析
  108. mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
  109. break;
  110. case STATE_IDLE:
  111. //进入到这个case时,本次的Idle状态暂时结束,开启maintenance window
  112. // We have been idling long enough, now it is time to do some work.
  113. mActiveIdleOpCount = 1;
  114. mActiveIdleWakeLock.acquire();
  115. //定义重新进入Idle的时间为5min (也就是手机可处于Maintenance window的时间)
  116. scheduleAlarmLocked(mNextIdlePendingDelay, false);
  117. mMaintenanceStartTime = SystemClock.elapsedRealtime();
  118. //调整mNextIdlePendingDelay,乘2(最大为10min)
  119. mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
  120. (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
  121. if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
  122. mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
  123. }
  124. mState = STATE_IDLE_MAINTENANCE;
  125. ...........
  126. //通知PMS等暂时退出了Idle状态,可以进行一些工作
  127. //此时PMS enable一些非白名单WakeLock;NetworkPolicyManagerService开始允许应用的网络访问
  128. mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
  129. break;
  130. }
  131. }

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

  1. case MSG_REPORT_IDLE_ON:
  2. case MSG_REPORT_IDLE_ON_LIGHT: {
  3. EventLogTags.writeDeviceIdleOnStart();
  4. final boolean deepChanged;
  5. final boolean lightChanged;
  6. if (msg.what == MSG_REPORT_IDLE_ON) {
  7. deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
  8. lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
  9. } else {
  10. deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
  11. lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
  12. }
  13. try {
  14. mNetworkPolicyManager.setDeviceIdleMode(true);
  15. mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
  16. ? BatteryStats.DEVICE_IDLE_MODE_DEEP
  17. : BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
  18. } catch (RemoteException e) {
  19. }
  20. if (deepChanged) {
  21. getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
  22. }
  23. if (lightChanged) {
  24. getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
  25. }
  26. EventLogTags.writeDeviceIdleOnComplete();
  27. } break;

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

scheduleAlarmLocked(mNextIdleDelay, true);

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

Idle总结

当手机关闭屏幕或者拔掉电源的时候,手机开始判断是否进入Doze模式。

Doze模式分两种,第一种是light idle:

1.light idle

light idle在手机灭屏且没有充电状态下,5分钟开始进入light idle流程。然后第一次进入LIGHT_STATE_INACTIVE流程时,会再定义一个10分钟的alarm。然后系统进入light idle状态。这个状态会使不是白名单的应用禁止访问网络,以及持wakelock锁。

2.deep idle

deep idle除了light idle的状态还会把非白名单中应用的alarm也禁止了。
 此时,系统中非白名单的应用将被禁止访问网络,它们申请的Wakelock也会被disable。
 从上面的代码可以看出,系统会周期性的退出Idle状态,进入到MAINTENANCE状态,集中处理相关的任务。
 一段时间后,会重新再次回到IDLE状态。每次进入IDLE状态,停留的时间都会是上次的2倍,最大时间限制为6h。

当手机运动,或者点亮屏幕,插上电源等,系统都会重新返回到ACTIVIE状态。

这里盗用别人的一样图,但仅仅是deep idle的状态:

(这里特别说明下,alarm和wakelock都是由DeviceIdleController主动调用相关接口设置的,而网络是调用了DeviceIdleController的getAppIdWhitelist接口来获取应用的白名单的,从而禁止非白名单访问网络。)

 

网络我们不分析了,之前我们在Android6.0时分析过idle状态下alarm和wakelock,7.0稍微有点不一样,下面两篇博客重新分析下吧。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/712548
推荐阅读
相关标签
  

闽ICP备14008679号