Android Doze模式源碼分析

来源:http://www.cnblogs.com/l2rf/archive/2017/02/07/6373794.html
-Advertisement-
Play Games

科技的仿生學無處不在,給予我們啟發。為了延長電池是使用壽命,google從蛇的冬眠中得到體會,那就是在某種情況下也讓手機進入類冬眠的情況,從而引入了今天的主題,Doze模式,Doze中文是打盹兒,打盹當然比活動節約能量了。 手機打盹兒的時候會怎樣呢? 按照google的官方說法,Walklocks, ...


 

科技的仿生學無處不在,給予我們啟發。為了延長電池是使用壽命,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的狀態

1 private int mState;

狀態值的可能取值有以下,一開始的狀態是STATE_ACTIVE。會依次經過1,2,3,4,狀態後進入5狀態,即STATE_IDLE

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

首先屏幕熄滅,回調熄屏處理函數

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

進入updateDisplayLocked

1     void updateDisplayLocked() {
2                 ...
3                 becomeInactiveIfAppropriateLocked(); //看是否可以進入Inactive狀態
4                 ....
5         }
6     }

然後我們拔出usb,不充電,會回調充電處理函數

 1     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 2         @Override public void onReceive(Context context, Intent intent) {
 3             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
 4                 int plugged = intent.getIntExtra("plugged", 0);
 5                 updateChargingLocked(plugged != 0); //充電狀態改變
 6             } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
 7                 synchronized (DeviceIdleController.this) {
 8                     stepIdleStateLocked();
 9                 }
10             }
11         }
12     };

進入updateChargingLocked

1     void updateChargingLocked(boolean charging) {
2          ....
3          becomeInactiveIfAppropriateLocked();//看是否可以進入Inactive狀態
4          .....
5     }

最後不插電和熄滅屏幕後都會進入becomeInactiveIfAppropriateLocked,狀態mState變成STATE_INACTIVE,並且開啟了一個定時器

 1 void becomeInactiveIfAppropriateLocked() {
 2         if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
 3         //不插電和屏幕熄滅的條件都滿足了
 4         if (((!mScreenOn && !mCharging) || mForceIdle) && mEnabled && mState == STATE_ACTIVE) {
 5             .....
 6             mState = STATE_INACTIVE;
 7             scheduleAlarmLocked(mInactiveTimeout, false);   
 8             ......
 9         }
10     }
11 
12     定時時長為常量30分鐘
13     INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
14                         !COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);

手機靜靜地躺在桌面上30分鐘後,定時器時間到達後,pendingintent會被髮出,廣播接收器進行處理

 1  Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
 2                      .setPackage("android")
 3                      .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
 4  mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
 5 
 6     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
 7         @Override public void onReceive(Context context, Intent intent) {
 8             if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
 9                 int plugged = intent.getIntExtra("plugged", 0);
10                 updateChargingLocked(plugged != 0);
11             } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
12                 synchronized (DeviceIdleController.this) {
13                     stepIdleStateLocked();   //接收到廣播
14                 }
15             }
16         }
17     };

進入stepIdleStateLocked,該函數是狀態轉換處理的主要函數

 1     void stepIdleStateLocked() {
 2         if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState);
 3         EventLogTags.writeDeviceIdleStep();
 4 
 5         final long now = SystemClock.elapsedRealtime();
 6         if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {
 7             // Whoops, there is an upcoming alarm.  We don't actually want to go idle.
 8             if (mState != STATE_ACTIVE) {
 9                 becomeActiveLocked("alarm", Process.myUid());
10             }
11             return;
12         }
13 
14         switch (mState) {
15             case STATE_INACTIVE:
16                 // We have now been inactive long enough, it is time to start looking
17                 // for significant motion and sleep some more while doing so.
18                 startMonitoringSignificantMotion(); //觀察是否有小動作
19                 scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false); //設置觀察小動作要觀察多久
20                 mState = STATE_IDLE_PENDING; //狀態更新為STATE_IDLE_PENDING
21                 break;
22             case STATE_IDLE_PENDING: //小動作觀察結束,很厲害,一直都沒有小動作,會進入這裡
23                 mState = STATE_SENSING;//狀態更新為STATE_SENSING
24                 scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);//設置感測器感應時長
25                 mAnyMotionDetector.checkForAnyMotion(); //感測器感應手機有沒有動
26                 break;
27             case STATE_SENSING: //感測器也沒發現手機動,就來最後一發,看GPS有沒有動
28                 mState = STATE_LOCATING;//狀態更新為STATE_LOCATING
29                 scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);//設置GPS觀察時長
30                 mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,
31                         mHandler.getLooper());//GPS開始感應
32                 break;
33             case STATE_LOCATING:  //GPS也發現沒動
34                 cancelSensingAlarmLocked();
35                 cancelLocatingLocked();
36                 mAnyMotionDetector.stop();  //這裡沒有break,直接進入下一個case
37             case STATE_IDLE_MAINTENANCE:
38                 scheduleAlarmLocked(mNextIdleDelay, true);//設置打盹多久後進行呼吸
39                 mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);//更新下次打盹多久後進行呼吸
40                 if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
41                 mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
42                 mState = STATE_IDLE; //噢耶 終於進入了STATE_IDLE
43                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
44                 break;
45             case STATE_IDLE: //打盹完了,呼吸一下就是這裡了
46                 scheduleAlarmLocked(mNextIdlePendingDelay, false);
47                 mState = STATE_IDLE_MAINTENANCE; //狀態更新為STATE_IDLE_MAINTENANCE
48                 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
49                         (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
50                //更新下次呼吸的時間
51                 mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
52                 break;
53         }
54     }

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的指令來強制變換狀態,這樣你就不用乾等著它狀態變化了。

1 adb shell dumpsys battery unplug       //相當於不插電
2 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#

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UI組件 weui-wxss ★1053 - 同微信原生視覺體驗一致的基礎樣式庫 wx-charts ★154 - 微信小程式圖表工具 Wa-UI ★134 - 針對微信小程式整合的一套UI庫 wemark ★93 - 微信小程式Markdown渲染庫 wx-scrollable-tab-view  ...
  • Jcrop 是一個功能強大的 jQuery 圖像裁剪插件,結合後端程式(例如:PHP)可以快速的實現圖片裁剪的功能。 版本: jQuery v1.5.1+ jQuery Jcrop v0.9.12 github 線上實例 實例預覽 jQuery Jcrop 圖像裁剪插件 Hello World 基礎 ...
  • 今天整理筆記,發現在學習javaScript的過程中,遇到過一個在當時看來很棘手的問題,現在特地總結一下,也希望能幫助到曾像我一樣迷惘的初學者。 我還是以一個案例來說明問題,html代碼如下: css代碼如下: js代碼如下: 稍微懂點js的人都知道當我點擊p時,基於事件冒泡機制,會觸發父元素div ...
  • 很久以前,我們在"細說gulp"隨筆中,以壓縮JavaScript為例,詳細地講解瞭如何利用gulp來完成前端自動化。再來短暫回顧下,當時除了藉助gulp之外,我們還利用了第三方gulp插件”gulp-uglify”,來達到壓縮JavaScript文件的目的。今兒,我們的重點就是,自己也來實現一個g... ...
  • 1、 冒泡排序,將數組從小到大排序,即從數組左邊開始相鄰兩個元素比較大小和交換位置。 <script> // 冒泡排序,從小到大。 var arr = [41, 21, 12, 52, 67, 32, 23]; var outer = 0, inner = 0; for (var j = 0; j ...
  • 這個問題很傻很天真,如何正確的添加圖像資源 我有一個名為 back.png 的圖片,直接拷貝到 Finder 的 project 目錄下了。 用如下的語句: roleImageView.image = UIImage(named: "back") 顯示不了圖片。 其實不是語句有問題,是添加圖像的方法 ...
  • 當你在一個Xcode版本上編輯Storyboard並儲存後(比如 8.1)在另一個版本上(比如8.2.1)打開想繼續編輯的時候,有時候會無法打開Storyboard。 所以兩人合作編寫一個 Xcode project 的時候,一定要使用相同的版本。不然以後麻煩多多,會出現很多莫名其妙的問題。 有兩個 ...
  • AToolsActivity.java shake.xml AddressDao.java ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...