Android消息處理機制(Handler、Looper、MessageQueue)

概述

Android是基礎消息驅動的,Android系統為每一個程序維護了一個消息隊列( MessageQueue ),我們可以非常簡單的往消息隊列中插入消息( Handler ),主線程會循環的從隊列中取出消息( Looper ),然后交給消息的發送者來執行( Handler ),這樣就實現了通過消息來驅動程序的執行。

這篇文章會詳情分析android的消息處理機制,為了讓理解更加簡單,我們將從日常應用中最常見的情景---子線程中返回了數據,通過handler更新UI開始。

大家都知道在子線程中通過handler來更新UI的步驟吧

  • 創建handler
  • 用handler發送消息
  • 用handler處理消息(收到消息,更新UI)

what? 好像就用handler就完了,說好的Looper、MessageQueue呢?別急,先來看看Handler的創建

public Handler() {
    this(null, false);
}

再來看看調用的這個this()

public Handler(Callback callback, boolean async) {
    .........        
    mLooper = Looper.myLooper();
    if (mLooper == null) { throw new RuntimeException( "Can't create whitout Looper");}
    mQueue = mLooper.mQueue;
        .......
}

首先通過 Looper 的靜態方法獲取對應的 looper ,如果為空,直接拋出異常,由此可以看出消息處理確定是需要 looper 的。這里你可能會有疑問,這個looper是怎么來的?

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();}
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

原來是通過 ThreadLocal 的 get 方法從 ThreadLocalMap 鏈表中以當前線程作為 key ,取出的 value 就是我們需要的 looper ,這里的ThreadLocal和ThreadLocalMap我們等下再說。

簡單點就是線程中維護了一個HashMap用來存取looper,這里你可能又有疑問了,既然是map表,我們都沒有做存looper的操作,那取出來肯定是空啊,那為什么我們創建handler的時候沒有出現異常呢?

其實,這是因為程序的主線程(UI線程)在啟動的時候,就創建了looper并存入了ThreadLocalMap中。

public static void main(String[] args) {
    ......    
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler(); }
    ........
    Looper.loop();}

Looper

來看下looper是怎么創建的

public static void prepareMainLooper() {
  //創建一個looper并存入ThreadlocalMap中
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
      //取出創建好的looper
        sMainLooper = myLooper();
    }
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }

//存入ThreadLocalMap中
sThreadLocal.set(new Looper(quitAllowed));

}</code></pre>

通過 prepare() 方法創建一個looper,在通過 myLooper() 方法取出來。再來看看Looper的構造

private Looper(boolean quitAllowed) {
    //構造一個消息隊列,復制給mQueue變量
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

看到這里是不是有一點眉目了?構造 looper 的同時構造了 MessageQueue ,構造 Handler 的時候獲取當前線程的 looper 同時獲取到了 消息隊列 ,這樣消息機制就聯系起來了。

消息循環

現在消息機制的三大主力(Handler、Looper、MessageQueue)都具備了,那handle是怎么發送消息,looper又是怎么取出消息,消息又是怎么被處理的呢?來看看Looper.loop()方法:

public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    } 
   final MessageQueue queue = me.mQueue;
    .....    
   for (; ; ) {
        Message msg = queue.next(); // might block
        if (msg == null) {      
            return;
        }
        msg.target.dispatchMessage(msg);
        .....    
  }
}

這個函數就是不斷的從消息隊列 mQueue 中去取下一個要執行的消息,如果消息為空就退出循環 (其實msg永遠不會為空,因為Message.next()也是一個for無限循環,里面判斷如果msg==null,continue繼續下一次循環) ,不為空就調用 target 的 dispatchMessage() 來執行消息, target 是一個 Handler ,也就是發送這個消息的 handler ,在后續消息發送中我們會看到targer是在哪里賦值的。

這個函數最重要的邏輯是 queue.next() ,它是在MessageQueue中實現的,用來取出消息隊列中的消息

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    //消息隊列等待時長
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //這是一個JNI方法,檢查消息隊列,取出消息并復制給mMessages
nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { . do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //如果當前時間小于消息的執行時間,消息隊列等待 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //否則就返回消息給looper處理 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // 如果消息隊列中沒有任何就無限等待,直到下一個消息到來,并喚醒消息隊列
nextPollTimeoutMillis = -1; }

    if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run.  Loop and wait some more. 
       mBlocked = true;    continue;
    }
     .......

    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
}

}</code></pre>

這個函數稍長,我們把它分解來看。

執行下面的JNI函數會檢查隊列中是否有消息, nextPollTimeoutMillis 表示隊列要等待的時間,初始為0,則不需要等待

nativePollOnce(ptr, nextPollTimeoutMillis);

等這個函數完成返回后,看消息隊列中是否有消息

if (msg != null) {
                if (now < msg.when) {
                    //如果當前時間小于消息的執行時間,消息隊列等待 
                   nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //否則就返回消息給looper處理 
                   mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            }

如果有消息,并且消息的執行時間大于當前時間, nextPollTimeoutMillis 重新賦值,等下一次 nativePollOnce(ptr, nextPollTimeoutMillis); 執行的時候就要等待到執行時間了。

nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

否則,就直接把消息返回,交給looper處理

else {
    mMessages = msg.next;
    msg.next = null;
    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
    msg.markInUse();
    return msg;
  }

如果整個隊列中沒有任何消息,就進入無限等待狀態,

// 如果消息隊列中沒有任何就無限等待,直到下一個消息到來,并喚醒消息隊列                
    nextPollTimeoutMillis = -1;

這里注意一點,所謂的等待是空閑等待,而不是忙等待。就是說如果應用程序注冊了 IdleHandler 接口來處理一些事情,那么就會先執行這里 IdleHandler ,因為在執行的過程中可能有新的消息加入隊列,所有需要重置等待時長為0,不在等待

if (pendingIdleHandlerCount <= 0) {
        // No idle handlers to run.  Loop and wait some more. 
           mBlocked = true;
          continue;
        }
         .......

    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;</code></pre> 

是不是有點頭暈?這一段邏輯的確是消息機制中最復雜的,沒明白的話可以多看一遍源碼。把這一部分弄懂了,后面消息的發送和處理就比較簡單了

消息發送

發送消息的函數有很多,但是最終都是調用的同一個函數

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

延時時間+當前時間組合成為這個消息的執行時間,然后調用 enqueueMessage

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

這里把當前 Handler 賦值給 message的target 變量,還記得 looper.loop() 函數里面如果 MessageQueue.next() 返回了消息就交給 msg.target.dispatchMessage() 處理,這個 target 就是發送消息的 handler 。這就解釋了為什么 handler 發送的消息一定會在 handler 中處理。

之后,消息交給了MessageQueue的enqueueMessage()方法處理

boolean enqueueMessage(Message msg, long when) {
      synchronized (this) {
        ......               
       msg.markInUse();
       msg.when = when;
       Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) { 
        //如果當前隊列中沒有任何消息,插入到消息隊列的頭部
           msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
        //否則,遍歷消息隊列,根據消息的執行時間,從小到大的順序插入到合適的位置
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (; ; ) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
             msg.next = p;
             prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

這個函數會根據消息的執行時間,從小到大的順序把消息插入到消息隊列中。如果消息隊列處于等待狀態的,還需要喚醒消息隊列。喚醒的函數 nativeWake(mPtr); 是一個JNI方法,我們后續再說。現在只要知道喚醒后,消息隊列又開始循環檢查消息就行了。

消息執行

通過上面我們知道了

  • handler發送消息
  • Message.next()不斷檢測消息
  • 檢測到消息后返回給looper.loop()
  • 交給msg.target.dispatchMessage()處理消息

我們也知道了這個target就是發送消息的那個handler,再來看看它是怎么處理消息的

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

這個函數就很簡單了,如果構造 handler 的時候有 callback 就執行callback,否則就執行 handleMessage() ,執行我們自己的邏輯。

好了,我們已經分析完 Handler、Looper、MessageQueue 的創建和它們在消息機制中扮演的角色,認真閱讀這篇文章,我相信你會對android的消息機制有一個比較全面的認識。下面我們看看關于消息機制一些比較有意思的問題

既然 Looper.loop()會阻塞主線程,那為什么不會導致ANR?

這個問題可以通過2個方面來說

1.主線程一定也必須要阻塞。android主線程是伴隨著程序的開啟就啟動,退出才結束的。而不管是進程還是線程,對于Linux系統來說都是一段可執行的代碼,如果不阻塞,主線程代碼執行完畢就結束了,那我們的程序就可能用著用著就退出了。所以android主線程一定是阻塞的。

2.至于為什么不會ANR。先看看會導致ANR的情況

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

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

2.當前的事件正在處理,但沒有及時完成

開篇我們說過android系統是基于消息驅動的,不管是點擊一個按鈕需要獲得反饋還是activity生命周期,其實都是通過發送一個消息交給對應handler處理的。所以,主線程阻塞( 其實是消息隊列進入等待狀態 ),說明沒有任何事件(觸摸,點擊,生命周期切換)發生,沒有消息需要處理,當然不會ANR

當消息隊列沒有消息了,Looper.loop()中的for()死循環會退出嗎

閱讀這篇文章后,我們知道了,Looper.loop()是肯定不會退出的,不然下一條消息到來了怎么處理呢?沒有消息,它就會阻塞在 Message.next() 里面的 nativePollOnce(ptr, nextPollTimeoutMillis); 本地方法里面,即 nextPollTimeoutMillis==-1

 

來自:http://www.jianshu.com/p/ea71e5921d83

 

 本文由用戶 mochihanbing 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!