科技的仿生学无处不在,给予我们启发。为了延长电池是使用寿命,google从蛇的冬眠中得到体会,那就是在某种情况下也让手机进入类冬眠的情况,从而引入了今天的主题,Doze模式,Doze中文是打盹儿,打盹当然比活动节约能量了。

手机打盹儿的时候会怎样呢?

按照google的官方说法,Walklocks,网络访问,jobshedule,闹钟,GPS/WiFi扫描都会停止。这些停止后,将会节省30%的电量。

手机什么时候才会开始打盹呢?

上图是谷歌的Doze时序示意图,可以看出让手机打盹要满足三个条件

1.屏幕熄灭
2 .不插电
3.静止不动

这个是不是很仿生学呢?屏幕熄灭->闭上双眼,不插电->不吃东西,静止不动->安静地做个睡美人。生物不也是要满足这些条件才能打盹吗?妙,是在妙!

打盹总得呼吸吧?上图中的maintenance window就是给你呼吸的!!呼吸的时候Walklocks,网络访问,jobshedule,闹钟,GPS/WiFi扫描这些都会恢复,来吧重重的吸一口新鲜空气吧!随着时间的推移,呼吸的间隔会越变越大,而每次呼吸的时间也会变长,当然,伙计,不会无限长!!最后都会归于一个定值。下面分析源码就知道了,biu!

没源码,说个球儿

下面以一台手机静静地放在桌面上,随着时间的推移,进入doze模式的过程来分析源码。
源码路径:/frameworks/base/services/core/java/com/android/server/DeviceIdleController.java
系统中用一个全局整形变量来表示当前doze的状态

 private int mState;

状态值的可能取值有以下,一开始的状态是STATE_ACTIVE。会依次经过1,2,3,4,状态后进入5状态,即STATE_IDLE

     private static final int STATE_ACTIVE = 0;
private static final int STATE_INACTIVE = 1;
private static final int STATE_IDLE_PENDING = 2;
private static final int STATE_SENSING = 3;
private static final int STATE_LOCATING = 4;
private static final int STATE_IDLE = 5;
private static final int STATE_IDLE_MAINTENANCE = 6;

首先屏幕熄灭,回调熄屏处理函数

     private final DisplayManager.DisplayListener mDisplayListener
= new DisplayManager.DisplayListener() {
@Override public void onDisplayAdded(int displayId) {
} @Override public void onDisplayRemoved(int displayId) {
} @Override public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
synchronized (DeviceIdleController.this) {
updateDisplayLocked(); //屏幕状态改变
}
}
}
};

进入updateDisplayLocked

     void updateDisplayLocked() {
...
becomeInactiveIfAppropriateLocked(); //看是否可以进入Inactive状态
....
}
}

然后我们拔出usb,不充电,会回调充电处理函数

     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0); //充电状态改变
} else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
stepIdleStateLocked();
}
}
}
};

进入updateChargingLocked

     void updateChargingLocked(boolean charging) {
....
becomeInactiveIfAppropriateLocked();//看是否可以进入Inactive状态
.....
}

最后不插电和熄灭屏幕后都会进入becomeInactiveIfAppropriateLocked,状态mState变成STATE_INACTIVE,并且开启了一个定时器

 void becomeInactiveIfAppropriateLocked() {
if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
//不插电和屏幕熄灭的条件都满足了
if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {
.....
mState = STATE_INACTIVE;
scheduleAlarmLocked(mInactiveTimeout, false);
......
}
} 定时时长为常量30分钟
INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);

手机静静地躺在桌面上30分钟后,定时器时间到达后,pendingintent会被发出,广播接收器进行处理

  Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
.setPackage("android")
.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
int plugged = intent.getIntExtra("plugged", 0);
updateChargingLocked(plugged != 0);
} else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
synchronized (DeviceIdleController.this) {
stepIdleStateLocked(); //接收到广播
}
}
}
};

进入stepIdleStateLocked,该函数是状态转换处理的主要函数

     void stepIdleStateLocked() {
if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
EventLogTags.writeDeviceIdleStep(); final long now = SystemClock.elapsedRealtime();
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());
}
return;
} switch (mState) {
case STATE_INACTIVE:
// We have now been inactive long enough, it is time to start looking
// for significant motion and sleep some more while doing so.
startMonitoringSignificantMotion(); //观察是否有小动作
scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); //设置观察小动作要观察多久
mState = STATE_IDLE_PENDING; //状态更新为STATE_IDLE_PENDING
break;
case STATE_IDLE_PENDING: //小动作观察结束,很厉害,一直都没有小动作,会进入这里
mState = STATE_SENSING;//状态更新为STATE_SENSING
scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);//设置传感器感应时长
mAnyMotionDetector.checkForAnyMotion(); //传感器感应手机有没有动
break;
case STATE_SENSING: //传感器也没发现手机动,就来最后一发,看GPS有没有动
mState = STATE_LOCATING;//状态更新为STATE_LOCATING
scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);//设置GPS观察时长
mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,
mHandler.getLooper());//GPS开始感应
break;
case STATE_LOCATING: //GPS也发现没动
cancelSensingAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.stop(); //这里没有break,直接进入下一个case
case STATE_IDLE_MAINTENANCE:
scheduleAlarmLocked(mNextIdleDelay, true);//设置打盹多久后进行呼吸
mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);//更新下次打盹多久后进行呼吸
if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
mState = STATE_IDLE; //噢耶 终于进入了STATE_IDLE
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE: //打盹完了,呼吸一下就是这里了
scheduleAlarmLocked(mNextIdlePendingDelay, false);
mState = STATE_IDLE_MAINTENANCE; //状态更新为STATE_IDLE_MAINTENANCE
mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
//更新下次呼吸的时间
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}

Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
这一句看到了吗?取最小值,这里就是保证了idle和窗口的时间不会变成无限大。
为了让各位有个感官的体验,上面的一些时间我直接列出来吧

熄屏不插电进入INACTIVE时间上面说了30分钟

观察小动作的时间30分钟
IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L); 观察传感器的时间4分钟
SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
!DEBUG ? 4 * 60 * 1000L : 60 * 1000L); 观察GPS的时间30秒
LOCATING_TIMEOUT = mParser.getLong(KEY_LOCATING_TIMEOUT,
!DEBUG ? 30 * 1000L : 15 * 1000L);

所以进入idle的总时间为30分钟+30分钟+4分钟+30s=1小时4分钟30秒,哈哈哈哈!!

下面给张状态转换图看看,没到达idle状态前,基本上有什么风吹草动都会变回ACTIVE状态。而变成IDLE状态后,只能插电或者点亮屏幕才离开IDLE状态。就像人入睡前,很容易被吵醒,而深度入眠后,估计只有闹钟能闹醒你了!!

上面说了这么多,跟我应用开发有什么关系?

其实,没多大关系,看下源码不行噻。
不过作为一种新的机制,最好测试下你的应用在这几种状态下是否能够正常运行,起码不能挂掉啊。
google提供了adb的指令来强制变换状态,这样你就不用干等着它状态变化了。

 adb shell dumpsys battery unplug       //相当于不插电
adb shell dumpsys device idle step //让状态转换

转自:http://www.jianshu.com/p/8fb25f53bed4?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq#

Android Doze模式源码分析的更多相关文章

  1. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

  2. OpenGL—Android 开机动画源码分析一

    .1 Android开机动画实现方式目前实现Android开机动画的方式主要是逐帧动画和OpenGL动画. ?逐帧动画 逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的 ...

  3. Android网络框架源码分析一---Volley

    转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...

  4. Android分包MultiDex源码分析

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/52845661 本文出自: [HansChen的博客] 概述 Android开发者应该 ...

  5. 【一起学源码-微服务】Nexflix Eureka 源码十二:EurekaServer集群模式源码分析

    前言 前情回顾 上一讲看了Eureka 注册中心的自我保护机制,以及里面提到的bug问题. 哈哈 转眼间都2020年了,这个系列的文章从12.17 一直写到现在,也是不容易哈,每天持续不断学习,输出博 ...

  6. Android 开机动画源码分析

    Android系统在启动SystemServer进程时,通过两个阶段来启动系统所有服务,在第一阶段启动本地服务,如SurfaceFlinger,SensorService等,在第二阶段则启动一系列的J ...

  7. Android开源框架源码分析:Okhttp

    一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...

  8. Android消息机制源码分析

    本篇主要介绍Android中的消息机制,即Looper.Handler是如何协同工作的: Looper:主要用来管理当前线程的消息队列,每个线程只能有一个Looper Handler:用来将消息(Me ...

  9. android hardware.c 源码分析

    android的jni通过ID来找hal模块,使用了hw_get_module()函数,本文就通过这个函数的来分析一下hal层的模块是如何匹配的. 首先要了解三个结构体hw_module_t,hw_m ...

随机推荐

  1. centos7 install python3

    1. 过程 # 1. root权限, 安装依赖 yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-dev ...

  2. QT子窗口及停靠实现

    Demo的效果 头文件中的变量声明 //退出动作 QAction* exit; //菜单栏菜单 QMenu* filemenu; QMenu* actiona; //在状态栏的标签控件 QLabel* ...

  3. Auto Layout Guide----(一)-----Understanding Auto Layout

    Understanding Auto Layout 理解自动布局 Auto Layout dynamically calculates the size and position of all the ...

  4. oracle查看表,索引,视图,存储过程的定义

    通过  DBMS_METADATA  包 Oracle 的在线文档,对这个包有详细说明: DBMS_METADATA 通过该dbms_metadata包的get_ddl()方法,我们可以查看表,索引, ...

  5. 怎么判断DropDownList是否选择值

    判断其 SelectedIndex 属性值 >0.

  6. 蜂窝网络TDOA定位方法的Fang算法研究及仿真纠错

    科学论文为我们提供科学方法,在解决实际问题中,能极大提高生产效率.但论文中一些失误则可能让使用者浪费大量时间.自己全部再推导那真不容易,怀疑的成本特别高,通常不会选择这条路.而如果真是它的问题,其它所 ...

  7. 微信公众号授权登录,提示“redirect_uri 参数错误”

    做微信公众号开发授权登录的时候遇到的坑... 后台服务用node,index.js相关代码如下: const oauth = new OAuth(conf.appid, conf.appsecret) ...

  8. [poj] Catch That Cow--bfs

    Description Farmer John has been informed of the location of a fugitive cow and wants to catch her i ...

  9. centos7及以上安装git服务

    检查git是否安装或者是版本 whereis git等命令来检查是否已经安装了git版本的 git --version检测到我的环境自带的git版本 已安装但不是想要的版本需要卸载 yum remov ...

  10. unity coroutine

    http://gad.qq.com/article/detail/695 使用Unity 3D引擎的同学,对于Coroutine(协程)的使用肯定也是非常熟悉的了.然而Coroutine背后的技术以及 ...