深入淺析Android消息機(jī)制
在Android中,線程內(nèi)部或者線程之間進(jìn)行信息交互時(shí)經(jīng)常會(huì)使用消息,這些基礎(chǔ)的東西如果我們熟悉其內(nèi)部的原理,將會(huì)使我們?nèi)菀?、更好地架?gòu)系統(tǒng),避免一些低級(jí)的錯(cuò)誤。
每一個(gè)Android應(yīng)用在啟動(dòng)的時(shí)候都會(huì)創(chuàng)建一個(gè)線程,這個(gè)線程被稱為主線程或者UI線程,Android應(yīng)用的所有操作默認(rèn)都會(huì)運(yùn)行在這個(gè)線程中。
但是當(dāng)我們想要進(jìn)行數(shù)據(jù)請(qǐng)求,圖片下載,或者其他耗時(shí)操作時(shí),是不可能在這個(gè)UI線程做的,因?yàn)锳ndroid在3.0以后的版本已經(jīng)禁止了這件事情,直接拋出一個(gè)異常。所以我們需要一個(gè)子線程來(lái)處理那些除UI操作的事情。
但是這個(gè)又會(huì)有一個(gè)問(wèn)題,我們只能在UI線程進(jìn)程UI操作,只能在子線程進(jìn)行耗時(shí)操作,如果我們需要在耗時(shí)操作結(jié)束后在Android界面上顯示一個(gè)View,我們應(yīng)該怎么做?我們是不可能在子線程直接刷新UI的。這時(shí)我們需要用到Android的消息機(jī)制,來(lái)實(shí)現(xiàn)主線程和子線程的通信。簡(jiǎn)單來(lái)說(shuō),就是子線程獲取到數(shù)據(jù)之后,不直接進(jìn)行UI更新,而是把數(shù)據(jù)裝到消息中發(fā)送到主線程,主線程中有一個(gè)循環(huán)輪詢會(huì)立即收到子線程發(fā)過(guò)來(lái)的信息,然后拿到消息數(shù)據(jù)后在主線程更新UI 。說(shuō)起來(lái)比較簡(jiǎn)單,我們來(lái)仔細(xì)的看一下具體是怎么說(shuō)的。
處理消息的手段——Handler, Looper, MessageQueue
Handler
我們先講解一下Handler,Handler顧名思義就是處理者,通常對(duì)他的用法是在UI線程中新建一個(gè)Handler,并覆寫(xiě)他的handleMessage, 然后再耗時(shí)的線程中將消息post給UI線程,例子如下:
class MyHandler extends Handler{ @Override public void handleMessage(Message msg){ //更新UI } } MyHandler mHandler = new MyHandler(); new Thread(){ public void run(){ mHandler.sendEmptyMessage(123); }; }.start();
這里規(guī)定了Handler必須在主線程創(chuàng)建,因?yàn)橹挥性赨I線程創(chuàng)建才會(huì)讓Handler關(guān)聯(lián)到已有的MessageQueue。而MessageQueue被封裝到Looper中,而Looper又通過(guò)ThreadLocal封裝到一個(gè)線程中,最后相當(dāng)于MessageQueue關(guān)聯(lián)了一個(gè)線程。所以簡(jiǎn)單來(lái)說(shuō)就是Handler將消息投遞到一個(gè)關(guān)聯(lián)了線程的MessageQueue中,然后Handler在從MessageQueue中取出消息,并且處理它。
我們看一下Handler的2個(gè)常用的方法
void handleMessage(Message msg) : 處理消息的方法 final boolean sendMessage(Message msg) : 立即發(fā)送消息
第一個(gè)方法 我們通常在UI線程中執(zhí)行,一般用來(lái)刷新UI,至于如果創(chuàng)建了一個(gè)非靜態(tài)內(nèi)部類產(chǎn)生對(duì)內(nèi)存泄漏,建議參考這篇博客Handler引發(fā)的內(nèi)存泄漏.第二個(gè)方法我們通常在子線程中執(zhí)行,需要一個(gè)Handler的實(shí)例化對(duì)象,通常是由主線程去去傳遞給子線程。并且需要一個(gè)Message對(duì)象,指定他的msg.what作為消息的標(biāo)示,但是如果我們只是用Handler去處理一個(gè)消息的時(shí)候,選擇post方法是個(gè)更好的選擇,例子如下:
private Handler mHandler = new Handler(); new Thread(new Runnable() { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { //UI操作 ... } }); } }).start();
下面我們接著討論下消息的循環(huán)隊(duì)列MessageQueue與包裝他的Looper循環(huán)
Looper和MessageQueue
上面提到了在UI線程中創(chuàng)建并實(shí)例化Handler對(duì)象不需要Looper和MessageQueue,因?yàn)槲覀兊膽?yīng)用在啟動(dòng)的時(shí)候先執(zhí)行了ActivityThreadMain,在這個(gè)方法就是Java語(yǔ)言運(yùn)行的入口public
static void main(String [] args) 在這里面創(chuàng)建了一個(gè)MainLooper,創(chuàng)建的過(guò)程如下: public static void main(string[] args){ //初始化 Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if(sMainThreadHandler == null){ sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); //動(dòng)起來(lái) Looper.loop(); }
這里面并沒(méi)有MessageQueue的出現(xiàn),我們可以看一看Looper類的源碼,來(lái)了解在初始化的時(shí)候發(fā)生了什么有趣的事情。
public class Looper { private static final ThreadLocal sThreadLocal = new ThreadLocal(); // Looper內(nèi)的消息隊(duì)列 final MessageQueue mQueue; // 當(dāng)前線程 Thread mThread; // 。。。其他屬性 // 每個(gè)Looper對(duì)象中有它的消息隊(duì)列,和它所屬的線程 private Looper() { mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); } // 我們調(diào)用該方法會(huì)在調(diào)用線程的TLS中創(chuàng)建Looper對(duì)象 public static final void prepare() { if (sThreadLocal.get() != null) { // 試圖在有Looper的線程中再次創(chuàng)建Looper將拋出異常 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); } // 其他方法 }
我們一行行的看這段代碼,首先是實(shí)例化一個(gè)ThreadLocal對(duì)象,這個(gè)用來(lái)實(shí)現(xiàn)Looper循環(huán)的本地化存儲(chǔ),關(guān)于ThreadLocal可以看這篇文章為什么用ThreadLocal,簡(jiǎn)而言之就是當(dāng)多個(gè)線程同時(shí)訪問(wèn)Looper對(duì)象的時(shí)候,我們不用synchronized同步機(jī)制來(lái)處理他,而是為每個(gè)線程創(chuàng)建一個(gè)自己的Looper副本,A線程改變了A的looper副本,不影響B(tài)線程的Looper,從而比較高效的實(shí)現(xiàn)線程安全。后面幾句依次定義了MessageQueue,并對(duì)Looper進(jìn)行了私有化構(gòu)造,在prepare方法中將Looper對(duì)象設(shè)置給了sThreadLocal 這樣MessageQueue包裝在了Looper對(duì)象中,同時(shí)通過(guò)ThreadLocal使得線程和Looper關(guān)聯(lián)上,從而消息隊(duì)列與線程關(guān)聯(lián)上,并且不同的線程就不能訪問(wèn)對(duì)方的消息隊(duì)列。
如下圖所示:
接著就是Looper.loop 循環(huán)執(zhí)行起來(lái),我們看一下,在loop方法里面執(zhí)行了發(fā)生了什么事情
public static final void loop() { Looper me = myLooper(); //得到當(dāng)前線程Looper MessageQueue queue = me.mQueue; //得到當(dāng)前l(fā)ooper的MQ while (true) { Message msg = queue.next(); // 取出message if (msg != null) { if (msg.target == null) { return; } msg.target.dispatchMessage(msg); msg.recycle(); } } }
這是省略版的代碼,我們從這里看出無(wú)限循環(huán)執(zhí)行,首先從消息隊(duì)列中不斷取出消息,然后不斷msg是否為空,msg.target是否為空,不空的話,執(zhí)行dispatchMessage方法,這個(gè)方法是handler的一個(gè)方法,由此我們可以看出msg.target是handler的類型,至此,通過(guò)Looper.prepare和Loop.loop實(shí)現(xiàn)了MessageQueue,Looper,Handler三者之間的關(guān)聯(lián)。而Handler與Looper,和MessageQueue關(guān)聯(lián)則是在Handler的默認(rèn)構(gòu)造器中,通過(guò)Looper.getLooper獲取loop對(duì)象,從而獲取MessageQueue,其源碼如下:
public Handler(){ //直接把關(guān)聯(lián)looper的MQ作為自己的MQ,因此它的消息將發(fā)送到關(guān)聯(lián)looper的MQ上 mLooper = Looper.myLooper(); mQueue = mLooper.mQueue; mCallback = null; }
然后我們的流程圖可以多些內(nèi)容,如下所示:
我們接下來(lái)看一下dispatchMessage() 方法,在該方法中實(shí)際上只是一個(gè)分發(fā)方法,如果Runable類型的callback為空,則執(zhí)行handlerMessage來(lái)處理消息,該方法為空,需要覆寫(xiě)。如果不為空,則執(zhí)行handleCallback。實(shí)際上,如果我們用handle的post方法,則就執(zhí)行了callback,如果用sendMessage,則就執(zhí)行了handleMessage
這里無(wú)論是post(Runnable callback)還是handlerMessage實(shí)際上都是在調(diào)用一個(gè)方法sendMessageDelayed(Message msg) 只不過(guò)handlerMessage是直接接受一個(gè)參數(shù),而Runable callback實(shí)際上是將這個(gè)Runable對(duì)象賦給了Message對(duì)象的callback成員變量,最后將Message對(duì)象插入消息隊(duì)列里面。最后Looper不斷從MessageQueue中讀取消息,并且調(diào)用Handler的dispatchMessage消息,在根據(jù)callback是否為空,來(lái)采用不同的方法執(zhí)行。Android消息機(jī)制分析到此結(jié)束。
回到最開(kāi)始
我們這次知道了為什么要在主線程中實(shí)例化Handler對(duì)象才能更新UI刷新,因?yàn)橹挥邪l(fā)送到UI線程的消息,才能被UI線程的handler處理,如果我們要在非UI線程中,實(shí)例化Handler,則必須先將線程變成LooperThread,在實(shí)例化。也就是說(shuō)執(zhí)行如下的代碼:
Loop.prepare(); hander = new Handler;
Loop.loop
至于原因相信讀完上面的講解,應(yīng)該知道。
現(xiàn)在我們看一下我們最開(kāi)始的代碼,最后腦補(bǔ)一下Handler的工作流程。
class MyHandler extends Handler{ @Override public void handleMessage(Message msg){ //更新UI } } MyHandler mHandler = new MyHandler(); new Thread(){ public void run(){ mHandler.sendEmptyMessage(123); }; }.start();
在Handler實(shí)例化成mHandler的時(shí)候,系統(tǒng)通過(guò)Handler默認(rèn)的構(gòu)造函數(shù)完成了Handler與Looper的關(guān)聯(lián),并通過(guò)Looper關(guān)聯(lián)到了MessageQueue。而主線程的Looper則早在系統(tǒng)啟動(dòng)的時(shí)候通過(guò)Loop.prepare就已經(jīng)構(gòu)造完成了,并與UI線程通過(guò)ThreadLocal關(guān)聯(lián)起來(lái),然后在新的線程中執(zhí)行mHandler.sendEmptyMessage,將Message發(fā)送給了MessageQueue,Looper.loop在循環(huán)的時(shí)候,不斷取出message,交給Handler處理,在我們覆寫(xiě)的HandleMessage中,識(shí)別出我們發(fā)送的消息,將消息處理。當(dāng)然這里只是一個(gè)Empty消息,所以在handleMessage中沒(méi)有去執(zhí)行msg.what的判斷。
以上內(nèi)容是小編給大家介紹的Android消息機(jī)制,希望對(duì)大家有所幫助!
- android異步消息機(jī)制 源碼層面徹底解析(1)
- 代碼分析Android消息機(jī)制
- Android異步消息機(jī)制詳解
- android線程消息機(jī)制之Handler詳解
- android利用消息機(jī)制獲取網(wǎng)絡(luò)圖片
- Android 消息機(jī)制詳解及實(shí)例代碼
- Android的消息機(jī)制
- Android消息機(jī)制Handler的工作過(guò)程詳解
- 深入剖析Android消息機(jī)制原理
- Android 消息機(jī)制以及handler的內(nèi)存泄露
- Android6.0 消息機(jī)制原理解析
- Android 消息機(jī)制問(wèn)題總結(jié)
- Android編程中的消息機(jī)制實(shí)例詳解
- Android編程之消息機(jī)制實(shí)例分析
- android異步消息機(jī)制 從源碼層面解析(2)
相關(guān)文章
Android應(yīng)用圖標(biāo)上的小紅點(diǎn)Badge實(shí)踐代碼
本篇文章主要介紹了Android應(yīng)用圖標(biāo)上的小紅點(diǎn)Badge實(shí)踐代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07Android實(shí)現(xiàn)原生鎖屏頁(yè)面音樂(lè)控制
這篇文章主要介紹了Android實(shí)現(xiàn)原生鎖屏頁(yè)面音樂(lè)控制,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12Android自定義view實(shí)現(xiàn)太極效果實(shí)例代碼
這篇文章主要介紹了Android自定義view實(shí)現(xiàn)太極效果實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05Android超清晰6.0權(quán)限申請(qǐng)AndPermission
這篇文章主要介紹了Android超清晰6.0權(quán)限申請(qǐng)AndPermission,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android?Studio實(shí)現(xiàn)簡(jiǎn)單繪圖板
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)簡(jiǎn)單繪圖板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Android實(shí)現(xiàn)簡(jiǎn)單的自定義ViewGroup流式布局
本文我們將一起復(fù)習(xí)一下ViewGroup的測(cè)量布局方式。然后會(huì)以入門(mén)級(jí)的 FlowLayout 為例,來(lái)看看流式布局是如何測(cè)量與布局的,感興趣的可以了解一下2022-12-12Android 實(shí)現(xiàn)仿支付寶的密碼均分輸入框
這篇文章主要介紹了Android 實(shí)現(xiàn)仿支付寶的密碼均分輸入框的相關(guān)資料,需要的朋友可以參考下2017-06-06Android畫(huà)廊效果之ViewPager顯示多個(gè)圖片
這篇文章主要為大家詳細(xì)介紹了Android畫(huà)廊效果之ViewPager顯示多個(gè)圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11