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

事件起因

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

正式開始

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

主要涉及的類

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

進行線程間通訊的主要流程如下

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

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

MessageQueue保存在哪

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

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

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

Message對象的緩存機制

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

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

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

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

因為threadLocalMap是賦值給當(dāng)前thread的,屬于thread的內(nèi)部變量,
所以每個線程的threadlocalMap就都是不同的對象,也就是上面說的threadlocal是線程安全的原因了.

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

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

Looper中的ThreadLocal對象為啥是static的?

其實threadLocal對象是否定義為static對ThreadLocal類本身的作用來講是沒影響的.
但是因為Looper.prepare方法是定義為static的,而prepare中又需要對threadlocal進行訪問,所以Looper中的threadLocal對象也就必須定義為static的了.

ThreadLocal中為啥只能保存一個Looper對象

threadLocalMap可以保存無線多的數(shù)據(jù),但是每個線程只能關(guān)聯(lián)一個Looper對象.
因為threadLocal中會保存當(dāng)前線程關(guān)聯(lián)的Looper對象,這個限制是Looper做的.

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

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

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

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

結(jié)尾

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

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

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

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