Android中的Doze模式

article/2025/9/15 11:41:09

概述

Android 6.0引入的Doze机制在于节省系统耗电量,保护电池,延长电池的使用时间。当设备未连接至电源,且长时间处于闲置状态时,系统会将应用进入Doze,置于App Standby模式。而最终的效果,能提升30%的电量续航能力。

Doze模式的状态

该状态与API版本无关,未适配API23以上的应用只要运行在6.0以上的系统上就会受到Doze模式的影响。

  • 在屏幕熄灭30分钟、没有晃动并且在不充电的时候,会进入Doze模式
  • 在进入Doze模式后,每间隔一段时间,会进入一段时长为30s的maintenance window的窗口期,可以唤醒系统,进行网络交互等等
  • 进入Doze模式后,如果没有退出的话,系统唤醒的间隔时长会越来越长

当系统处于Doze模式下,系统和白名单之外的应用将受到以下限制:

  • 无法访问网络
  • Wake Locks被忽略
  • AlarmManager闹铃会被推迟到下一个maintenance window响应
    • 使用setAndAllowWhileIdleSetExactAndAllowWhileIdle设置闹铃的闹钟则不会受到Doze模式的影响
    • setAlarmClock设置的闹铃在Doze模式下仍然生效,但系统会在闹铃生效前退出Doze
  • 系统不执行Wi-Fi/GPS扫描;
  • 系统不允许同步适配器运行;
  • 系统不允许JobScheduler运行;

而位于白名单中的应用可以:

  • 继续使用网络并保留部分wake lock
  • Job和同步仍然会被推迟
  • 常规的AlarmManager闹铃也不会被触发

应用申请加入白名单

App可以通过PowerManager.isIgnoringBatteryOptimizations检查本App是否在系统的白名单列表中。

如果不在,则可以通过在AndroidManifest.xml中添加REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限,并且通过发送ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS的Intent来向用户申请该权限

原理

Doze的原理是在框架层对资源加入了一层资源的调度。在监听系统硬件或者屏幕亮暗的中断信号所发出来的广播,然后对于JobScheduler以及AlarmManager中任务进行统一调度。

而Doze的源码在于链接:
/frameworks/base/services/core/java/com/android/server/DeviceIdleController.java

DeviceIdleController中存在一个mState变量来保存当前设备的状态,状态值如下:



作者:None_Ling
链接:https://www.jianshu.com/p/d62d58d6ba5a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  /** Device is currently active. */private static final int STATE_ACTIVE = 0;/** Device is inactve (screen off, no motion) and we are waiting to for idle. */private static final int STATE_INACTIVE = 1;/** Device is past the initial inactive period, and waiting for the next idle period. */private static final int STATE_IDLE_PENDING = 2;/** Device is currently sensing motion. */private static final int STATE_SENSING = 3;/** Device is currently finding location (and may still be sensing). */private static final int STATE_LOCATING = 4;/** Device is in the idle state, trying to stay asleep as much as possible. */private static final int STATE_IDLE = 5;/** Device is in the idle state, but temporarily out of idle to do regular maintenance. */private static final int STATE_IDLE_MAINTENANCE = 6;

DeviceIdleController继承自SystemService,在SystemServer初始化的时候,会初始化该对象,并且将它添加到ServiceManager

而在onBootPhase,即设备Boot初始化阶段,也就是所有的SystemService都初始化完毕后,DeviceIdleController会初始化需要用到的AlarmManager、LocationManager等,并且会调用updateDisplayLoced

@Overridepublic void onBootPhase(int phase) {if (phase == PHASE_SYSTEM_SERVICES_READY) {synchronized (this) {mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);mBatteryStats = BatteryStatsService.getService();mLocalPowerManager = getLocalService(PowerManagerInternal.class);mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));mDisplayManager = (DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);mLocationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);mLocationRequest = new LocationRequest().setQuality(LocationRequest.ACCURACY_FINE).setInterval(0).setFastestInterval(0).setNumUpdates(1);mAnyMotionDetector = new AnyMotionDetector((PowerManager) getContext().getSystemService(Context.POWER_SERVICE),mHandler, mSensorManager, this);Intent intent = new Intent(ACTION_STEP_IDLE_STATE).setPackage("android").setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);Intent intentSensing = new Intent(ACTION_STEP_IDLE_STATE).setPackage("android").setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);mSensingAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intentSensing, 0);mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY| Intent.FLAG_RECEIVER_FOREGROUND);IntentFilter filter = new IntentFilter();filter.addAction(Intent.ACTION_BATTERY_CHANGED);filter.addAction(ACTION_STEP_IDLE_STATE);getContext().registerReceiver(mReceiver, filter);mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);mDisplayManager.registerDisplayListener(mDisplayListener, null);updateDisplayLocked();}}}

而在updateDisplayLockedupdateChargingLocked函数中会判断当前屏幕是否亮着,或者是否在充电,如果屏幕熄灭或者没在充电的话,则会调用becomeInactiveIfAppropriateLocked开始准备进入Doze状态。
PS:后者是在收到ACTION_BATTERY_CHANGED的时候调用的,代表充电的变化

void updateDisplayLocked() {mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);// We consider any situation where the display is showing something to be it on,// because if there is anything shown we are going to be updating it at some// frequency so can't be allowed to go into deep sleeps.boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);if (!screenOn && mScreenOn) {mScreenOn = false;if (!mForceIdle) {becomeInactiveIfAppropriateLocked();}} else if (screenOn) {mScreenOn = true;if (!mForceIdle) {becomeActiveLocked("screen", Process.myUid());}}}void updateChargingLocked(boolean charging) {if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging);if (!charging && mCharging) {mCharging = false;if (!mForceIdle) {becomeInactiveIfAppropriateLocked();}} else if (charging) {mCharging = charging;if (!mForceIdle) {becomeActiveLocked("charging", Process.myUid());}}}

becomeInactiveIfAppropriateLocked函数中:

  • 将状态设置成STATE_INACTIVE
  • 取消定位、传感器监听的闹钟
  • 重新设置mInactiveTimeout时长的闹钟,也就是30分钟或者3分钟
  • 在闹钟的Intent中,会发送一个广播ACTION_STEP_IDLE_STATE
void becomeInactiveIfAppropriateLocked() {if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {// Screen has turned off; we are now going to become inactive and start// waiting to see if we will ultimately go idle.mState = STATE_INACTIVE;if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");resetIdleManagementLocked();scheduleAlarmLocked(mInactiveTimeout, false);EventLogTags.writeDeviceIdle(mState, "no activity");}}void resetIdleManagementLocked() {mNextIdlePendingDelay = 0;mNextIdleDelay = 0;cancelAlarmLocked();cancelSensingAlarmLocked();cancelLocatingLocked();stopMonitoringSignificantMotion();mAnyMotionDetector.stop();}

在接收到ACTION_STEP_IDLE_STATE的广播后,会调用stepIdleStateLocked,在该函数中,处理所有的状态变化,而在状态处理的过程中还会有几个Alarm被设置。在该函数中,主要涉及一些状态变化,以及闹钟的设置,借图说明:

最终,在进入Doze模式后,会通过mHandler发送一个MSG_REPORT_IDLE_ON的消息,在该消息中,通过mNetworkPolicyManager.setDeviceIdleMode禁止网络连接,通过PowerManager来限制WakeLock

case MSG_REPORT_IDLE_ON: {EventLogTags.writeDeviceIdleOnStart();mLocalPowerManager.setDeviceIdleMode(true);try {mNetworkPolicyManager.setDeviceIdleMode(true);mBatteryStats.noteDeviceIdleMode(true, null, Process.myUid());} catch (RemoteException e) {}getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);EventLogTags.writeDeviceIdleOnComplete();

 


http://chatgpt.dhexx.cn/article/6mCNDyVy.shtml

相关文章

Mock工具之Mockito实战

在实际项目中写单元测试的过程中我们会发现需要测试的类有很多依赖,这些依赖项又会有依赖,导致在单元测试代码里几乎无法完成构建,尤其是当依赖项尚未构建完成时会导致单元测试无法进行。为了解决这类问题我们引入了Mock的概念,简…

Mockito 入门

目录 1.什么是 Mock 测试?2.Mockito简介3.在 SpringBoot 单元测试中使用 Mockito3.1 Maven依赖:3.2 UserService.java3.3 User.java3.4 thenReturn系列方法(测试桩)3.5 thenThrow系列方法3.6 verify 系列方法 4.Spring中mock任何容…

Mock工具之Moco使用教程

目录 一、什么是Moco二、安装&配置三、接口配置&测试3.1 第一个简单的请求:3.2 Get请求:3.3 Post请求:3.4 返回值为Json格式的请求:3.5 带cookie信息才能访问的post请求:3.6 重定向请求:3.7 前后端…

mock功能

目标 了解mock的作用及使用场景; mock使用场景 mock:假的 前端程序员提到的mock数据的含义是:真的假数据 真的:符合接口规范要求的。 假数据:数据是人为创建出来的,不是真正的业务数据。 什么时候需…

mock的介绍

1.什么是mock mock测试是以可控的方式模拟真实的对象行为。程序员通常创造模拟对象来测试对象本身该具备的行为,很类似汽车设计者使用碰撞测试假人来模拟车辆碰撞中人的动态行为 2.为什么要使用mock 之所以使用mock测试,是因为真实场景很难实现或者短…

Mock介绍

mock的定义(what): mock是在测试过程中,对于一些不容易构造/获取的对象,创建一个mock对象来模拟对象的行为 为什么要使用mock(why): 在做单元测试过程中,经常会有以下的…

Mockito单元测试

文章目录 Mockito单元测试为什么要使用Mock?导入依赖import导入包使用Mock模拟测试某个类中的某个方法是否可以成功执行使用Mock模拟某个类的方法,自己给这个方法返回我们指定的值使用Mock模拟某个方法调用后会抛出指定的异常使用Mock模拟测试某个类中的某个方法(加…

javascript 中怎么判断为数字类型

javascript中判断变量是否为数字类型抄的方法有两种: 方法一: function isnum(s) { if(s!null){ var r,re; re /\d*/i; //\d表示数字,*表示匹配多个数字 r s.match(re); return (rs)?true:false; } return false; } 方法二: function isnu…

js判断字符串是不是一个纯数字

1.使用隐式转换判断 使用parseInt()/parseFloat()会把其他类型转换为数值,转换结果为数值或NaN,转换字符串的时候,头部内容有数值就会截取前面,如123456aff789bb转成123456,后面即使有数字也不会转换处理,开头如果是字…

javaScript 判断是否为数值类型的三种方法

第一种方法:isNaN(value) isNaN : isNaN() 函数用于检查其参数是否是非数字值。 返回true则判断为非数值。缺点:空字符串,空格 ,null会做为数字0进行处理,结果为falsehttps://www.w3school.com.cn/js/jsref_isnan.anusp 第二种方法…

JS中判断变量是否为数字方法

推荐教程:《JavaScript视频教程》 JavaScript 是一种动态类型语言,这意味着解释器在运行时确定变量的类型。实际上,这也允许我们在相同的代码中使用相同的变量来存储不同类型的数据。如果没有文档和一致性,我们在使用代码时并不总…

matlab绘制图形中,常用函数调用(num2str,disp,gcf,hold on,plot,axis,subplot,line,stairs,grid,set,gca)

num2str 将数字转换为字符数组 s = num2str(A) 将数字数组转换为表示数字的字符数组。输出格式取决于原始值的大小。 num2str对于使用数值标注和标题绘图非常有用。 s = num2str(A,precision) 返回一个字符数组,表示具有精度指定的最大有效位数的数字。 s = num2str(A,fo…

Matlab中num2str函数使用

目录 语法 说明 示例 浮点值的默认转换 指定精度 指定格式 num2str函数将数字转换为字符数组。 语法 s num2str(A)s num2str(A,precision)s num2str(A,formatSpec) 说明 s num2str(A) 将数值数组转换为表示数字的字符数组。输出格式取决于原始值的量级。num2str 对…

C/C++ split函数 num2str str2num函数

C/C split函数,num2str及str2num函数实现 在C98及其以下版本的C/C中,没有现成的字符串split函数和num2str及str2num函数,对于竞赛不能使用C11的我来说非常头疼,通过学习stringstream和strtok实现了自己的split函数,通过stringstr…

matlab 中num2str函数的使用

运行ECO代码报错:转义字符 ‘\d’ 无效。有关支持的特殊字符,请参阅 ‘doc sprintf’。 num2str函数功能:将数字转换为字符串 具体所有功能可见官网。 这里使用的格式为: str num2str(A, format) 将 format 指定的格式应用到 A …

js基础运用——js用*打印三角形,菱形

js基础运用 新手见解,有不足或更简单的方法希望各位指点 1.用*排列直角三形 (图一) (图二) 图一图二是js语句利用*号画出来的直角三角形,方法思路都一致,下面以图一为例讲一下本人所用的方法…

java:打印十字图

java:打印十字图 题目 问题描述 小明为某机构设计了一个十字型的徽标(并非红十字会啊),如下所示:..$$$$$$$$$$$$$.. ..$...........$.. $$$.$$$$$$$$$.$$$ $...$.......$...$ $.$$$.$$$$$.$$$.$ $.$...$...$...$.$ $.$.$$$.$.$$…

JAVA 利用JFram,JPanel,JScrollPane简单文本框实现

****1.JFram 支持通用窗口的所有基本功能 2…JPanel是一个无边框,不可以被移动的,放大的,缩小的,或者关闭的面板,默认布局是FlowLayout(也可用setLayout()来设定)** 3……

deflater java_Java Deflater finished()用法及代码示例

如果已到达压缩数据输出流的末尾,则java.util.zip中Deflater类的finished()函数将返回true。 函数签名: public boolean finished() 用法: d.finished(); 参数:该函数不需要参数 返回类型:该函数返回布尔值,即如果所有…

jQuery API ( 三 ) -------- 链式编程 与 修改样式方法

今天这篇文章是 jQuery API 的第三篇,今天心情很好,因为破了300粉,希望所有代码人前端人在编程与写作的路上都能一帆风顺,全都早日拿认证。okk言归正传,这篇文章将带大家走进链式编程的世界,这也是 jQuery …