Android消息機制全面解析

Android是基於事件驅動,每一個觸摸事件或者是Activity的生命週期都是運行在Looper.looper()的控制之下,理解弄懂消息機制可以讓我們在開發的過程中更加得心應手。

Android的消息機制也是Handler機制,主要的作用是用來在不同線程之間的通信,通常使用在子線程執行完成一些耗時操作,需要回到主線程更新界面UI時,通過Handler將有關UI的操作切換到主線程。

工作流程

先來大致梳理下整個流程:

  • 應用程序啓動的時候,在主線程中會默認調用了 Looper.preper()方法,初始化Looper對象並綁定到當前線程中,並在Looper內部維護一個MessageQueue
  • 接着調用handler.sendMessage()發送消息,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息
  • 主線程調用Looper.looper()開啓循環,不斷輪詢消息隊列,通過MessageQueue.next()取出消息
  • 取出的message不爲空則調用msg.target.dispatchMessage()傳遞分發消息,目標handler收到消息後會執行handler.handlerMessage()方法處理消息

詳細分析

Looper

Looper的字面意思是“循環者”,它被設計用來使一個普通線程變成Looper線程 。所謂Looper線程就是執行循環工作的線程

Looper的創建(Looper.prepare())

在應用程序的入口ActivityThread.main()方法中系統已經幫我們創建好Looper對象

	//主線程中不需要自己創建Looper
	public static void main(String[] args) {
	        ......
	        Looper.prepareMainLooper();//爲主線程創建Looper,該方法內部又調用 Looper.prepare()
	        ......
	        Looper.loop();//開啓消息輪詢
	        ......
	    }

子線程中的Looper是需要我們自己手動創建的

public class LooperThread extends Thread {
    @Override
    public void run() {
        // 將當前線程初始化爲Looper線程
        Looper.prepare();
        
        // ...其他處理,如實例化handler
        
        // 開始循環處理消息隊列
        Looper.loop();
    }
}

這樣你的線程就可以成爲Looper線程

在Looper的構造方法中還創建了MessageQueue

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • 無論是主線程還是子線程,Looper只能被創建一次,即一個Thread只有一個Looper。
  • 所創建的Looper會保存在ThreadLocal(線程本地存儲區)中,它不是線程,作用是幫助Handler獲得當前線程的Looper。(ThreadLocal可以在不同的線程之中互不干擾地存儲並提供數據,通過ThreadLocal可以輕鬆獲取每個線程的Looper)

Looper.prepare()方法的作用
1.將當前線程變成Looper線程,在其內部維護一個消息隊列MQ
2.創建Looper對象並將Looper對象定義爲ThreadLocal

Looper.loop()

調用Looper.loop()後,Looper線程就開始真正的工作了。該方法是一個阻塞性的死循環,它不斷輪詢自己的MQ,並從中取出隊頭的消息執行。

public static void loop() {
        final Looper me = myLooper();//得到當前線程Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // 開始循環
        for (;;) {
            Message msg = queue.next(); // might block 從MQ中取出消息
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                // 將真正的處理工作交給message的target(handler)處理分發消息
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

Looper.myLooper()得到當前線程looper對象:

 public static @Nullable Looper myLooper() {
       // 在任意線程調用Looper.myLooper()返回的都是那個線程綁定的looper
        return sThreadLocal.get();
    }

Handler

handler扮演了往MQ上添加消息和處理消息的角色(只處理由自己發出的消息),即通知MQ它要執行一個任務(sendMessage),並在loop到自己的時候執行該任務(handleMessage),整個過程是異步的。handler創建時會關聯一個looper,默認的構造方法將關聯當前線程的looper,如果當前線程還沒有初始化Looper,或者說當前線程還不是looper線程,會報RuntimeException,不過這也是可以set的。
public class handler {

final MessageQueue mQueue;  // 關聯的MQ
final Looper mLooper;  // 關聯的looper
final Callback mCallback; 
// 其他屬性

public Handler() {
    // 沒看懂,直接略過,,,
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    // 默認將關聯當前線程的looper
    mLooper = Looper.myLooper();
    // looper不能爲空,即該默認的構造方法只能在looper線程中使用
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // 重要!!!直接把關聯looper的MQ作爲自己的MQ,因此它的消息將發送到關聯looper的MQ上
    mQueue = mLooper.mQueue;
    mCallback = null;
}

// 其他方法

}
Handler發出的message有如下特點

1.message.target爲該handler對象,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵代碼
msg.target.dispatchMessage(msg);
2.post發出的message,其callback爲Runnable對象

 // 此方法用於向關聯的MQ上發送Runnable對象,它的run方法將在handler關聯的looper線程中執行
    public final boolean post(Runnable r)
    {
       // 注意getPostMessage(r)將runnable封裝成message
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

Handler處理消息

// 處理消息,該方法由looper調用
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 如果message設置了callback,即runnable消息,處理callback!
            handleCallback(msg);
        } else {
            // 如果handler本身設置了callback,則執行callback
            if (mCallback != null) {
                 /* 這種方法允許讓activity等來實現Handler.Callback接口,避免了自己編寫handler重寫handleMessage方法。 */
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 如果message沒有callback,則調用handler的鉤子方法handleMessage
            handleMessage(msg);
        }
    }
    
    // 處理runnable消息
    private final void handleCallback(Message message) {
        message.callback.run();  //直接調用run方法!
    }
    // 由子類實現的鉤子方法
    public void handleMessage(Message msg) {
    }

重點
1.Handler 可以在任意線程中發送消息,這些消息會被添加到關聯的MQ上
2.Handler是在它關聯的Looper線程(Looper綁定的線程)中處理消息的
在這裏插入圖片描述

總結
1.handler可以在任意線程發送消息,這些消息會被添加到關聯的MQ上。
2.handler是在它關聯的looper線程中處理消息的。(handlerMessage()方法運行所在的線程是根據handler創建的時候綁定的Looper線程,和綁定的Looper所在的線程一致)
3.Android的主線程也是一個looper線程,我們在其中創建的handler默認將關聯主線程MQ

Message

message又叫task,封裝了任務攜帶的信息和處理該任務的handler

注意事項:
1.儘管Message有public的默認構造方法,但是你應該通過Message.obtain()來從消息池中獲得空消息對象,以節省資源。
2.如果你的message只需要攜帶簡單的int信息,請優先使用Message.arg1和Message.arg2來傳遞信息,這比用Bundle更省內存
3.擅用message.what來標識信息,以便用不同方式處理message。

爲了能夠更好的理解掌握消息機制,以下有幾道面試題

1.爲什麼一個線程只有一個Looper、只有一個MessageQueue?

因爲線程對應的Looper是在ThreadLocal裏面存儲,它是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定線程中可以獲取到存儲的數據,對於其它線程來說無法獲取到數據。ThreadLocal它的作用是可以在不同的線程之中互不干擾地存儲並提供數據(就相當於一個Map集合,鍵位當前的Thead線程,值爲Looper對象)。另外,在looper創建的方法looper.prepare()中,會有一個判斷如果當前線程存在Looper對象,就會報RunTimeException,所以一個線程只有一個Looper,而MQ作爲Looper的成員變量自然也就只有一個。

2.如何獲取當前線程的Looper?是怎麼實現的?(理解ThreadLocal)

3.是不是任何線程都可以實例化Handler?有沒有什麼約束條件?

任何線程都可以實例化Handler,handler創建時會關聯一個looper,默認的構造方法將關聯當前線程的looper,如果當前線程還沒有初始化Looper,或者說當前線程還不是looper線程,會報RuntimeException。

4.Looper.loop是一個死循環,拿不到需要處理的Message就會阻塞,那在UI線程中爲什麼不會導致ANR?

在應用程序的入口ActivityThread裏面的main方法中會創建一個主線程的looper對象和一個大Handler,(這也是爲什麼直接在主線程拿Handler就有Looper的原因,在其他線程是要自己Looper.prepare())
Android是基於事件驅動的,通過looper.looper()不斷接收事件,處理事件,每一個觸摸事件或者是Activity的生命週期都是運行在Looper.looper()的控制之下,當收到不同Message時則採用相應措施:在H.handleMessage(msg)方法中,根據接收到不同的msg,執行相應的生命週期。如果它停止了,應用也就停止了。也就是說我們的代碼其實就是運行在這個循環裏面去執行的,當然就不會阻塞。

而所謂ANR便是Looper.loop沒有得到及時處理,一旦沒有消息,Linux的epoll機制則會通過管道寫文件描述符的方式來對主線程進行喚醒與睡眠,Android裏調用了linux層的代碼實現在適當時會睡眠主線程。

拓展

消息循環(死循環)的必要性:

對於線程既然是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出
ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那麼你的應用也就退出了。如果main方法中沒有looper進行循環,那麼主線程一運行完畢就會退出。

主線程在沒有事件需要處理的時候就是處於阻塞的狀態。想讓主線程活動起來一般有兩種方式:
第一種 :是系統喚醒主線程,並且將點擊事件傳遞給主線程;
第二種 :是其他線程使用主線程的Handler向MessageQueue中存放了一條消息,導致loop被喚醒繼續執行。

總結
Looer.loop()方法可能會引起主線程的阻塞,但只要它的消息循環沒有被阻塞,能一直處理事件就不會產生ANR異常。

looper.looper()阻塞會不會消耗大量的cpu資源

主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是爲了讀取消息,當消息讀取完畢,再次睡眠,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生。因此loop的循環並不會對CPU性能有過多的消耗。

5.Handler.sendMessageDelayed()怎麼實現延遲的?結合Looper.loop()循環中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。

Handler.sendMessageDelayed()內部調用sendMessageAtTime()把傳入的時間轉化成絕對時間when(延時的時間加上系統當前的時間),然後調用MessageQueue的enqueueMessage(),採用線程安全的方式將Message插入到消息隊列中,消息隊列的插入是由when順序排列,插入的新消息有三種可能成爲消息隊列的head:
(1)消息隊列爲空;
(2)參數when爲0,因爲此時when已經轉成絕對時間,所以只有AtFrontOfQueue(sendMessageAtFrontOfQueue直接把消息插入到隊列的頭部)系列的API纔會滿足這個條件;
(3)當前的head Message執行時間在when之後,即消息隊列中無需要在此Message之前執行的Message。
接着就是Looper.looper()啓動消息循環,循環開始調用messageQueue.next()從消息隊列中取出一個合理的消息。如果next()返回null,則looper()直接return,本次消息循環結束。如果消息不爲空則調用msg.target.dispatchMessage(msg)處理消息(msg.target就是Handler)

.next()取下一個消息的實際執行時間取決於上一個消息什麼時候處理完
在MessageQueue.next()中,如果在消息隊列中順序找到了一個消息msg(前文分析過,消息隊列的插入是由when順序排列,所以如果當前的消息沒有到執行時間,其後的也一定不會到),當前的系統時間小於msg.when,那麼會計算一個timeout,以便在到執行時間時wake up;如果當前系統時間大於或等於msg.when,那麼會返回msg給Looper.loop()。所以這個邏輯只能保證在when之前消息不被處理,不能夠保證一定在when時被處理。
(1)在Loop.loop()中是順序處理消息,如果前一個消息處理耗時較長,完成之後已經超過了when,消息不可能在when時間點被處理。
(2)即使when的時間點沒有被處理其他消息所佔用,線程也有可能被調度失去cpu時間片。
(3)在等待時間點when的過程中有可能入隊處理時間更早的消息,會被優先處理,又增加了(1)的可能性。
所以由上述三點可知,Handler提供的指定處理時間的api諸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保證在指定時間之前不被執行,不能保證在指定時間點被執行。

參考
https://blog.csdn.net/zhanglianyu00/article/details/70842494
http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
https://www.jianshu.com/p/1c79fb5296b6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章