天天看點

我要做 Android 之消息機制

Android的消息機制指的是Handler的運作機制,本篇将總結Handler機制的相關知識點:

  • 消息機制概述
  • 消息機制分析

    1.消息機制概述

a.作用:跨線程通信。

b.常用場景:當子線程中進行耗時操作後需要更新UI時,通過Handler将有關UI的操作切換到主線程中執行。

系統不建議在子線程通路UI的原因:UI控件非線程安全,在多線程中并發通路可能會導緻UI控件處于不可預期的狀态。而不對UI控件的通路加上鎖機制的原因有:

  • 上鎖會讓UI控件變得複雜和低效
  • 上鎖後會阻塞某些程序的執行

c.四要素:

  • Message(消息):需要被傳遞的消息,其中包含了消息ID,消息處理對象以及處理的資料等,由MessageQueue統一列隊,最終由Handler處理。
  • MessageQueue(消息隊列):用來存放Handler發送過來的消息,内部通過單連結清單的資料結構來維護消息清單,等待Looper的抽取。
  • Handler(處理者)

    :負責Message的發送及處理。

    • Handler.sendMessage():向消息池發送各種消息事件。
    • Handler.handleMessage() :處理相應的消息事件。
  • Looper(消息泵):通過Looper.loop()不斷地從MessageQueue中抽取Message,按分發機制将消息分發給目标處理者。

Thread(線程):負責排程整個消息循環,即消息循環的執行場所。

存在關系:

  • 一個Thread隻能有一個Looper,可以有多個Handler;
  • Looper有一個MessageQueue,可以處理來自多個Handler的Message;
  • MessageQueue有一組待處理的Message,這些Message可來自不同的Handler;
  • Message中記錄了負責發送和處理消息的Handler;
  • Handler中有Looper和MessageQueue;
我要做 Android 之消息機制

01.png

我要做 Android 之消息機制

02.png

d.實作方法:

  • 在主線程執行個體化一個全局的Handler對象;
  • 在需要執行UI操作的子線程裡執行個體化一個Message并填充必要資料,調用Handler.sendMessage(Message msg)方法發送出去;
  • 複寫handleMessage()方法,對不同Message執行相關操作;

2.消息機制分析

a.工作流程:

  • Handler.sendMessage()發送消息時,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息;
  • 通過Looper.loop()開啟循環後,不斷輪詢調用MessageQueue.next();
  • 調用目标Handler.dispatchMessage()去傳遞消息,目标Handler收到消息後調用Handler.handlerMessage()處理消息。

簡單來看,即Handler将Message發送到Looper的成員變量MessageQueue中,之後Looper不斷循環周遊MessageQueue從中讀取Message,最終回調給Handler處理。如圖:

我要做 Android 之消息機制

03.png

b.工作原理:

  • (1)Looper的建立:先從應用程式的入口函數ActivityThread.main()看起,在這裡(主線程)系統自動建立了Looper,主要方法:
//主線程中不需要自己建立Looper
public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();//為主線程建立Looper,該方法内部又調用 Looper.prepare()
        ......
        Looper.loop();//開啟消息輪詢
        ......
        
    }

           

注意:

  • 子線程的Looper需要手動去建立,标準寫法是:
//子線程中需要自己建立一個Looper
new Thread(new Runnable() {
            @Override
            public void run() {
                
                Looper.prepare();//為子線程建立Looper               
                Looper.loop(); //開啟消息輪詢
            }
        }).start();

           
  • 無論是主線程還是子線程,Looper隻能被建立一次,即一個Thread隻有一個Looper。
  • 所建立的Looper會儲存在ThreadLocal(線程本地存儲區)中,它不是線程,作用是幫助Handler獲得目前線程的Looper。更多講解見 ThreadLocal詳解
  • (2)MessageQueue的建立:在Looper的構造函數建立了一個MessageQueue:
private Looper(boolean quitAllowed) {
       
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
           

(3)Message輪詢及處理:有了Looper和MessageQueue之後通過Looper.loop()開啟消息輪詢:

public static void loop() {
        ......
        for (;;) {//死循環
            Message msg = queue.next(); //用于提取下一條資訊,該方法裡同樣有個for(;;)死循環,當沒有可處理該Message的Handler時,會一直阻塞
            if (msg == null) {
                
                return;
            }
         ......   
         try {
                msg.target.dispatchMessage(msg);//如果從MessageQueue中拿到Message,由和它綁定的Handler(msg.target)将它發送到MessageQueue
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         ......   
    }
           

現在就剩建立Handler及Message發送了。(4)先看Handler的建立:有兩種形式的Handler:

//第一種:send方式的Handler建立
Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //如UI操作
                
            }
        };

//第二種:post方式的Handler建立
Handler handler = new Handler();
           
  • (5) Message的發送:

對于send方式的Handler:建立好一個Message後,調用Handler的以下幾種常見的方法來發送消息:

sendEmptyMessage();           //發送空消息
sendEmptyMessageAtTime();     //發送按照指定時間處理的空消息
sendEmptyMessageDelayed();    //發送延遲指定時間處理的空消息
sendMessage();                //發送一條消息
sendMessageAtTime();          //發送按照指定時間處理的消息
sendMessageDelayed();         //發送延遲指定時間處理的消息
sendMessageAtFrontOfQueue();  //将消息發送到消息隊頭
           

對于post方式的Handler,可在子線程直接調用Handler的以下幾種常見方法,使得切換到主線程:

post(Runnable r)
postAtFrontOfQueue(Runnable r)
postAtTime(Runnable r, Object token, long uptimeMillis)
postAtTime(Runnable r, long uptimeMillis)
postDelayed(Runnable r, long delayMillis)

//例如,postDelayed()方法
handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //如UI操作
                    }
                },300);
           

通過以上各種Handler的發送方法,都會依次調用 Handler.sendMessageDelayed->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage() 最終将Message發送到MessageQueue。

我要做 Android 之消息機制

04.png

Q:Message可以如何建立?哪種效果更好,為什麼?

建立Message對象的時候,有三種方式,分别為:

1.Message msg = new Message();

2.Message msg2 = Message.obtain();

3.Message msg1 = handler1.obtainMessage();

這三種方式有什麼差別呢?

Message msg = new Message();這種就是直接初始化一個Message對象,沒有什麼特别的。

2)Message msg2 = Message.obtain();

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
           

從注釋可以得知,從整個Messge池中傳回一個新的Message執行個體,通過obtainMessage能避免重複Message建立對象。

Message msg1 = handler1.obtainMessage();

public final Message obtainMessage()
    {
        return Message.obtain(this);
    }1234
public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
           

可以看到,第二種跟第三種其實是一樣的,都可以避免重複建立Message對象,是以建議用第二種或者第三種任何一個建立Message對象。

Q:主線程中Looper的輪詢死循環為何沒有阻塞主線程?

正如我們所知,在android中如果主線程中進行耗時操作會引發ANR(Application Not Responding)異常。

造成ANR的原因一般有兩種:

  1. 目前的事件沒有機會得到處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
  2. 目前的事件正在處理,但沒有及時完成

為了避免ANR異常,android使用了Handler消息處理機制。讓耗時操作在子線程運作。

是以産生了一個問題,主線程中的Looper.loop()一直無限循環檢測消息隊列中是否有新消息為什麼不會造成ANR?

while (true) {



       //取出消息隊列的消息,可能會阻塞



       Message msg = queue.next(); // might block



       ...



       //解析消息,分發消息



       msg.target.dispatchMessage(msg);



       ...



    }
           

顯而易見的,如果main方法中沒有looper進行循環,那麼主線程一運作完畢就會退出。

總結:ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那麼你的應用也就退出了。

因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點選觸摸或者說Activity的生命周期都是運作在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。隻能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

也就說我們的代碼其實就是在這個循環裡面去執行的,當然不會阻塞了。

如果某個消息處理時間過長,比如你在onCreate(),onResume()裡面處理耗時操作,那麼下一次的消息比如使用者的點選事件不能處理了,整個循環就會産生卡頓,時間一長就成了ANR。

而且主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,并且往管道檔案寫資料,主線程即被喚醒,從管道檔案讀取資料,主線程被喚醒隻是為了讀取消息,當消息讀取完畢,再次睡眠。是以loop的循環并不會對CPU性能有過多的消耗。

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

繼續閱讀