1. 程式人生 > 實用技巧 >基於Android9.0,瞭解Android訊息機制

基於Android9.0,瞭解Android訊息機制

基於Android9.0,瞭解Android訊息機制

​ 還是那句話:點成線,線成面,切勿貪心,否則一臉懵逼

​ 由於Android的主執行緒(UI執行緒)是非安全的,而且Android開發規範的限制,不能在UI子執行緒中訪問UI控制元件,否則就會出發程式異常。這個時候,就可以通過Handler來將更新UI的操作切換到UI執行緒中執行。因此,Handler被大家經常用來更新UI,但是從本質上說,Handler並不是專門用於更新UI的。

​ Android的訊息機制主要是指Handler的執行機制,然後得到MessageQueue、Looper的支撐。

接下來,我們來了解這三個類

  • Handler
  • ThreadLocal
  • MessageQueue
  • Looper

還是從原始碼出發。

Handler有兩種傳送訊息的方式

第一種

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {//重寫Handler的handlerMessage方法,因為Handler的handlerMessage是一個空實現
        super.handleMessage(msg);
    }
};
Message msg = Message.obtain();//或者new Message();
msg.what = 1;
msg.obj  = "fat";
handler.sendMessage(msg);

Handler的handleMessage方法
/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {//空實現
    }

先看Handler原始碼

public Handler() {
        this(null, false);
    }
public Handler(Callback callback, boolean async) {
    ...
    mLooper = Looper.myLooper();//注意這裡,獲取當前執行緒的Looper。至於什麼時候建立的,這條路走通後,會來說明
    if (mLooper == null) {//如果沒有獲取到Looper,會異常。這裡證明:執行緒沒有Looper物件,無法建立Handler
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//把執行緒的MessageQueue與Handler繫結。MessageQueue原理,下面說介紹
    mCallback = callback;
    mAsynchronous = async;
}

看原始碼發現,該構造方法,就是獲取當前執行緒的Looper,然後與Handler繫結。

下面看Message msg = Message.obtain();//或者new Message();

public static Message obtain() {//從Message池裡獲取
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;//Message池是連結串列結構,獲取效率高與陣列。
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();//池裡沒有,還是會new Message()
}

handler.sendMessage(msg);

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

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

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;//得到構造方法時的訊息佇列
        if (queue == null) {//為空拋異常
            ...
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

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

接下來,看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) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
            //這裡鏈式插入頭部
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            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; // invariant: p == prev.next
            prev.next = msg;
            //這裡鏈式插入。msg插入到p的後面
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

這裡,就完成了Handler的sendMessage,最後enqueueMessage到MessageQueue中。

第二種

Handler handler = new Handler();
handler.post(new Runnable() {
    @Override
    public void run() {
        
    }
});
或
Handler handler = new Handler();//Lambda表示式
        handler.post(()->{

        });
        

建立Handler和第一種方式一樣,直接看post

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

sendMessageDelayed後的流程,和第一方式一樣,我們來看不同點,看getPostMessage

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;//這裡把我們建立的Runnable,賦值給callback
    return m;// 返回帶有callback的Message
}

到這裡,Handler基本結束。

接下來,我們先來看看ThreadLocal,為什麼需要先看ThreadLocal呢?

因為涉及到如何保證一個執行緒對應一個Looper,並且各個執行緒之間的Looper互不干擾,所以我們先來了解ThreadLocal。

ThreadLocal

ThreadLocal是一個泛型類

public class ThreadLocal<T> {

然後,看看類結構(基於9.0),發現,有get、set、remove等方法,應該能感覺到,這個類大方向的作用。

我們先來看看get

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();
}

我們先看看getMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

繼續看threadLocals

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

到這裡,發現getMap返回的就是當前執行緒的ThreadLocal.ThreadLocalMap。然後,我們去看ThreadLocalMap的getEntry方法

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);//注意這裡
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);//注意這裡
}

threadLocalHashCode是一個雜湊值,防止相同的執行緒獲取同一個物件。

/**
 * ThreadLocals rely on per-thread linear-probe hash maps attached
 * to each thread (Thread.threadLocals and
 * inheritableThreadLocals).  The ThreadLocal objects act as keys,
 * searched via threadLocalHashCode.  This is a custom hash code
 * (useful only within ThreadLocalMaps) that eliminates collisions
 * in the common case where consecutively constructed ThreadLocals
 * are used by the same threads, while remaining well-behaved in
 * less common cases.
 */
private final int threadLocalHashCode = nextHashCode();

這裡的table是一個Entry陣列

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 */
private Entry[] table;

Entry是ThreadLocalMap一個內部類

static class ThreadLocalMap {
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

到這裡,我們可以得出結論,ThreadLocal的get方法,獲取的是:當前執行緒下的ThreadLocalMap裡面的table中的item,至於這個item裡面具體是什麼,我們去看看set()方法,繼續

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//這裡上面分析過了,就是獲取當前執行緒下的threadLocals
    if (map != null)
        map.set(this, value);//注意這裡
    else
        createMap(t, value);//注意這裡
}

繼續createMap

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

這裡我們發現,當前執行緒下threadLocals為null的話,會構造一個ThreadLocalMap,且初始化table。我們再看看map的set

private void set(ThreadLocal<?> key, Object value) {
...
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

這裡,我們得出結論:ThreadLocal主要是維護當前執行緒下的ThreadLocal.ThreadLocalMap,每次get或set時,都會先通過當前執行緒的雜湊&內部table陣列長度拿到table的index值,而當前執行緒就為這個table item的key。

至於table如何確定位置、如何防止重複、如何擴容,有時間再慢慢學習,還是那句話:先點成線,再線成面,切勿貪心,否則一臉懵逼。

好了,現在來看Looper

Looper

在Handler構造中,有這麼一句話

mLooper = Looper.myLooper();//注意這裡
if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread " + Thread.currentThread()
                + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//這裡關聯handler和looper的MessageQueue

看myLooper

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
//Looper類中申明sThreadLocal
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

這裡發現一個問題,在整個過程中,沒發現sThreadLocal呼叫set程式碼,why?

這裡2個點,注意:

1.看胖子之前的文章Android啟動流程,可知。在呼叫ActivityThread的main方法時,會呼叫Looper.prepareMainLooper(),這裡就建立了主執行緒的Looper。
2.子執行緒,需要手動呼叫Looper的prepare方法來建立Looper,否則會丟擲異常,看Handler原始碼可知。

這裡,我們以主執行緒為主,來看看ActivityThread的main方法

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();//注意這裡
...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
...
    Looper.loop();//注意這裡,開啟訊息迴圈

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

來看看Looper.prepareMainLooper

public static void prepareMainLooper() {
    prepare(false);//注意這裡
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();//上面已經介紹過了,會呼叫sThreadLocal.get()
    }
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

到這裡,真相大白了。現在剩下最後二個問題:MessageQueue以及三者如何貫穿起來的,分析原始碼雖然很枯燥,不過貌似看完受益匪淺,內功會提升很多。

MessageQueue

在new Looper時,會建立MessageQueue,我們來看看Looper

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);//建立MessageQueue
    mThread = Thread.currentThread();//關聯當前執行緒
}

看看構造方法MessageQueue

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;//用於標示訊息佇列是否可以被關閉,主執行緒的訊息佇列不可關閉
    mPtr = nativeInit();//native方法
}

這裡很簡單,就兩行程式碼,這裡還是先看這條線,至於MessageQueue的內部實現,有時間,再來和大家一起研究、學習,這裡提一句,MessageQueue內部是以連結串列的形式。

接下來,來看看幾個類,如何貫穿、運作起來的。

全面貫穿

記得ActivityThread,main方法嗎?裡面呼叫了Looper.loop(),來看看。

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;//獲取對應的訊息佇列
     ...

    for (;;) {//訊息迴圈
         
         //取出訊息
        Message msg = queue.next(); // might block  注意這裡,可能會阻塞
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
...
        try {
            msg.target.dispatchMessage(msg);//注意這裡,msg.target為對應的Handler
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            ...
        }
        ...

        msg.recycleUnchecked();
    }
}

先看queue.next()

Message next() {
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
     // nextPollTimeoutMillis該引數用於確定訊息佇列中是否還有訊息,從而決定訊息佇列應處於出隊訊息狀態 or 等待狀態。
     //nextPollTimeoutMillis為-1,訊息佇列處於等待狀態
    for (;;) {//死迴圈獲取message
         ...
        //阻塞操作,當等待超時或者訊息佇列被喚醒,才會繼續
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
           ...
            Message prevMsg = null;
            Message msg = mMessages;
            ...
            if (msg != null) {
                if (now < msg.when) {
                    ...
                } else {
                    // Got a message.
                    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;
            }
...
    }
}

記住:點成線,線成面,切勿貪心,否則一臉懵逼。先不要去管為什麼死迴圈不會造成執行緒阻塞,後續再走這條線。

接著再來看dispatchMessage

//如果你記得handler的兩種方式(忘記了,看上面),就知道callback代表什麼了
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {//使用post方式
        handleCallback(msg);
    } else {//sengMessage方式
        if (mCallback != null) {//我們在構成handler時,mCallback傳入的null
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//注意這裡
    }
}

先看msg.callback == null的時候,呼叫的handleMessage

/**
 * Subclasses must implement this to receive messages.子類必須實現它來接收訊息。
 */
public void handleMessage(Message msg) {
}

這裡是一個空實現,子類需要覆蓋,這裡其實就是我們構造Handler時的

![image-20200717182455681](/Users/King/Library/Application Support/typora-user-images/image-20200717182455681.png)

再來看看msg.callback != null的時候,handleCallback方法

private static void handleCallback(Message message) {
    message.callback.run();
}

message.callback為我們傳入的Runnable,所以這裡最後呼叫傳入的Runnable的run方法。

至此,handler機制差不多完結了。


關於總結:還是不借鑑各路大神的blog總結了。胖子覺得這樣會造成部分朋友只看總結,不看內容,最後變成知其然不知其所以然(胖子就吃過很多這樣的虧),還是交給朋友們自行總結吧。

胖子總結

  • 先搞清楚Handler、Looper、ThreadLocal、MessageQueue的基本實現
  • 自己用工具畫一畫它們之間的關係
  • 溫馨提示:點成線,線成面,切勿貪心,否則一臉懵逼
  • 胖子有什麼理解錯誤的,歡迎大家指出來,一起討論、學習、進步
  • 期待胖子的第三篇《Android事件分發(一)》

參考文獻

Android Handler:手把手帶你深入分析 Handler機制原始碼