[Android面試系列]一句話講清楚Android消息機(jī)制

事件起因

招聘季,面試了一些Android兄弟,發(fā)現(xiàn)對(duì)基礎(chǔ)概念吃的不透.
遂成此文,打算出個(gè)一句話系列,講清楚一些android的基本概念.
讓大家在面試的時(shí)候能找到心儀的工作,并且不被面試官鄙視.....
此篇內(nèi)容主要將的Android消息機(jī)制相關(guān)的內(nèi)容.

正式開(kāi)始

Handler,Looper機(jī)制是android中的消息系統(tǒng),兩個(gè)線程間傳遞消息,(進(jìn)程間也可以使用Messenger傳遞Message).

主要涉及的類(lèi)

  • Looper
    負(fù)責(zé)從MessageQueue中獲取消息及將消息分發(fā)到對(duì)應(yīng)的Handler.
  • Thread
    任務(wù)執(zhí)行的線程環(huán)境.
  • ThreadLocal
    負(fù)責(zé)保存Thread關(guān)聯(lián)的Looper.
  • ThreadLocalMap
    ThreadLocal中具體保存數(shù)據(jù)的類(lèi).內(nèi)部使用Entry數(shù)組.來(lái)保存數(shù)據(jù).
  • MessageQueue
    保存Message的容器,內(nèi)部實(shí)現(xiàn)為鏈表.
  • Handler
    MessageQueue中插入消息,以及對(duì)收到的Looper分發(fā)的消息的處理.
  • Message
    消息載體.主要如下屬性
    • callback
      任務(wù)的runnable對(duì)象
    • when
      message需要被dispatch的執(zhí)行時(shí)間
    • obj
      message中傳遞的對(duì)象.

進(jìn)行線程間通訊的主要流程如下

  1. 創(chuàng)建Thread
  2. Thread.run方法中依次調(diào)用Looper.prepare,Looper.loop.
    • Looper.prepare
      主要用來(lái)將Looper關(guān)聯(lián)到當(dāng)前thread的threadLocal.
      執(zhí)行Looper.prepare時(shí),會(huì)判斷當(dāng)前線程如果沒(méi)有關(guān)聯(lián)looper時(shí),
      就會(huì)調(diào)用threadLocal.set()方法向threadLocal中添加一個(gè)創(chuàng)建的Looper對(duì)象.
    • Looper.loop
      主要用來(lái)循環(huán)從LooperMessageQueue中取對(duì)象.
      Looper.loop方法內(nèi)部為一個(gè)無(wú)限循環(huán)的for循環(huán).
      在for循環(huán)中通過(guò)調(diào)用messqueue.next()方法來(lái)獲取到隊(duì)列中的message.
  3. 創(chuàng)建Handler關(guān)聯(lián)到該Looper
  4. 通過(guò)創(chuàng)建的Handler.sendMessage方法將message傳遞到創(chuàng)建該Handler所在Looper中的MessageQueue.
  5. MessageQueue在判斷其中有message.when小于當(dāng)前時(shí)間的message時(shí),將該消息返回給Looper.loop
  6. Looper.loop在拿到message后,調(diào)用message.target所關(guān)聯(lián)的Handler對(duì)象的dispatch方法,
    從而實(shí)現(xiàn)了在創(chuàng)建Handler所在線程中執(zhí)行消息的能力.
    如果創(chuàng)建Handler跟通過(guò)該Handler發(fā)送Message不在同一個(gè)線程,則也就實(shí)現(xiàn)了線程間通訊的能力.

流程中涉及到的一些技術(shù)點(diǎn)

MessageQueue保存在哪

MessageQueue保存在Looper中,Looper保存在ThreadLocal中,
ThreadLocal保存在當(dāng)前Thread中的ThreadLocal.ThreadLocalMap中.

MessageQueue中的保存Message的數(shù)據(jù)結(jié)構(gòu)

MessageQueue對(duì)象中保存著Message對(duì)象,MessageQueue中保存的Message對(duì)象是以鏈表數(shù)據(jù)結(jié)構(gòu)保存的.
通過(guò)Message.next指向下一個(gè)Message對(duì)象.

Message對(duì)象的緩存機(jī)制

對(duì)象的頻繁創(chuàng)建銷(xiāo)毀,會(huì)消耗cpu的性能,而消息傳遞又會(huì)頻繁創(chuàng)建消息對(duì)象,所以message對(duì)象是會(huì)被緩存起來(lái)的.
Message對(duì)象通過(guò)Message.obtain()可以獲得,.obtain()方法可以看作是一個(gè)工廠方法,
Message內(nèi)部維護(hù)著一個(gè)緩沖區(qū),緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)也可看做時(shí)鏈表形式.
通過(guò)Message.next方法指向下一個(gè)可被復(fù)用的Message.
緩存區(qū)的大小為50,所以最多可緩存50個(gè)Message對(duì)象.

ThreadLocal是什么,如何實(shí)現(xiàn)的

ThreadLocal是一種保存變量的線程安全的類(lèi).
在多線程環(huán)境下能安全使用變量,最好的方式是在每個(gè)線程中定義一個(gè)local變量.
這樣對(duì)線程中的local變量做修改就只會(huì)影響當(dāng)前線程中的該變量,因此變量也就是線程安全的.
這種保證變量線程安全的思想,實(shí)際上就是ThreadLocal的實(shí)現(xiàn).

Threadlocal在調(diào)用threadLocal.get方法時(shí),會(huì)獲取當(dāng)前thread的threadLocalMap.
threadLocalMap是thread中的一個(gè)屬性.
第一次調(diào)用ThreadLocal.get()方法時(shí),會(huì)先判斷當(dāng)前線程對(duì)應(yīng)的threadlocalMap是否被創(chuàng)建了,
如果沒(méi)創(chuàng)建則會(huì)創(chuàng)建ThreadLocalMap,并把該對(duì)象賦值給thread.sThreadLocal對(duì)象.后續(xù)再獲取當(dāng)前thread的threadLocalMap時(shí),就會(huì)取該賦值對(duì)象.
ThreadLocalMap就是用來(lái)保存線程中需要保存的變量的對(duì)象了.

因?yàn)閠hreadLocalMap是賦值給當(dāng)前thread的,屬于thread的內(nèi)部變量,
所以每個(gè)線程的threadlocalMap就都是不同的對(duì)象,也就是上面說(shuō)的threadlocal是線程安全的原因了.

ThreadLocalMap內(nèi)部實(shí)際上是一個(gè)Entry[],用來(lái)保存Entry對(duì)象的數(shù)組.
Entry對(duì)象是繼承weakReference的,其中Entry的key為T(mén)hreadLocal對(duì)象,value為threadLocal需要保存的變量值.

調(diào)用ThreadLocal.set方法時(shí),會(huì)向threadLocalMap中添加一個(gè)Entry對(duì)象.
調(diào)用get方法時(shí),是通過(guò)將調(diào)用的threadLocal對(duì)象本身作為key,來(lái)遍歷threadLocalMap數(shù)組.
當(dāng)threadLocal等于Entry[]中的key時(shí),則返回該Entry中的value.

Looper中的ThreadLocal對(duì)象為啥是static的?

其實(shí)threadLocal對(duì)象是否定義為static對(duì)ThreadLocal類(lèi)本身的作用來(lái)講是沒(méi)影響的.
但是因?yàn)長(zhǎng)ooper.prepare方法是定義為static的,而prepare中又需要對(duì)threadlocal進(jìn)行訪問(wèn),所以Looper中的threadLocal對(duì)象也就必須定義為static的了.

ThreadLocal中為啥只能保存一個(gè)Looper對(duì)象

threadLocalMap可以保存無(wú)線多的數(shù)據(jù),但是每個(gè)線程只能關(guān)聯(lián)一個(gè)Looper對(duì)象.
因?yàn)?code>threadLocal中會(huì)保存當(dāng)前線程關(guān)聯(lián)的Looper對(duì)象,這個(gè)限制是Looper做的.

Looper是如何從MessageQueue中獲取將要分發(fā)的message的

通過(guò)Looper.loop方法來(lái)獲取并分發(fā)message.
Looper.loop內(nèi)部為一個(gè)無(wú)限循環(huán)的for循環(huán).在for循環(huán)中通過(guò)調(diào)用messqueue.next()方法來(lái)獲取到隊(duì)列中的message.
messagequeue.next()方法內(nèi)部會(huì)調(diào)用nativePollOnce方法,該方法會(huì)阻塞線程.
該方法的作用簡(jiǎn)單說(shuō),就是當(dāng)消息隊(duì)列中沒(méi)消息時(shí),阻塞掉當(dāng)前執(zhí)行的線程.避免過(guò)度的cpu消耗.
關(guān)于nativePollOnce阻塞線程的解釋
當(dāng)消息隊(duì)列中有消息時(shí),messagequeue.next()方法內(nèi)部會(huì)判斷現(xiàn)有的消息鏈表中的消息的message.when屬性是否小于當(dāng)前時(shí)間.
如果小于當(dāng)前時(shí)間,則next方法會(huì)將該條消息返回給loop中調(diào)用next函數(shù)的地方,這樣looper就拿到了將要dispatch的消息了.
所以是通過(guò)Messagequeue來(lái)判斷message是否能被looper進(jìn)行分發(fā)的.

Looper是如何分發(fā)message的?

當(dāng)looper.loop方法中獲取到message時(shí),會(huì)調(diào)用message.target.dispatch方法.
其中message.target屬性是在Handler內(nèi)部調(diào)用Handler.enqueueMessage方法時(shí),將當(dāng)前調(diào)用方法的handler對(duì)象設(shè)置到Message中的.
通過(guò)message.target.dispatchMessage方法,將該message的執(zhí)行環(huán)境切換到了該handler對(duì)應(yīng)的thread中.
handler可以實(shí)現(xiàn)handleMessage方法來(lái)處理消息.
調(diào)用完handler.dispatchMessage方法后,則會(huì)將該消息通過(guò)recycleUnchecked方法,對(duì)message進(jìn)行回收.
Message.recycleUnchecked方法中會(huì)重置該message對(duì)象(將message對(duì)象相關(guān)屬性置空),并將該對(duì)象添加到Message緩存區(qū)中.
加入緩存去的過(guò)程也就是將該對(duì)象加入到緩存區(qū)鏈表中.加入緩沖區(qū)的message后面可通過(guò)Message.obtain方法來(lái)獲取.

結(jié)尾

這文章寫(xiě)了一天吧,希望能幫助到需要面試Android的同學(xué).
喜歡的麻煩點(diǎn)個(gè)贊,關(guān)注下.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容