成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

面試官:“看你簡歷上寫熟悉Handler機制,那聊聊IdleHandler吧?”

新聞
IdleHandler 是 Handler 提供的一種在消息隊列空閑時,執行任務的時機。但它執行的時機依賴消息隊列的情況,那么如果 MessageQueue 一直有待執行的消息時,IdleHandler 就一直得不到執行,也就是它的執行時機是不可控的,不適合執行一些對時機要求比較高的任務。

 [[315353]]

一、序

Handler 機制算是 Android 基本功,面試常客。但現在面試,多數已經不會直接讓你講講 Handler 的機制,Looper 是如何循環的,MessageQueue 是如何管理 Message 等,而是基于場景去提問,看看你對 Handler 機制的掌握是否扎實。

本文就來聊聊 Handler 中的 IdleHandler,這個我們比較少用的功能。它能干什么?怎么使用?有什么合適的使用場景?哪些不是合適的使用場景?在 Android Framework 中有哪些地方用到了它?

二、IdleHandler

2.1 簡單說說 Handler 機制

在說 IdleHandler 之前,先簡單了解一下 Handler 機制。

Handler 是標準的事件驅動模型,存在一個消息隊列 MessageQueue,它是一個基于消息觸發時間的優先級隊列,還有一個基于此消息隊列的事件循環 Looper,Looper 通過循環,不斷的從 MessageQueue 中取出待處理的 Message,再交由對應的事件處理器 Handler/callback 來處理。

其中 MessageQueue 被 Looper 管理,Looper 在構造時同步會創建 MessageQueue,并利用 ThreadLocal 這種 TLS,將其與當前線程綁定。而 App 的主線程在啟動時,已經構造并準備好主線程的 Looper 對象,開發者只需要直接使用即可。

Handler 類中封裝了大部分「Handler 機制」對外的操作接口,可以通過它的 send/post 相關的方法,向消息隊列 MessageQueue 中插入一條 Message。在 Looper 循環中,又會不斷的從 MessageQueue 取出下一條待處理的 Message 進行處理。

IdleHandler 使用相關的邏輯,就在 MessageQueue 取消息的 next() 方法中。

2.2 IdleHandler 是什么?怎么用?

IdleHandler 說白了,就是 Handler 機制提供的一種,可以在 Looper 事件循環的過程中,當出現空閑的時候,允許我們執行任務的一種機制。

IdleHandler 被定義在 MessageQueue 中,它是一個接口。 

  1. // MessageQueue.java 
  2. public static interface IdleHandler { 
  3.   boolean queueIdle(); 

可以看到,定義時需要實現其 queueIdle() 方法。同時返回值為 true 表示是一個持久的 IdleHandler 會重復使用,返回 false 表示是一個一次性的 IdleHandler。

既然 IdleHandler 被定義在 MessageQueue 中,使用它也需要借助 MessageQueue。在 MessageQueue 中定義了對應的 add 和 remove 方法。 

  1. // MessageQueue.java 
  2. public void addIdleHandler(@NonNull IdleHandler handler) { 
  3.     // ... 
  4.   synchronized (this) { 
  5.     mIdleHandlers.add(handler); 
  6.   } 
  7. public void removeIdleHandler(@NonNull IdleHandler handler) { 
  8.   synchronized (this) { 
  9.     mIdleHandlers.remove(handler); 
  10.   } 

可以看到 add 或 remove 其實操作的都是 mIdleHandlers,它的類型是一個 ArrayList。

既然 IdleHandler 主要是在 MessageQueue 出現空閑的時候被執行,那么何時出現空閑?

MessageQueue 是一個基于消息觸發時間的優先級隊列,所以隊列出現空閑存在兩種場景。

  1. MessageQueue 為空,沒有消息;
  2. MessageQueue 中最近需要處理的消息,是一個延遲消息(when>currentTime),需要滯后執行;

這兩個場景,都會嘗試執行 IdleHandler。

處理 IdleHandler 的場景,就在 Message.next() 這個獲取消息隊列下一個待執行消息的方法中,我們跟一下具體的邏輯。 

  1. Message next() { 
  2.     // ... 
  3.   int pendingIdleHandlerCount = -1; 
  4.   int nextPollTimeoutMillis = 0; 
  5.   for (;;) { 
  6.     nativePollOnce(ptr, nextPollTimeoutMillis); 
  7.  
  8.     synchronized (this) { 
  9.       // ... 
  10.       if (msg != null) { 
  11.         if (now < msg.when) { 
  12.           // 計算休眠的時間 
  13.           nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); 
  14.         } else { 
  15.           // Other code 
  16.           // 找到消息處理后返回 
  17.           return msg; 
  18.         } 
  19.       } else { 
  20.         // 沒有更多的消息 
  21.         nextPollTimeoutMillis = -1; 
  22.       } 
  23.        
  24.       if (pendingIdleHandlerCount < 0 
  25.           && (mMessages == null || now < mMessages.when)) { 
  26.         pendingIdleHandlerCount = mIdleHandlers.size(); 
  27.       } 
  28.       if (pendingIdleHandlerCount <= 0) { 
  29.         mBlocked = true
  30.         continue
  31.       } 
  32.  
  33.       if (mPendingIdleHandlers == null) { 
  34.         mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; 
  35.       } 
  36.       mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); 
  37.     } 
  38.  
  39.     for (int i = 0; i < pendingIdleHandlerCount; i++) { 
  40.       final IdleHandler idler = mPendingIdleHandlers[i]; 
  41.       mPendingIdleHandlers[i] = null
  42.  
  43.       boolean keep = false
  44.       try { 
  45.         keep = idler.queueIdle(); 
  46.       } catch (Throwable t) { 
  47.         Log.wtf(TAG, "IdleHandler threw exception", t); 
  48.       } 
  49.  
  50.       if (!keep) { 
  51.         synchronized (this) { 
  52.           mIdleHandlers.remove(idler); 
  53.         } 
  54.       } 
  55.     } 
  56.  
  57.     pendingIdleHandlerCount = 0; 
  58.     nextPollTimeoutMillis = 0; 
  59.   } 

我們先解釋一下 next() 中關于 IdleHandler 執行的主邏輯:

  1. 準備執行 IdleHandler 時,說明當前待執行的消息為 null,或者這條消息的執行時間未到;
  2. 當 pendingIdleHandlerCount < 0 時,根據 mIdleHandlers.size() 賦值給 pendingIdleHandlerCount,它是后期循環的基礎;
  3. 將 mIdleHandlers 中的 IdleHandler 拷貝到 mPendingIdleHandlers 數組中,這個數組是臨時的,之后進入 for 循環;
  4. 循環中從數組中取出 IdleHandler,并調用其 queueIdle() 記錄返回值存到 keep 中;
  5. 當 keep 為 false 時,從 mIdleHandler 中移除當前循環的 IdleHandler,反之則保留;

可以看到 IdleHandler 機制中,最核心的就是在 next() 中,當隊列空閑的時候,循環 mIdleHandler 中記錄的 IdleHandler 對象,如果其 queueIdle() 返回值為 false 時,將其從 mIdleHander 中移除。

需要注意的是,對 mIdleHandler 這個 List 的所有操作,都通過 synchronized 來保證線程安全,這一點無需擔心。

2.3 IdleHander 是如何保證不進入死循環的?

當隊列空閑時,會循環執行一遍 mIdleHandlers 數組并執行 IdleHandler.queueIdle() 方法。而如果數組中有一些 IdleHander 的 queueIdle() 返回了 true,則會保留在 mIdleHanders 數組中,下次依然會再執行一遍。

注意現在代碼邏輯還在 MessageQueue.next() 的循環中,在這個場景下 IdleHandler 機制是如何保證不會進入死循環的?

有些文章會說 IdleHandler 不會死循環,是因為下次循環調用了 nativePollOnce() 借助 epoll 機制進入休眠狀態,下次有新消息入隊的時候會重新喚醒,但這是不對的。

注意看前面 next() 中的代碼,在方法的末尾會重置 pendingIdleHandlerCount 和 nextPollTimeoutMillis。 

  1. Message next() { 
  2.     // ... 
  3.   int pendingIdleHandlerCount = -1; 
  4.   int nextPollTimeoutMillis = 0; 
  5.   for (;;) { 
  6.     nativePollOnce(ptr, nextPollTimeoutMillis); 
  7.     // ... 
  8.     // 循環執行 mIdleHandlers 
  9.     // ... 
  10.     pendingIdleHandlerCount = 0; 
  11.     nextPollTimeoutMillis = 0; 
  12.   } 

nextPollTimeoutMillis 決定了下次進入 nativePollOnce() 超時的時間,它傳遞 0 的時候等于不會進入休眠,所以說 natievPollOnce() 進入休眠所以不會死循環是不對的。

這很好理解,畢竟 IdleHandler.queueIdle() 運行在主線程,它執行的時間是不可控的,那么 MessageQueue 中的消息情況可能會變化,所以需要再處理一遍。

實際不會死循環的關鍵是在于 pendingIdleHandlerCount,我們看看下面的代碼。 

  1. Message next() { 
  2.     // ... 
  3.   // Step 1 
  4.   int pendingIdleHandlerCount = -1; 
  5.   int nextPollTimeoutMillis = 0; 
  6.   for (;;) { 
  7.     nativePollOnce(ptr, nextPollTimeoutMillis); 
  8.  
  9.     synchronized (this) { 
  10.       // ... 
  11.       // Step 2 
  12.       if (pendingIdleHandlerCount < 0 
  13.           && (mMessages == null || now < mMessages.when)) { 
  14.         pendingIdleHandlerCount = mIdleHandlers.size(); 
  15.       } 
  16.         // Step 3 
  17.       if (pendingIdleHandlerCount <= 0) { 
  18.         mBlocked = true
  19.         continue
  20.       } 
  21.       // ... 
  22.     } 
  23.         // Step 4 
  24.     pendingIdleHandlerCount = 0; 
  25.     nextPollTimeoutMillis = 0; 
  26.   } 

我們梳理一下:

  • Step 1,循環開始前,pendingIdleHandlerCount 的初始值為 -1;
  • Step 2,在 pendingIdleHandlerCount<0 時,才會通過 mIdleHandlers.size() 賦值。也就是說只有第一次循環才會改變 pendingIdleHandlerCount 的值;
  • Step 3,如果 pendingIdleHandlerCount<=0 時,則循環 continus;
  • Step 4,重置 pendingIdleHandlerCount 為 0;

在第二次循環時,pendingIdleHandlerCount 等于 0,在 Step 2 不會改變它的值,那么在 Step 3 中會直接 continus 繼續下一次循環,此時沒有機會修改 nextPollTimeoutMillis。

那么 nextPollTimeoutMillis 有兩種可能:-1 或者下次喚醒的等待間隔時間,在執行到nativePollOnce() 時就會進入休眠,等待再次被喚醒。

下次喚醒時,mMessage 必然會有一個待執行的 Message,則 MessageQueue.next() 返回到 Looper.loop() 的循環中,分發處理這個 Message,之后又是一輪新的 next() 中去循環。

2.4 framework 中如何使用 IdleHander?

到這里基本上就講清楚 IdleHandler 如何使用以及一些細節,接下來我們來看看,在系統中,有哪些地方會用到 IdleHandler 機制。

在 AS 中搜索一下 IdleHandler。 

簡單解釋一下:

  1. ActivityThread.Idler 在 ActivityThread.handleResumeActivity() 中調用。
  2. ActivityThread.GcIdler 是在內存不足時,強行 GC;
  3. Instrumentation.ActivityGoing 在 Activity onCreate() 執行前添加;
  4. Instrumentation.Idler 調用的時機就比較多了,是鍵盤相關的調用;
  5. TextToSpeechService.SynthThread 是在 TTS 合成完成之后發送廣播;

有興趣可以自己追一下源碼,這些都是使用的場景,具體用 IdleHander 干什么,還是要看業務。

三、一些面試問題

到這里我們就講清楚 IdleHandler 干什么?怎么用?有什么問題?以及使用中一些原理的講解。

下面準備一些基本的問題,供大家理解。

Q:IdleHandler 有什么用?

  1. IdleHandler 是 Handler 提供的一種在消息隊列空閑時,執行任務的時機;
  2. 當 MessageQueue 當前沒有立即需要處理的消息時,會執行 IdleHandler;

Q:MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成對使用?

  1. 不是必須;
  2. IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;

Q:當 mIdleHanders 一直不為空時,為什么不會進入死循環?

  1. 只有在 pendingIdleHandlerCount 為 -1 時,才會嘗試執行 mIdleHander;
  2. pendingIdlehanderCount 在 next() 中初始時為 -1,執行一遍后被置為 0,所以不會重復執行;

Q:是否可以將一些不重要的啟動服務,搬移到 IdleHandler 中去處理?

  1. 不建議;
  2. IdleHandler 的處理時機不可控,如果 MessageQueue 一直有待處理的消息,那么 IdleHander 的執行時機會很靠后;

Q:IdleHandler 的 queueIdle() 運行在那個線程?

  1. 這是陷進問題,queueIdle() 運行的線程,只和當前 MessageQueue 的 Looper 所在的線程有關;
  2. 子線程一樣可以構造 Looper,并添加 IdleHandler。

四、小結時刻

到這里就把 IdleHandler 的使用和原理說清楚了。

IdleHandler 是 Handler 提供的一種在消息隊列空閑時,執行任務的時機。但它執行的時機依賴消息隊列的情況,那么如果 MessageQueue 一直有待執行的消息時,IdleHandler 就一直得不到執行,也就是它的執行時機是不可控的,不適合執行一些對時機要求比較高的任務。

本文就到這里,對你有幫助嗎?

 

責任編輯:武曉燕 來源: 承香墨影
相關推薦

2024-01-15 06:42:00

高并發熱點賬戶數據庫

2022-05-23 08:43:02

BigIntJavaScript內置對象

2021-01-19 09:11:35

Zookeeper面試分布式

2021-01-18 08:25:44

Zookeeper面試分布式

2020-07-03 14:19:01

Kafka日志存儲

2021-12-13 11:54:13

SetEs6接口

2020-09-04 07:33:12

Redis HashMap 數據

2022-03-21 09:05:18

volatileCPUJava

2019-04-29 14:59:41

Tomcat系統架構

2018-04-27 14:46:07

面試簡歷程序員

2023-07-13 08:19:30

HaspMapRedis元素

2024-07-25 18:20:03

2021-06-29 09:47:34

ReactSetState機制

2023-09-12 14:56:13

MyBatis緩存機制

2021-06-30 07:19:36

React事件機制

2021-07-05 07:55:11

String[]byte轉換

2025-06-18 08:20:00

Redis數據庫線程

2024-11-14 14:53:04

2022-05-15 21:52:04

typeTypeScriptinterface

2020-07-28 08:59:22

JavahreadLocal面試
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 婷婷久久综合 | 久久精品小视频 | 国产欧美日韩精品一区二区三区 | 在线国产一区 | 国产高清视频一区二区 | 欧美一区二区三区在线播放 | 国产精品精品 | 欧美日韩专区 | 99精品视频在线 | 羞羞视频免费在线观看 | 99久久精品国产一区二区三区 | 久久久精品一区二区三区 | 在线天堂免费中文字幕视频 | 黄色小视频大全 | gogo肉体亚洲高清在线视 | 欧美成人影院在线 | 伊人狠狠操 | 欧美成人免费在线 | 国产性网 | 亚洲黄色在线免费观看 | 国产视频一区二区三区四区五区 | h小视频| 久久久久黄 | 国产一区二区三区久久久久久久久 | 在线看亚洲 | 亚洲欧美在线一区 | 日本一区二区三区免费观看 | 亚洲一区二区三区免费 | 在线观看视频一区二区三区 | 黄网免费看 | 免费一区 | 免费中文字幕日韩欧美 | 三级高清 | 亚洲激精日韩激精欧美精品 | 免费黄视频网站 | 国产xxxx岁13xxxxhd| 欧美综合在线观看 | 日韩一区二区三区精品 | 另类一区 | 亚洲精品第一国产综合野 | 一级做a爰片性色毛片视频停止 |