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