天天看點

Handler消息機制詳解Handler消息機制詳解(29題)

轉載請注明連結:https://blog.csdn.net/feather_wch/article/details/79263855

詳解Handler消息機制,包括Handler、MessageQueue、Looper和LocalThread。

本文是我一點點歸納總結的幹貨,但是難免有疏忽和遺漏,希望不吝賜教。

Handler消息機制詳解(29題)

版本:2018/9/7-1(2359)

  • Handler消息機制詳解(29題)
    • 問題彙總
    • 消息機制概述(8)
    • ThreadLocal(4)
    • MessageQueue(3)
    • Looper(8)
      • 主線程的消息循環
    • Handler(6)
      • 記憶體洩漏

問題彙總

以問答形式将本文的所有内容進行彙總。考考你喲。
  1. Handler是什麼?
  2. 消息機制是什麼?
  3. MeesageQueue是什麼?
  4. Looper是什麼?
  5. ThreadLocal是什麼?
  6. 為什麼不能在子線程中通路UI?
  7. Handler的運作機制概述(Handler的建立、Looper的建立、MessageQueue的建立、以及消息的接收和處理)
  8. 主線程向子線程發送消息的方法?
  9. ThreadLocal的作用?
  10. ThreadLocal的應用場景
  11. ThreadLocal的使用
  12. ThreadLocal的set()源碼分析
  13. ThreadLocal的get()源碼分析
  14. MessageQueue的主要操作
  15. MessageQueue的插入和讀取源碼分析
  16. MessageQueue的next源碼詳解
  17. Looper的構造中做了什麼?
  18. 子線程中如何建立Looper?
  19. 主線程ActivityThread中的Looper是如何建立和擷取的?
  20. Looper如何退出?
  21. quit和quitSafely的差別
  22. Looper的loop()源碼中的4個工作?
  23. Android如何保證一個線程最多隻能有一個Looper?
  24. Looper的構造器為什麼是private的?
  25. Handler消息機制中,一個looper是如何區分多個Handler的?
  26. 主線程ActivityThread的消息循環
  27. ActivityThread中Handler H是做什麼的?
  28. Handler的post/send()邏輯流程
  29. Handler的postDelayed邏輯流程
  30. Handler的消息處理的流程?
  31. Handler的特殊構造方法中,為什麼有Callback接口?
  32. Handler的記憶體洩漏如何避免?

消息機制概述(8)

1、Handler是什麼?

  1. Android消息機制的上層接口(從開發角度)
  2. 能輕松将任務切換到Handler所在的線程中去執行
  3. 主要目的在于解決在子線程中無法通路UI的沖突

2、消息機制?

  1. Android的消息機制

    主要就是指

    Handler的運作機制

  2. Handler

    的運作需要底層

    MessageQueue

    Looper

    的支撐

3、MeesageQueue是什麼?

  1. 消息隊列
  2. 内部存儲結構并不是真正的隊列,而是單連結清單的資料結構來存儲消息清單
  3. 隻能存儲消息,而不能處理

4、Looper是什麼?

  1. 消息循環
  2. Looper

    以無限循環的形式去查找是否有新消息,有就處理消息,沒有就一直等待着。

5、ThreadLocal是什麼?

  1. Looper

    中一種特殊的概念
  2. ThreadLocal

    并不是線程,作用是可以在每個線程中互不幹擾的

    存儲資料

    提供資料

  3. Handler

    建立時會采用目前線程的

    Looper

    來構造消息循環系統,

    Handler

    内部就是通過

    ThreadLocal

    來擷取目前線程的

    Looper

  4. 線程預設是沒有

    Looper

    的,如果需要使用

    Handler

    就必須為線程建立

    Looper

  5. UI線程

    就是

    ActivityThread

    ,被建立時會初始化

    Looper

    ,是以

    UI線程

    中預設是可以使用

    Handler

6、為什麼不能在子線程中通路UI?

ViewRootImpl會對UI操作進行驗證,禁止在子線程中通路UI:
void checkThread(){
  if(mThread != Thread.currentThread()){
    throw new CalledFromWrongThreadException("Only th original thread that created a view hierarchy can touch its views");
  }
}
           

7、Handler的運作機制概述(Handler的建立、Looper的建立、MessageQueue的建立、以及消息的接收和處理)

  1. Handler建立時會采用目前線程的Looper
  2. 如果目前線程沒有

    Looper

    就會報錯,要麼建立

    Looper

    ,要麼在有

    Looper

    的線程中建立

    Handler

  3. Handler

    post

    方法會将一個

    Runnable

    投遞到

    Handler

    内部的

    Looper

    中處理(本質也是通過

    send

    方法完成)
  4. Handler

    send

    方法被調用時,會調用

    MessageQueue

    enqueueMessage

    方法将消息放入消息隊列, 然後

    Looper

    發現有新消息到來時,就會處理這個消息,最終消息中的

    Runnable

    或者

    Handler

    handleMessage

    就會被調用
  5. 因為

    Looper

    是運作在建立

    Handler

    所在的線程中的,是以通過

    Handler

    執行的業務邏輯就會被切換到

    Looper

    所在的線程中執行。

8、主線程向子線程發送消息的方法?

  1. 通過在主線程調用子線程中Handler的post方法,完成消息的投遞。
  2. 通過

    HandlerThread

    實作該需求。

ThreadLocal(4)

1、ThreadLocal的作用

  1. ThreadLocal

    是線程内部的資料存儲類,可以在指定線程中存儲資料,之後隻有在指定線程中才開業讀取到存儲的資料
  2. 應用場景1:某些資料是以線程為作用域,并且不同線程具有不同的資料副本的時候。

    ThreadLocal

    可以輕松實作

    Looper

    線上程中的存取。
  3. 應用場景2:在複雜邏輯下的對象傳遞,通過

    ThreadLocal

    可以讓對象成為線程内的全局對象,線程内部通過

    get

    就可以擷取。

2、ThreadLocal的使用

mBooleanThreadLocal.set(true);
Log.d("ThreadLocal", "[Thread#main]" + mBooleanThreadLocal.get());

new Thread("Thread#1"){
    @Override
    public void run(){
        mBooleanThreadLocal.set(false);
        Log.d("ThreadLocal", "[Thread#1]" + mBooleanThreadLocal.get());
    }
}.start();

new Thread("Thread#2"){
    @Override
    public void run(){
        Log.d("ThreadLocal", "[Thread#2]" + mBooleanThreadLocal.get());
    }
}.start();
           
  1. 最終

    main

    中輸出

    true

    ;

    Thread#1

    中輸出

    false

    ;

    Thread#2

    中輸出

    null

  2. ThreadLocal

    内部會從各自線程中取出數組,再根據目前

    ThreadLocal

    的索引去查找出對應的

    value

    值。

3、ThreadLocal的set()源碼分析

//ThreadLocal.java
public void set(T value) {
    //1. 擷取目前線程
    Thread t = Thread.currentThread();
    //2. 擷取目前線程對應的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //3. map存在就進行存儲
        map.set(this, value);
    else
        //4. 不存在就建立map并且存儲
        createMap(t, value);
}
//ThreadLocal.java内部類: ThreadLocalMap
private void set(ThreadLocal<?> key, Object value) {
    //1. table為Entry數組
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-);
    //2. 根據目前ThreadLocal擷取到Hash key,并以此從table中查詢出Entry
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //3. 如果Entry的ThreadLocal與目前的ThreadLocal相同,則用新值覆寫e的value
        if (k == key) {
            e.value = value;
            return;
        }
        //4. Entry沒有ThreadLocal則把目前ThreadLocal置入,并存儲value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //5. 沒有查詢到Entry,則建立Entry并且存儲value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
//ThreadLocal内部類ThreadLocalMap的靜态内部類
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
           

4、ThreadLocal的get()源碼分析

public T get() {
    //1. 擷取目前線程對應的ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //2. 取出map中的對應該ThreadLocal的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            //3. 擷取到entry後傳回其中的value
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //4. 沒有ThreadLocalMap或者沒有擷取到ThreadLocal對應的Entry,傳回規定數值
    return setInitialValue();
}
private T setInitialValue() {
    //1. value = null
    T value = initialValue();//傳回null
    Thread t = Thread.currentThread();
    //2. 若不存在則新ThreadLocalMap, 在裡面以threadlocal為key,value為值,存入entry
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}
           
  1. 目前線程對應了一個

    ThreadLocalMap

  2. 目前線程的

    ThreadLocal

    對應一個Map中的

    Entry

    (存在table中)
  3. Entry

    key

    會擷取其對應的ThreadLocal,

    value

    就是存儲的數值

MessageQueue(3)

1、MessageQueue的主要操作

  1. enqueueMessage

    : 往消息隊列中插入一條消息
  2. next

    :取出一條消息,并且從消息隊列中移除
  3. 本質采用

    單連結清單

    的資料結構來維護消息隊列,而不是采用隊列

2、MessageQueue的插入和讀取源碼分析

//MessageQueue.java:插入資料
boolean enqueueMessage(Message msg, long when) {
    //1. 主要就是單連結清單的插入操作
    synchronized (this) {
        ......
    }
    return true;
}
/**==========================================
 * 功能:讀取并且删除資料
 * 内部是無限循環,如果消息隊列中沒有消息就會一直阻塞。
 * 一旦有新消息到來,next方法就會傳回該消息并且将其從單連結清單中移除
 *===========================================*/
Message next() {
    for (;;) {
        ......
    }
}
           

3、MessageQueue的next源碼詳解

Message next() {
        int nextPollTimeoutMillis = ;
        for (;;) {
            /**======================================================================
             * 1、精确阻塞指定時間。第一次進入時因為nextPollTimeoutMillis=0,是以不會阻塞。
             *   1-如果nextPollTimeoutMillis=-1,一直阻塞不會逾時。
             *   2-如果nextPollTimeoutMillis=0,不會阻塞,立即傳回。
             *   3-如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(逾時),如果期間有程式喚醒會立即傳回。
             *====================================================================*/
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 目前時間
                final long now = SystemClock.uptimeMillis();
                Message msg = mMessages;
                /**=======================================================================
                 * 2、目前Msg為消息屏障
                 *   1-說明有重要的異步消息需要優先處理
                 *   2-周遊查找到異步消息并且傳回。
                 *   3-如果沒查詢到異步消息,會continue,且阻塞在nativePollOnce直到有新消息
                 *====================================================================*/
                if (msg != null && msg.target == null) {
                   // 周遊尋找到異步消息,或者末尾都沒找到異步消息。
                    do {
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                /**================================================================
                 *  3、擷取到消息
                 *    1-消息時間已到,傳回該消息。
                 *    2-消息時間沒到,表明有個延時消息,會修正nextPollTimeoutMillis。
                 *    3-後面continue,精确阻塞在nativePollOnce方法
                 *===================================================================*/
                if (msg != null) {
                    // 延遲消息的時間還沒到,是以重新計算nativePollOnce需要阻塞的時間
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 傳回擷取到的消息(可以為一般消息、時間到的延遲消息、異步消息)
                        return msg;
                    }
                } else {
                    /**=============================
                     * 4、沒有找到消息或者異步消息
                     *==============================*/
                    nextPollTimeoutMillis = -;
                }

                /**===========================================
                 * 5、沒有擷取到消息,進行下一次循環。
                 *   (1)此時可能處于的情況:
                 *      1-沒有擷取到消息-nextPollTimeoutMillis = -1
                 *      2-沒有擷取到異步消息(接收到同步屏障卻沒找到異步消息)-nextPollTimeoutMillis = -1
                 *      3-延時消息的時間沒到-nextPollTimeoutMillis = msg.when-now
                 *   (2)根據nextPollTimeoutMillis的數值,最終都會阻塞在nativePollOnce(-1),
                 *      直到enqueueMessage将消息添加到隊列中。
                 *===========================================*/
                if (pendingIdleHandlerCount <= ) {
                    // 用于enqueueMessage進行精準喚醒
                    mBlocked = true;
                    continue;
                }
            }
        }
    }
           
  1. 如果是一般消息,會去擷取消息,沒有擷取到就會阻塞(native方法),直到enqueueMessage插入新消息。擷取到直接傳回Msg。
  2. 如果是同步屏障,會去循環查找異步消息,沒有擷取到會進行阻塞。擷取到直接傳回Msg。
  3. 如果是延時消息,會計算時間間隔,并進行精準定時阻塞(native方法)。直到時間到達或者被enqueueMessage插入消息而喚醒。時間到後就傳回Msg。

Looper(8)

1、Looper的構造

private Looper(boolean quitAllowed) {
    //1. 會建立消息隊列: MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    //2. 目前線程
    mThread = Thread.currentThread();
}
           

2、為線程建立Looper

//1. 在沒有Looper的線程建立Handler會直接異常
new Thread("Thread#2"){
    @Override
    public void run(){
        Handler handler = new Handler();
    }
}.start();
           

異常:

java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()

//2. 用prepare為目前線程建立一個Looper
new Thread("Thread#2"){
    @Override
    public void run(){
        Looper.prepare();
        Handler handler = new Handler();
        //3. 開啟消息循環
        Looper.loop();
    }
}.start();
           

3、主線程ActivityThread中的Looper

  1. 主線程中使用

    prepareMainLooper()

    建立

    Looper

  2. getMainLooper

    能夠在任何地方擷取到主線程的

    Looper

4、Looper的退出

  1. Looper

    的退出有兩個方法:

    quit

    quitSafely

  2. quit

    會直接退出

    Looper

  3. quitSafely

    隻會設定退出标記,在已有消息全部處理完畢後才安全退出
  4. Looper

    退出後,

    Handler

    的發行的消息會失敗,此時

    send

    傳回

    false

  5. 子線程

    中如果手動建立了

    Looper

    ,應該在所有事情完成後調用

    quit

    方法來終止消息循環

5、Looper的loop()源碼分析

//Looper.java
public static void loop() {
    //1. 擷取Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //2. 擷取消息隊列
    final MessageQueue queue = me.mQueue;
    ......
    for (; ; ) {
        //3. 擷取消息,如果沒有消息則會一直阻塞
        Message msg = queue.next();
        /**=================================
         * 4. 如果消息獲得為null,則退出循環
         * -Looper退出後,next就會傳回null
         *=================================*/
        if (msg == null) {
            return;
        }
        ......
        /**==========================================================
         * 5. 處理消息
         *  -msg.target:是發送消息的Handler
         *  -最終在該Looper中執行了Handler的dispatchMessage()
         *  -成功将代碼邏輯切換到指定的Looper(線程)中執行
         *========================================================*/
        msg.target.dispatchMessage(msg);
        ......
    }
}
           

6、Android如何保證一個線程最多隻能有一個Looper?

1-Looper的構造方法是private,不能直接構造。需要通過Looper.prepare()進行建立,
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
           
2-如果在已有Looper的線程中調用

Looper.prepare()

會抛出RuntimeException異常
public class Looper {

    static final HashMap<Long, Looper> looperRegistry = new HashMap<Long, Looper>();

    private static void prepare() {
        synchronized(Looper.class) {
            long currentThreadId = Thread.currentThread().getId();
            // 根據線程ID查詢Looper
            Looper l = looperRegistry.get(currentThreadId);
            if (l != null)
                throw new RuntimeException("Only one Looper may be created per thread");
            looperRegistry.put(currentThreadId, new Looper(true));
        }
    }
    ...
}
           

7、Handler消息機制中,一個looper是如何區分多個Handler的?

  1. Looper.loop()會阻塞于MessageQueue.next()
  2. 取出msg後,msg.target成員變量就是該msg對應的Handler
  3. 調用msg.target的disptachMessage()進行消息分發。這樣多個Handler是很容易區分的。

主線程的消息循環

8、主線程ActivityThread的消息循環

//ActivityThread.java
public static void main(String[] args) {
    //1. 建立主線程的Looper和MessageQueue
    Looper.prepareMainLooper();
    ......
    //2. 開啟消息循環
    Looper.loop();
}
/**=============================================
 * ActivityThread中需要Handler與消息隊列進行互動
 * -内部定義一系列消息類型:主要有四大元件等
 * //ActivityThread.java
 *=============================================*/
private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = ;
    public static final int PAUSE_ACTIVITY          = ;
    public static final int PAUSE_ACTIVITY_FINISHING= ;
    ......
}
           
  1. ActivityThread

    通過

    ApplicationThread

    AMS

    進行

    IPC通信

  2. AMS

    完成請求的工作後會回調

    ApplicationThread

    中的

    Binder

    方法
  3. ApplicationThread

    會向

    Handler H

    發送消息
  4. H

    接收到消息後會将

    ApplicationThread

    的邏輯切換到

    ActivityThread

    中去執行

Handler(6)

1、Handler使用執行個體post/sendMessage

post
handler.post(new Runnable() {
        @Override
        public void run() {

        }
});
           
sendMessage
// 自定義msg的what
static final int INT_WHAT_MSG = ;
// 0、兩種建立Msg的方法
Message message = new Message();
message = Message.obtain();
// 1、自定義MSG的類型,通過what進行區分
message.what = INT_WHAT_MSG;
// 2、發送Msg
handler.sendMessage(message);
// 3、自定義Handler處理Msg
class MsgHandler extends android.os.Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case INT_WHAT_MSG:
                    // 識别出了Msg,進行邏輯處理
                    break;
                default:
                    break;
            }
        }
}
           
post内部還是通過sendMessage實作的。

2、Handler的post/send()源碼分析

//Handler.java: post最終是通過send系列方法實作的
//Handler.java
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, );
}
//Handler.java
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < ) {
        delayMillis = ;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//Handler.java
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //1. 最終是向消息隊列插入一條消息
    return queue.enqueueMessage(msg, uptimeMillis);
}
           
  1. sendMessage()

    會将消息插入到

    消息隊列中

  2. MessageQueue

    next

    方法就會傳回這條消息交給

    Looper

  3. 最終

    Looper

    會把消息交給

    Handler

    dispatchMessage

3、Handler的postDelayed源碼分析

//Handler.java---層層傳遞,和一般的post調用的同一個底層方法.
    public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    //xxxxxx
    //Handler.java
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        ...
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    //MessageQueue.java
    boolean enqueueMessage(Message msg, long when) {
        //會直接加進隊列
    }
           
  1. postDelayed和post調用的底層sendMessage系列方法,隻不過前者有延遲,後者延遲參數=0。
  2. 最終會直接将Msg加入到隊列中。
  3. MessageQueue.next()在取出Msg時,如果發現消息A有延遲且時間沒到,會阻塞消息隊列。
  4. 如果此時有非延遲的新消息B,會将其加入消息隊列, 且處于消息A的前面,并且喚醒阻塞的消息隊列。
  5. 喚醒後會拿出隊列頭部的消息B,進行處理。然後會繼續因為消息A而阻塞。
  6. 如果達到了消息A延遲的時間,會取出消息A進行處理。

4、Handler的消息處理源碼

//Handler.java
public void dispatchMessage(Message msg) {
    //1. Msg的callback存在時處理消息——Handler的post所傳遞的Runnable
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        /**===============================================
         *2. mCallback不為null時調用handleMessage
         * -Handler handle = new Handler(callback)
         * -好處在于不需要派生Handler子類并且也不需要重寫其handleMessage
         *=============================================*/
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //3. 如果其他條件都不符合,最後會調用Handler的handleMessage進行處理
        handleMessage(msg);
    }
}
//Handler.java-調用Handler的post所傳遞的Runnable的run()方法
private static void handleCallback(Message message) {
    message.callback.run();
}
//Handler.java-Callback接口用于不需要派生Handler就能完成功能
public interface Callback {
    public boolean handleMessage(Message msg);
}
           

5、Handler的特殊構造方法

  1. Handler handle = new Handler(callback);

    -不需要派生Handler
  2. 通過特定

    Looper

    構造

    Handler

public Handler(Looper looper) {
    this(looper, null, false);
}
           
  1. 預設構造函數
public Handler(Callback callback, boolean async) {
    ......
    mLooper = Looper.myLooper();
    //1. 在沒有Looper的線程中建立Handler
    if (mLooper == null) {
        throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
           

記憶體洩漏

6、Handler的記憶體洩漏如何避免?

  1. 采用靜态内部類:

    static handler = xxx

  2. Activity結束時,調用

    handler.removeCallback()

    、然後handler設定為null
  3. 如果使用到Context等引用,要使用

    弱引用