自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

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

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

 [[315353]]

一、序

Handler 機(jī)制算是 Android 基本功,面試???。但現(xiàn)在面試,多數(shù)已經(jīng)不會(huì)直接讓你講講 Handler 的機(jī)制,Looper 是如何循環(huán)的,MessageQueue 是如何管理 Message 等,而是基于場(chǎng)景去提問,看看你對(duì) Handler 機(jī)制的掌握是否扎實(shí)。

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

二、IdleHandler

2.1 簡單說說 Handler 機(jī)制

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

Handler 是標(biāo)準(zhǔn)的事件驅(qū)動(dòng)模型,存在一個(gè)消息隊(duì)列 MessageQueue,它是一個(gè)基于消息觸發(fā)時(shí)間的優(yōu)先級(jí)隊(duì)列,還有一個(gè)基于此消息隊(duì)列的事件循環(huán) Looper,Looper 通過循環(huán),不斷的從 MessageQueue 中取出待處理的 Message,再交由對(duì)應(yīng)的事件處理器 Handler/callback 來處理。

其中 MessageQueue 被 Looper 管理,Looper 在構(gòu)造時(shí)同步會(huì)創(chuàng)建 MessageQueue,并利用 ThreadLocal 這種 TLS,將其與當(dāng)前線程綁定。而 App 的主線程在啟動(dòng)時(shí),已經(jīng)構(gòu)造并準(zhǔn)備好主線程的 Looper 對(duì)象,開發(fā)者只需要直接使用即可。

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

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

2.2 IdleHandler 是什么?怎么用?

IdleHandler 說白了,就是 Handler 機(jī)制提供的一種,可以在 Looper 事件循環(huán)的過程中,當(dāng)出現(xiàn)空閑的時(shí)候,允許我們執(zhí)行任務(wù)的一種機(jī)制。

IdleHandler 被定義在 MessageQueue 中,它是一個(gè)接口。 

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

可以看到,定義時(shí)需要實(shí)現(xiàn)其 queueIdle() 方法。同時(shí)返回值為 true 表示是一個(gè)持久的 IdleHandler 會(huì)重復(fù)使用,返回 false 表示是一個(gè)一次性的 IdleHandler。

既然 IdleHandler 被定義在 MessageQueue 中,使用它也需要借助 MessageQueue。在 MessageQueue 中定義了對(duì)應(yīng)的 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 其實(shí)操作的都是 mIdleHandlers,它的類型是一個(gè) ArrayList。

既然 IdleHandler 主要是在 MessageQueue 出現(xiàn)空閑的時(shí)候被執(zhí)行,那么何時(shí)出現(xiàn)空閑?

MessageQueue 是一個(gè)基于消息觸發(fā)時(shí)間的優(yōu)先級(jí)隊(duì)列,所以隊(duì)列出現(xiàn)空閑存在兩種場(chǎng)景。

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

這兩個(gè)場(chǎng)景,都會(huì)嘗試執(zhí)行 IdleHandler。

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

  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.           // 計(jì)算休眠的時(shí)間 
  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() 中關(guān)于 IdleHandler 執(zhí)行的主邏輯:

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

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

需要注意的是,對(duì) mIdleHandler 這個(gè) List 的所有操作,都通過 synchronized 來保證線程安全,這一點(diǎn)無需擔(dān)心。

2.3 IdleHander 是如何保證不進(jìn)入死循環(huán)的?

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

注意現(xiàn)在代碼邏輯還在 MessageQueue.next() 的循環(huán)中,在這個(gè)場(chǎng)景下 IdleHandler 機(jī)制是如何保證不會(huì)進(jìn)入死循環(huán)的?

有些文章會(huì)說 IdleHandler 不會(huì)死循環(huán),是因?yàn)橄麓窝h(huán)調(diào)用了 nativePollOnce() 借助 epoll 機(jī)制進(jìn)入休眠狀態(tài),下次有新消息入隊(duì)的時(shí)候會(huì)重新喚醒,但這是不對(duì)的。

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

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

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

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

實(shí)際不會(huì)死循環(huán)的關(guān)鍵是在于 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,循環(huán)開始前,pendingIdleHandlerCount 的初始值為 -1;
  • Step 2,在 pendingIdleHandlerCount<0 時(shí),才會(huì)通過 mIdleHandlers.size() 賦值。也就是說只有第一次循環(huán)才會(huì)改變 pendingIdleHandlerCount 的值;
  • Step 3,如果 pendingIdleHandlerCount<=0 時(shí),則循環(huán) continus;
  • Step 4,重置 pendingIdleHandlerCount 為 0;

在第二次循環(huán)時(shí),pendingIdleHandlerCount 等于 0,在 Step 2 不會(huì)改變它的值,那么在 Step 3 中會(huì)直接 continus 繼續(xù)下一次循環(huán),此時(shí)沒有機(jī)會(huì)修改 nextPollTimeoutMillis。

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

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

2.4 framework 中如何使用 IdleHander?

到這里基本上就講清楚 IdleHandler 如何使用以及一些細(xì)節(jié),接下來我們來看看,在系統(tǒng)中,有哪些地方會(huì)用到 IdleHandler 機(jī)制。

在 AS 中搜索一下 IdleHandler。 

簡單解釋一下:

  1. ActivityThread.Idler 在 ActivityThread.handleResumeActivity() 中調(diào)用。
  2. ActivityThread.GcIdler 是在內(nèi)存不足時(shí),強(qiáng)行 GC;
  3. Instrumentation.ActivityGoing 在 Activity onCreate() 執(zhí)行前添加;
  4. Instrumentation.Idler 調(diào)用的時(shí)機(jī)就比較多了,是鍵盤相關(guān)的調(diào)用;
  5. TextToSpeechService.SynthThread 是在 TTS 合成完成之后發(fā)送廣播;

有興趣可以自己追一下源碼,這些都是使用的場(chǎng)景,具體用 IdleHander 干什么,還是要看業(yè)務(wù)。

三、一些面試問題

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

下面準(zhǔn)備一些基本的問題,供大家理解。

Q:IdleHandler 有什么用?

  1. IdleHandler 是 Handler 提供的一種在消息隊(duì)列空閑時(shí),執(zhí)行任務(wù)的時(shí)機(jī);
  2. 當(dāng) MessageQueue 當(dāng)前沒有立即需要處理的消息時(shí),會(huì)執(zhí)行 IdleHandler;

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

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

Q:當(dāng) mIdleHanders 一直不為空時(shí),為什么不會(huì)進(jìn)入死循環(huán)?

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

Q:是否可以將一些不重要的啟動(dòng)服務(wù),搬移到 IdleHandler 中去處理?

  1. 不建議;
  2. IdleHandler 的處理時(shí)機(jī)不可控,如果 MessageQueue 一直有待處理的消息,那么 IdleHander 的執(zhí)行時(shí)機(jī)會(huì)很靠后;

Q:IdleHandler 的 queueIdle() 運(yùn)行在那個(gè)線程?

  1. 這是陷進(jìn)問題,queueIdle() 運(yùn)行的線程,只和當(dāng)前 MessageQueue 的 Looper 所在的線程有關(guān);
  2. 子線程一樣可以構(gòu)造 Looper,并添加 IdleHandler。

四、小結(jié)時(shí)刻

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

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

本文就到這里,對(duì)你有幫助嗎?

 

責(zé)任編輯:武曉燕 來源: 承香墨影
相關(guān)推薦

2024-01-15 06:42:00

高并發(fā)熱點(diǎn)賬戶數(shù)據(jù)庫

2022-05-23 08:43:02

BigIntJavaScript內(nèi)置對(duì)象

2020-07-03 14:19:01

Kafka日志存儲(chǔ)

2021-12-13 11:54:13

SetEs6接口

2021-01-18 08:25:44

Zookeeper面試分布式

2021-01-19 09:11:35

Zookeeper面試分布式

2022-03-21 09:05:18

volatileCPUJava

2020-09-04 07:33:12

Redis HashMap 數(shù)據(jù)

2019-04-29 14:59:41

Tomcat系統(tǒng)架構(gòu)

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機(jī)制

2023-09-12 14:56:13

MyBatis緩存機(jī)制

2021-06-30 07:19:36

React事件機(jī)制

2022-05-15 21:52:04

typeTypeScriptinterface

2021-07-05 07:55:11

String[]byte轉(zhuǎn)換

2024-11-14 14:53:04

2020-05-21 10:02:51

Explain SQL優(yōu)化

2020-07-28 08:59:22

JavahreadLocal面試
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)