Android消息機制——Handler分析

前言

Android的消息機制主要是指Handler的運行機制,Handler的運行需要底層的MessageQueue和Looper的支撐。Handler 是Android 消息機制的上層接口,這使得在開發過程中只需要和Handler交互即可。通過Handler 可以將一個任務切換到Handler所在的線程中去執行。

機制組成部分

分析Handler機制原理必然涉及ActivityThread,Handler、MessageQueue,Looper,Message等重要類。對這些類作簡要介紹:

  • ActivityThread
    程序的啓動入口,這就是主線程(UI線程)。ActivityThread被創建時就會初始化Looper,這也就是在主線程中默認可以使用Handler的原因。注意:線程默認沒有Looper的,如果需要使用Handler就必須爲線程創建Looper。
  • MessageQueue
    消息隊列,內部儲存了一組消息,以隊列的形式對外提供插入和刪除的工作。內部存儲結構不是真正的隊列,採用單鏈表的數組結構來存儲消息列表。
  • Looper
    消息循環,由於MessageQueue只是一個消息的存儲單元,它不能去處理消息,而Looper會以無限循環的形式去查找是否有新消息,如果有就處理消息,否則就一直等待。
  • ThreadLocal
    它不是線程,作用是可以在每個線程中存儲數據。ThreadLocal可以在不同的線程中互不干擾地存儲並提供數據,通過ThreadLocal可以獲取每個線程的Looper。

在Android消息機制中,Handler各個部分運行關係,如下圖:
Handler機制圖

ActivityThread

應用程序的入口是在ActivityThread的main方法中,下面是源碼分析

public static void main(String[] args) {
    ......
    //1 創建Looper 和 MessageQueue
    Looper.prepareMainLooper();
      //2 建立與AMS的通信
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    ......
    //3 無限循環,不斷取出消息,向Handler分發
    Looper.loop();
    
    //可以看出來主線程也是在無限的循環的,
    //異常退出循環的時候會報錯. 
    throw new RuntimeException("Main thread loop 
    unexpectedly exited");
}

我們應該知道,如果程序沒有死循環的話,執行完main函數以後就會立馬退出了。之所以我們的APP能夠一直運行着,就是因爲Looper.loop()裏面是一個死循環。

Looper 和 ThreadLocal

1.Looper 類分析

public final class Looper {
    // 每個線程都有一個ThreadLocal,用來保存Looper對象
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class
    // 保存消息隊列
    final MessageQueue mQueue;
    // 保存線程
    final Thread mThread;
    ......
    public static void prepare() {
        prepare(true);
    }
    
    //prepare 函數 
    private static void prepare(boolean quitAllowed) {
        //判斷sThreadLocal.get()是否爲空,如果不爲空說明已經爲該線程設Looper,不能重複設置。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //如果sThreadLocal.get()爲空,說明還沒有爲該線程設置Looper,那麼創建Looper並設置
        sThreadLocal.set(new Looper(quitAllowed));
    }
    //ActivityThread 調用Looper.prepareMainLooper();該函數調用prepare(false);
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
    // 獲取當前線程Looper 
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    ......
    public static void loop() {
    //得到Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //得到MessageQueue
    final MessageQueue queue = me.mQueue;
    ......
    for (;;) {//無限循環
        Message msg = queue.next(); // 取下一個Message 可能阻塞在這裏
        if (msg == null) {
            //如果隊列爲空直接return直接結束了該方法,即循環結束
            return;
        }
        ......
        try {
            //分發message (target指handler)
            msg.target.dispatchMessage(msg);
            ......
        } finally {
        }
        ......
    }
}

從源碼中可知,在Looper.loop() 方法中會取出內部的MessageQueue,並且迭代消息隊列裏面的消息,根據消息的target分發消息(會到Handler類中handleMessage方法中)。同時,源碼中for()循環是死循環,爲什麼不會導致應用卡死?

對於線程即是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出,例如,binder線程也是採用死循環的方法,通過循環方式不同與Binder驅動進行讀寫操作,當然並非簡單地死循環,無消息時會休眠

但這裏可能又引發了另一個問題,既然是死循環又如何去處理其他事務呢?

通過創建新線程的方式。真正會卡死主線程的操作是在回調方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,looper.loop本身不會導致應用卡死。

主線程的死循環一直運行是不是特別消耗CPU資源呢?

其實不然,這裏就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裏,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入數據來喚醒主線程工作。這裏採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

2.ThreadLocal 類分析

ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定線程中可以獲取到存儲的數據,對於其他線程來說則無法獲取到數據。

public class ThreadLocal<T> {
     ....
      public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
     
      public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
     ....
}

從ThreadLocal的set和get方法可以看出,它們所操作對象都是當前線程的localValues對象的table數組,因此在不同線程中訪問同一個ThreadLocal的set和get方法,他們對ThreadLocal所做的讀寫操作僅限於各自線程的內部,這就是ThreadLocal可以在多個線程中互不干擾地存儲和修改數據。

3.Thread 、ThreadLocal 和Looper 關係

Looper 類源碼

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// prepare() 方法中Looper 對象存儲到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
-----------------------------------------------------------------------------------
ThreadLocal 類源碼

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
//這是ThreadLocal的getMap方法
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

小結: 從以上源碼分析可知,map.set(this, value)通過把自身(ThreadLocal)以及值(Looper)放到了一個Map裏面,如果再放一個的話,就會覆蓋,因爲map不允許鍵值對中的鍵是重複的。所以,ThreadLocal通過get()和set()方法就可以綁定Thread和Looper。

MessageQueue

MessageQueue主要包含2個操作:插入和讀取。插入和讀取對應的方法分別爲enqueueMessage和next,其中enqueueMessage的作用是往消息隊列中插入一條消息,而next的作用是從消息隊列中取出一條消息並將其從消息隊列中移除。消息隊列的內部實現是通過一個單鏈表的數據結構來維護消息列表,單鏈表在插入和刪除上比較有優勢。

boolean enqueueMessage(Message msg, long when) {
      ......
    synchronized (this) {
       ......
        msg.when = when;
        Message p = mMessages;
        //檢測當前頭指針是否爲空(隊列爲空)或者沒有設置when 或者設置的when比頭指針的when要前
        if (p == null || when == 0 || when < p.when) {
            //插入隊列頭部,並且喚醒線程處理msg
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
           // 幾種情況要喚醒線程處理消息:1)隊列是堵塞的 2)barrier,頭部結點無target 3)當前msg是堵塞的
            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; // 將當前msg插入第一個比其when值大的結點前。
            prev.next = msg;
        }
        //調用Native方法進行底層操作,在這裏把那個沉睡的主線程喚醒
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Message next() {
     .....
     for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // 獲取當前消息
                Message msg = mMessages;
                //  這裏是異步消息
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 獲取消息,並把消息隊列移動到下一條消息
                        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 {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                ......
}

Handler 分析

handler的工作主要包含消息的發送和接收過程。post的一系列方法最終是通過send的一系列方法來實現。handler發消息的過程是向消息隊列中插入了一條消息。MessageQueue的next方法就會返回這條消息給Looper,Looper收到消息後就開始處理了,最終消息由Looper交由Handler處理,即Handler 的dispathchMessage方法會被調用,這時handler就進入了處理消息的階段。


public void dispathchMessage(Message msg){
    // 檢查Message的callback是否爲null,不爲null就通過handleCallback來處理消息
    // Message的callback是一個Runnable對象,實際上就是handler的post方法所傳遞Runnable參數
    if(msg.callback!=null){
        handleCallback(msg);
    }else{
    // 檢查Handler 的mCallback是否爲null,不爲null就調用mCallback的
    // handleMessage
        if(mCallback!=null){
            if(mCallback.handleMessage(msg))
            return;
        }
        handleMessage(msg);
    }
}

總結

通過handler的post方法將一個Runnable投遞到handler 內部的Looper 中去處理,也可以通過handler的send一個消息,這個消息同樣會在Looper中去處理。其實post 方法最終也是通過send方法來完成的。當handler的send方法被調用時,它會調用MessageQueue的enqueueMessage 方法將這個消息放入消息隊列中,然後Looper發現有新消息到來時,就會處理這個消息,最終消息中的Runnable或者handler的handleMessage 方法就會被調用。注意Looper 是運行在創建Handler 所在的線程中的,這樣一來handler中的業務邏輯就被切換到創建handler所在的線程中去執行了。

參考

Android 源碼分析之旅3.1–消息機制源碼分析
Android 消息機制——你真的瞭解Handler?
一步一步分析Android的Handler機制

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