Android消息機(jī)制(一):概述設(shè)計(jì)架構(gòu)

本系列文章將分N篇介紹Android中的消息機(jī)制。

  1. 概述和設(shè)計(jì)架構(gòu)
  2. Message和MessageQueue
  3. Looper
  4. Handler
  5. Handler使用實(shí)戰(zhàn)
  6. Handler引起的內(nèi)存溢出
  7. ThreadLocal

Android的應(yīng)用程序和Windows應(yīng)用程序一樣,都是由消息驅(qū)動(dòng)的。在Android操作系統(tǒng)中,谷歌也實(shí)現(xiàn)了消息循環(huán)處理機(jī)制。

相關(guān)概念

學(xué)習(xí)Android的消息機(jī)制,有幾個(gè)設(shè)計(jì)概念我們必須了解:

  1. 消息:Message
    消息(Message)代表一個(gè)行為(what)或者一串動(dòng)作(Runnable),每一個(gè)消息在加入消息隊(duì)列時(shí),都有明確的目標(biāo)(Handler)。
  2. 消息隊(duì)列:MessageQueue
    以隊(duì)列的形式對(duì)外提供插入和刪除的工作,其內(nèi)部結(jié)構(gòu)是以鏈表的形式存儲(chǔ)消息的。
  3. Looper
    Looper是循環(huán)的意思,它負(fù)責(zé)從消息隊(duì)列中循環(huán)的取出消息然后把消息交給目標(biāo)(Handler)處理。
  4. Handler
    消息的真正處理者,具備獲取消息、發(fā)送消息、處理消息、移除消息等功能。
  5. 線程
    線程,CPU調(diào)度資源的基本單位。Android中的消息機(jī)制也是基于線程中的概念。
  6. ThreadLocal
    可以理解為ThreadLocalData,ThreadLocal的作用是提供線程內(nèi)的局部變量(TLS),這種變量在線程的生命周期內(nèi)起作用,每一個(gè)線程有他自己所屬的值(線程隔離)。

5、6為牽涉到的概念,不是本文重點(diǎn)。會(huì)另起文章討論

平時(shí)我們最常使用的就是Message與Handler了,如果使用過(guò)HandlerThread或者自己實(shí)現(xiàn)類似HandlerThread的東西可能還會(huì)接觸到Looper,而MessageQueue是Looper內(nèi)部使用的,對(duì)于標(biāo)準(zhǔn)的SDK,我們是無(wú)法實(shí)例化并使用的(構(gòu)造函數(shù)是包可見(jiàn)性)。

我們平時(shí)接觸到的Looper、Message、Handler都是用JAVA實(shí)現(xiàn)的,Android是一個(gè)基于Linux的系統(tǒng),底層用C、C++實(shí)現(xiàn)的,而且還有NDK的存在,Android消息驅(qū)動(dòng)的模型為了消息的及時(shí)性、高效性,在Native層也設(shè)計(jì)了Java層對(duì)應(yīng)的類如Looper、MessageQueue等。

handle機(jī)制.jpg
handle機(jī)制.jpg

他們?nèi)绾螀f(xié)作

Handler、MessageQueue、Looper如何協(xié)作

一句話總結(jié)為:Looper不斷從MessageQueue中取出一個(gè)Message,然后交給其對(duì)應(yīng)的Handler處理。

他們之間的類圖如下:


Handler、Looper、Message、MessageQueue類圖
Handler、Looper、Message、MessageQueue類圖

從上文兩張圖中我們可以得到以下結(jié)論:

  1. Looper依賴于MessageQueue和Thread,每個(gè)Thread只對(duì)應(yīng)一個(gè)Looper,每個(gè)Looper只對(duì)應(yīng)一個(gè)MessageQueue(一對(duì)一)。
  2. MessageQueue依賴于Message,每個(gè)MessageQueue中有N個(gè)待處理消息(一對(duì)N)。
  3. Message依賴于Handler來(lái)進(jìn)行處理,每個(gè)Message有且僅有一個(gè)對(duì)應(yīng)的Handler。(一對(duì)一)
  4. Handler中持有Looper和MessageQueue的引用,可直接對(duì)其進(jìn)行操作。

還有一點(diǎn)要說(shuō)明的是:普通的線程是沒(méi)有l(wèi)ooper的,如果需要looper對(duì)象,那么必須要先調(diào)用Looper.prepare()方法,而且一個(gè)線程只能有一個(gè)looper。調(diào)用完以后,此線程就成為了所謂的LooperThread,若在當(dāng)前LooperThread中創(chuàng)建Handler對(duì)象,那么此Handler會(huì)自動(dòng)關(guān)聯(lián)到當(dāng)前線程的looper對(duì)象,也就是擁有l(wèi)ooper的引用。
下面是官方給出的LooperThread最標(biāo)準(zhǔn)的用法。

class LooperThread extends Thread {
public Handler mHandler;

public void run() {
Looper.prepare();

mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};

Looper.loop();
}

為什么我們需要這樣的消息處理機(jī)制

  1. 不阻塞主線程
    Android應(yīng)用程序啟動(dòng)時(shí),系統(tǒng)會(huì)創(chuàng)建一個(gè)主線程,負(fù)責(zé)與UI組件(widget、view)進(jìn)行交互,比如控制UI界面界面顯示、更新等;分發(fā)事件給UI界面處理,比如按鍵事件、觸摸事件、屏幕繪圖事件等,因此,Android主線程也稱為UI線程。
    由此可知,UI線程只能處理一些簡(jiǎn)單的、短暫的操作,如果要執(zhí)行繁重的任務(wù)或者耗時(shí)很長(zhǎng)的操作,比如訪問(wèn)網(wǎng)絡(luò)、數(shù)據(jù)庫(kù)、下載等,這種單線程模型會(huì)導(dǎo)致線程運(yùn)行性能大大降低,甚至阻塞UI線程,如果被阻塞超過(guò)5秒,系統(tǒng)會(huì)提示應(yīng)用程序無(wú)相應(yīng)對(duì)話框,縮寫(xiě)為ANR,導(dǎo)致退出整個(gè)應(yīng)用程序或者短暫殺死應(yīng)用程序。
    Android系統(tǒng)將大部分耗時(shí)、繁重任務(wù)交給子線程完成,不會(huì)在主線程中完成。
  2. 并發(fā)程序設(shè)計(jì)的有序性
    單線程模型的UI主線程也是不安全的,會(huì)造成不可確定的結(jié)果。
    線程不安全簡(jiǎn)單理解為:多線程訪問(wèn)資源時(shí),有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù)造成數(shù)據(jù)不一致。比如,A工作線程(也稱為子線程)訪問(wèn)某個(gè)公共UI資源,B工作線程在某個(gè)時(shí)候也訪問(wèn)了該公共資源,當(dāng)B線程正訪問(wèn)時(shí),公共資源的屬性已經(jīng)被A改變了,這樣B得到的結(jié)果不是所需要的的,造成了數(shù)據(jù)不一致的混亂情況。
    線程安全簡(jiǎn)單理解為:當(dāng)一個(gè)線程訪問(wèn)功能資源時(shí),對(duì)該資源進(jìn)程了保護(hù),比如加了鎖機(jī)制,當(dāng)前線程在沒(méi)有訪問(wèn)結(jié)束釋放鎖之前,其他線程只能等待直到釋放鎖才能訪問(wèn),這樣的線程就是安全的。
    Android只允許主線程更新UI界面,子線程處理后的結(jié)果無(wú)法和主線程交互,即無(wú)法直接訪問(wèn)主線程,這就要用到Handler機(jī)制來(lái)解決此問(wèn)題?;贖andler機(jī)制,在子線程先獲得Handler對(duì)象,該對(duì)象將數(shù)據(jù)發(fā)送到主線程消息隊(duì)列,主線程通過(guò)Loop循環(huán)獲取消息交給Handler處理。

是如何完成跨線程通信的

Handler發(fā)送消息后添加消息到消息隊(duì)列,然后消息在恰當(dāng)時(shí)候出列,都是由Handler來(lái)執(zhí)行,那么是如何完成跨線程通信的?
這里就牽涉到了Linux系統(tǒng)的跨線程通信的知識(shí),Android中采用的是Linux中的管道通信。
Looper是通過(guò)管道(pipe)實(shí)現(xiàn)的。
關(guān)于管道,簡(jiǎn)單來(lái)說(shuō),管道就是一個(gè)文件。
在管道的兩端,分別是兩個(gè)打開(kāi)文件文件描述符,這兩個(gè)打開(kāi)文件描述符都是對(duì)應(yīng)同一個(gè)文件,其中一個(gè)是用來(lái)讀的,別一個(gè)是用來(lái)寫(xiě)的。
一般的使用方式就是,一個(gè)線程通過(guò)讀文件描述符中來(lái)讀管道的內(nèi)容,當(dāng)管道沒(méi)有內(nèi)容時(shí),這個(gè)線程就會(huì)進(jìn)入等待狀態(tài),而另外一個(gè)線程通過(guò)寫(xiě)文件描述符來(lái)向管道中寫(xiě)入內(nèi)容,寫(xiě)入內(nèi)容的時(shí)候,如果另一端正有線程正在等待管道中的內(nèi)容,那么這個(gè)線程就會(huì)被喚醒。這個(gè)等待和喚醒的操作是如何進(jìn)行的呢,這就要借助Linux系統(tǒng)中的epoll機(jī)制了。 Linux系統(tǒng)中的epoll機(jī)制為處理大批量句柄而作了改進(jìn)的poll,是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,它能顯著減少程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率。

(01) pipe(wakeFds),該函數(shù)創(chuàng)建了兩個(gè)管道句柄。
(02) mWakeReadPipeFd=wakeFds[0],是讀管道的句柄。
(03) mWakeWritePipeFd=wakeFds1,是寫(xiě)管道的句柄。
(04) epoll_create(EPOLL_SIZE_HINT)是創(chuàng)建epoll句柄。
(05) epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem),它的作用是告訴mEpollFd,它要監(jiān)控mWakeReadPipeFd文件描述符的EPOLLIN事件,即當(dāng)管道中有內(nèi)容可讀時(shí),就喚醒當(dāng)前正在等待管道中的內(nèi)容的線程。

這樣一個(gè)線程(比如UI線程)消息隊(duì)列和Looper就準(zhǔn)備就緒了。

消息隊(duì)列創(chuàng)建時(shí),會(huì)調(diào)用JNI函數(shù),初始化NativeMessageQueue對(duì)象。NativeMessageQueue則會(huì)初始化Looper對(duì)象。Looper的作用就是,當(dāng)Java層的消息隊(duì)列中沒(méi)有消息時(shí),就使Android應(yīng)用程序主線程進(jìn)入等待狀態(tài),而當(dāng)Java層的消息隊(duì)列中來(lái)了新的消息后,就喚醒Android應(yīng)用程序的主線程來(lái)處理這個(gè)消息。

由于鄙人C++荒廢,在此不做過(guò)多探討。
關(guān)于C++層邏輯可參考文章:
Android應(yīng)用程序消息處理機(jī)制(Looper、Handler)分析
Android消息機(jī)制-Handler(native篇)
Android消息處理機(jī)制(Handler、Looper、MessageQueue與Message)
Android消息機(jī)制架構(gòu)和源碼解析

本系列文章參考的資料還有:
書(shū)籍:深入理解Android內(nèi)核設(shè)計(jì)思想
Android源碼分析-消息隊(duì)列和Looper
Android消息循環(huán)機(jī)制源碼分析
Handler Looper MessageQueue 詳解

最后編輯于
?著作權(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)容

  • Android消息處理機(jī)制估計(jì)都被寫(xiě)爛了,但是依然還是要寫(xiě)一下,因?yàn)锳ndroid應(yīng)用程序是通過(guò)消息來(lái)驅(qū)動(dòng)的,An...
    一碼立程閱讀 4,591評(píng)論 4 36
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評(píng)論 25 709
  • 簡(jiǎn)介 消息驅(qū)動(dòng)是一種進(jìn)程/線程的運(yùn)行模式,內(nèi)部或者外部的消息事件被放到進(jìn)程/線程的消息隊(duì)列中按序處理是現(xiàn)在的操作系...
    xxxxcoder閱讀 2,081評(píng)論 1 7
  • Android平臺(tái)上,主要用到的通信機(jī)制有兩種:Handler和Binder,前者用于進(jìn)程內(nèi)部的通信,后者主要用于...
    帝都De霧霾閱讀 1,551評(píng)論 1 7
  • 穿越金雞版圖,擁抱天涯海角, 退去厚重棉衣,脫胎換骨重生。 赤臂拖板登場(chǎng),隨風(fēng)移俗之舉, 龜行百步之遙,汗水依舊...
    家鄉(xiāng)雪韻閱讀 561評(píng)論 3 6

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