詳解 Handler 訊息處理機制(附自整理超全 Q&A)

銀色子彈發表於2019-06-04

Android 為什麼要用訊息處理機制

如果有多個執行緒更新 UI,並且沒有加鎖處理,會導致介面更新的錯亂,而如果每個更新操作都進行加鎖處理,那麼必然會造成效能的下降。所以在 Android 開發中,為了使 UI 操作是執行緒安全的,規定只許主執行緒即 UI 執行緒可以更新 UI 元件。但實際開發中,常常會遇到多個執行緒併發操作 UI 元件的需求,於是 Android 提供了一套訊息傳遞與處理機制來解決這個問題。也就是在非主執行緒需要更新 UI 時,通過向主執行緒傳送訊息通知,讓主執行緒更新 UI。

當然訊息處理機制不僅僅可以用來解決 UI 執行緒安全問題,它同時也是一種執行緒之間溝通的方式。

Looper

Looper 即訊息迴圈器,是訊息處理機制的核心,它可以將一個普通執行緒轉換為一個 Looper 執行緒。所謂的 Looper 執行緒就是一個不斷迴圈的執行緒,執行緒不斷迴圈的從 MessageQueue 中獲取 Message,交給相應的 Handler 處理任務。在 Looper 類的註釋介紹中,我們可以得知通過兩個靜態方法 Looper.prepare()Looper.loop() 就可以將執行緒升級成 Looper 執行緒:

class LooperThread extends Thread {
    public Handler mHandler;
    
    public void run() {
        // 將當前執行緒初始化為Looper執行緒
        Looper.prepare();
        
        // 注意:一定要在兩個方法之間建立繫結當前Looper的Handler物件,
        // 否則一旦執行緒開始進入死迴圈就沒法再建立Handler處理Message了
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // 處理收到的訊息
                
            }
        };
        
        // 開始迴圈處理訊息佇列
        Looper.loop();
    }
}

Looper.prepare()

該方法線上程中將 Looper 物件定義為 ThreadLocal 物件,使得 Looper 物件成為該執行緒的私有物件,一個執行緒最多僅有一個 Looper。並在這個 Looper 物件中維護一個訊息佇列 MessageQueue 和持有當前執行緒的引用,因此 MessageQueue 也是執行緒私有。

public final class Looper {

    // 每個執行緒僅包含一個的Looper物件,定義為一個執行緒本地儲存(TLS)物件
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    // 每個Looper維護一個唯一的訊息佇列
    final MessageQueue mQueue;
    
    // 持有當前執行緒引用
    final Thread mThread;
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    public static void prepare() {
        prepare(true);
    }

    // 該方法會在呼叫執行緒的TLS中建立Looper物件,為執行緒私有
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            // 試圖在有Looper的執行緒中再次建立Looper物件將丟擲異常
            throw new RuntimeException(
                "Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
}

Looper.loop()

該方法啟動執行緒的迴圈模式,從 Looper 的 MessageQueue 中不斷的提取 Message,再交由 Handler 處理任務,最後回收 Message 供以後複用。

public static void loop() {
    // 得到當前執行緒的Looper物件
    final Looper me = myLooper();
    if (me == null) {
        // 執行緒沒有呼叫Looper.prepare(),所以沒有Looper物件
        throw new RuntimeException(
            "No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 得到當前訊息佇列
    final MessageQueue queue = me.mQueue;

    ...

    // 開始迴圈
    for (;;) {
        // 從訊息佇列中獲取下一個Message,該方法可以被阻塞
        Message msg = queue.next();
        
        ...
        
        // 將Message推送到Message中的target處理
        // 此處的target就是傳送該Message的Handler物件
        msg.target.dispatchMessage(msg);
        
        ...
        
        // 回收Message,這樣就可以通過Message.obtain()複用
        msg.recycleUnchecked();
    }
}

Handler

Handler 可以稱之為訊息處理者。Looper 執行緒不斷的從訊息佇列中獲取訊息,而向訊息佇列中推送訊息的正是 Handler 這個類。

Handler 在工作執行緒通過 sendMessage() 向 MessageQueue 中推送 Message,而主執行緒 Looper 迴圈得到 Message 後,即可得到發出該 Message 的 Handler 物件(Handler 傳送訊息時將自身引用賦值給 message.target),再通過 Handler 物件的 dispatchMessage()handleMessage() 方法處理相應的任務。這樣我們就可以通過 Handler 同時完成了非同步執行緒的訊息傳送與訊息處理兩個功能。

Handler 預設構造方法會關聯當前執行緒的 Looper 物件,一個執行緒只能有一個 Looper 物件,但可以有多個 Handler 關聯這個 Looper 物件。Handler 也提供一些構造方法關聯自定義的 Looper 物件。

public class Handler {
    
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    
    public Handler() {
        this(null, false);
    }
    
    public Handler(boolean async) {
        this(null, async);
    }
    
    public Handler(Callback callback) {
        this(callback, false);
    }
    
    public Handler(Looper looper) {
        this(looper, null, false);
    }
    
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    
    /**
     * @hide
     */
    public Handler(Callback callback, boolean async) {
        
        if (FIND_POTENTIAL_LEAKS) {
            // 如果開啟了FIND_POTENTIAL_LEAKS開關
            // 會判斷是否是非靜態匿名內部類、非靜態成員內部類或非靜態區域性內部類
            // 如果是,可能發生記憶體洩漏,發出警告
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() 
                    || klass.isMemberClass() 
                    || klass.isLocalClass()) 
                        && (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, 
                    "The following Handler class should be static or leaks might occur: " 
                    + klass.getCanonicalName());
            }
        }
        
        // 關聯到當前執行緒的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            // 當前執行緒未呼叫Looper.prepare(),不是Looper執行緒
            throw new RuntimeException(
                "Can't create handler inside thread "
                + Thread.currentThread()
                + " that has not called Looper.prepare()");
        }
        // 持有當前Looper中訊息佇列的引用
        mQueue = mLooper.mQueue;
        mCallback = callback;
        // 設定是否是非同步訊息
        mAsynchronous = async;
    }
    
    /**
     * @hide
     */
    public Handler(Looper looper, Callback callback, boolean async) {
        // 指定了Looper物件,Handler處理訊息就會在該Looper對應的執行緒中執行
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
}

sendMessage()

Handler 的功能之一就是向訊息佇列傳送訊息,其有多個方法可以實現這個功能,如 post()postAtTime()postDelayed()sendMessage()sendMessageAtTime()sendMessageDelayed() 等等。post 一類的方法傳送的是 Runnable 物件,send 一類的方法傳送的是 Message 物件,但實際上 Runnable 物件最後都會封裝成 Message 物件。

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

public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

// 將Runnable封裝成Message物件
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    // 將Runnable設為Message的callback變數
    m.callback = r;
    return m;
}

再看 sendMessage 類方法的原始碼:

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

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

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 核心程式碼,sendMessage一路呼叫到此,讓Message持有當前Handler的引用
    // 當訊息被Looper執行緒輪詢到時,可以通過target回撥Handler的handleMessage方法
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    // 將Message加入MessageQueue中,並根據訊息延遲排序
    return queue.enqueueMessage(msg, uptimeMillis);
}

dispatchMessage()handleMessage()

Handler 的另外一個主要功能就是處理訊息。處理訊息的核心程式碼即是 Looper.loop() 中的msg.target.dispatchMessage(msg) 以及 Handler 子類重寫實現的 handleMessage() 回撥方法。原始碼如下:

public interface Callback {
    public boolean handleMessage(Message msg);
}

// 處理訊息,該方法由Looper的loop方法呼叫
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 處理Runnable類的訊息
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // 這種方法允許讓Activity等來實現Handler.Callback介面
            // 避免了自己定義Handler重寫handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 根據Handler子類實現的回撥方法處理訊息
        handleMessage(msg);
    }
}

// 處理Runnbale類訊息的方法
private static void handleCallback(Message message) {
    // 直接呼叫Message中封裝的Runnable的run方法
    message.callback.run();
}

// 由Handler子類實現的回撥方法
public void handleMessage(Message msg) { }

通過上面對 Handler 核心原始碼的分析,我們可以看到其在非同步執行緒訊息處理機制中的重要作用。任何持有 Handler 引用的其他執行緒都可以傳送訊息,這些訊息都傳送到 Handler 內部關聯的 MessageQueue 中,而 Handler 是在 Looper 執行緒中建立的(必須在 Looper.prepare()呼叫之後,Looper.loop() 呼叫之前建立),所以 Handler 是在其關聯的 Looper 執行緒中處理取到的訊息。

正是由於 Handler 這種機制解決了 Android 在非主執行緒(UI 執行緒)更新 UI 的問題。由於 Android 主執行緒也是一個 Looper 執行緒,在主執行緒建立的 Handler 預設將關聯到主執行緒的 MessageQueue。我們可以在 Activity 中建立 Handler,並將其引用傳遞給工作執行緒,在工作執行緒執行完任務後,通過 Handler 傳送訊息通知 Activity 更新 UI。

Message

在訊息處理機制中,Message 扮演的角色就是訊息本身,它封裝了任務攜帶的額外資訊和處理該任務的 Handler 引用。

Message 雖然有 public 構造方法,但是還是建議使用 Message.obtain() 方法從一個全域性的訊息池中得到空的 Message 物件,這樣可以有效的節省系統資源。Handler 有一套 obtain 方法,其本質是其實是呼叫 Message 的一套 obtain 方法,最終都是呼叫 Message.obtain()

另外可以利用 message.what 來區分訊息型別,以處理不同的任務。在需要攜帶簡單的 int 額外資訊時,可以優先使用 message.arg1message.arg2,這比用 Bundle 封裝資訊更省資源。

MessageQueue

MessageQueue.enqueueMessage()

這個方法是所有訊息傳送方法最終呼叫的終點,也就是說無論怎麼傳送訊息,都會直接插入到對應的訊息佇列中去。並且在插入後還會根據一些判斷,來決定是否喚醒阻塞的佇列。

boolean enqueueMessage(Message msg, long when) {
    
    ...

    synchronized (this) {
        // 如果傳送訊息所在的執行緒已經終止,則回收訊息,返回訊息插入失敗的結果
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
        
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 佇列裡無訊息,或插入訊息的執行時間為0(強制插入隊頭),或插入訊息的執行
            // 時間先於隊頭訊息,這三種情況下插入訊息為新隊頭,如果佇列被阻塞則將其喚醒
            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;
        }
        
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            // Native方法喚醒等待的執行緒
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue.next()

該方法可以從訊息佇列中取出一個需處理的訊息,在沒有訊息或者訊息還未到時時,該方法會阻塞執行緒,等待訊息通過 MessageQueue.enqueueMessage() 方法入隊後喚醒執行緒。

Message next() {
    
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        
        // 通過native層的MessageQueue阻塞nextPollTimeoutMillis毫秒時間
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        synchronized (this) {
            // 嘗試檢索下一個訊息,如果找到則返回該訊息
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // 被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 {
                    // 從佇列中獲取一個要執行的訊息
                    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;
            }
            
            ...
            
            // 如果第一次遇到空閒狀態,則獲取要執行的IdleHandler數量
            // 僅當佇列為空或者佇列中的第一條訊息(可能是同步屏障)
            // 還沒到執行時間時,才會執行IdleHandler
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // 如果沒有IdleHandler需要執行,那麼就阻塞等待下一個訊息到來
                mBlocked = true;
                continue;
            }
            
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        
        // 執行IdleHandler
        // 執行一次next方法只有第一次輪詢能執行這裡的操作
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            
            boolean keep = false;
            try {
                // 執行IdleHandler,返回是否保留IdleHandler
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            
            if (!keep) {
                synchronized (this) {
                    // 如果不需要保留,則移除這個IdleHandler
                    mIdleHandlers.remove(idler);
                }
            }
        }
        
        // 設定為0保證在再次執行next方法之前不會再執行IdleHandler
        pendingIdleHandlerCount = 0;
        
        // 當呼叫一個IdleHandler執行後,無需等待直接再次檢索一次訊息佇列
        nextPollTimeoutMillis = 0;
    }
}

上面原始碼裡面的方法 nativePollOnce(ptr, nextPollTimeoutMillis) 是一個 Native 方法,實際作用就是通過 Native 層的 MessageQueue 阻塞 nextPollTimeoutMillis 毫秒的時間。

  • 如果 nextPollTimeoutMillis = -1,一直阻塞不會超時。
  • 如果 nextPollTimeoutMillis = 0,不會阻塞,立即返回。
  • 如果 nextPollTimeoutMillis > 0,最長阻塞 nextPollTimeoutMillis 毫秒(超時),如果期間有程式喚醒會立即返回。

Q&A

1. ThreadLocal 是什麼?

ThreadLocal 可以包裝一個物件,使其成為執行緒私有的區域性變數,通過 ThreadLocal 的 get 和 set 方法來訪問這個執行緒區域性變數,而不受到其他執行緒的影響。ThreadLocal 實現了執行緒之間的資料隔離,同時提升同一執行緒中該變數訪問的便利性。

2. postsendMessage 兩類傳送訊息的方法有什麼區別?

post 一類的方法傳送的是 Runnable 物件,但是其最後還是會被封裝成 Message 物件,將 Runnable 物件賦值給 Message 物件中的 callback 變數,然後交由 sendMessageAtTime() 方法傳送出去。在處理訊息時,會在 dispatchMessage() 方法裡首先被 handleCallback(msg) 方法執行,實際上就是執行 Message 物件裡面的 Runnable 物件的 run 方法。

sendMessage 一類的方法傳送的直接是 Message 物件,處理訊息時,在 dispatchMessage 裡優先順序會低於 handleCallback(msg) 方法,是通過自己重寫的 handleMessage(msg) 方法執行。

3. 為什麼要通過 Message.obtain() 方法獲取 Message 物件?

obtain 方法可以從全域性訊息池中得到一個空的 Message 物件,這樣可以有效節省系統資源。同時,通過各種 obtain 過載方法還可以得到一些 Message 的拷貝,或對 Message 物件進行一些初始化。

4. Handler 實現傳送延遲訊息的原理是什麼?

我們常用 postDelayed()sendMessageDelayed() 來傳送延遲訊息,其實最終都是將延遲時間轉為確定時間,然後通過 sendMessageAtTime() -> enqueueMessage -> queue.enqueueMessage 這一系列方法將訊息插入到 MessageQueue 中。所以並不是先延遲再傳送訊息,而是直接傳送訊息,再借由 MessageQueue 的設計來實現訊息的延遲處理。

訊息延遲處理的原理涉及 MessageQueue 的兩個靜態方法 MessageQueue.next()MessageQueue.enqueueMessage()。通過 Native 方法阻塞執行緒一定時間,等到訊息的執行時間到後再取出訊息執行。

5. 為什麼主執行緒不會因為 Looper.loop() 裡的死迴圈卡死?

主執行緒確實是通過 Looper.loop() 進入了迴圈狀態,因為這樣主執行緒才不會像我們一般建立的執行緒一樣,當可執行程式碼執行完後,執行緒生命週期就終止了。

而對於 CPU 來說無論是程式還是執行緒都是一段可執行程式碼,CPU 採用 CFS 排程演算法,保證每個執行任務都儘可能公平的享有 CPU 時間片段。所以只要建立了其他的新執行緒處理事務,主執行緒的迴圈就不會導致系統卡死。這裡有兩個問題:何時建立了其他的新執行緒運轉?主執行緒的迴圈會不會消耗大量的 CPU 資源?

  • 何時建立了其他的新執行緒運轉?
    實際上執行在主執行緒的 ActivityThread 的 main() 方法中就建立了新的 Binder 執行緒(即 ApplicationThread,用於接受系統服務 AMS 發來的事件):
public static void main(String[] args) {
    
    ...

    Looper.prepareMainLooper();

    ...
    
    ActivityThread thread = new ActivityThread();
    // 這一句建立了一個Binder執行緒
    // 這個執行緒可以通過 ActivityThread 裡的 Handler 將訊息傳送給主執行緒
    thread.attach(false, startSeq);

    ...
    
    Looper.loop();

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

一些 Activity 生命週期的訊息就是通過這個機制在迴圈外執行起來的。比如一條暫停 Activity 的訊息,首先是處於系統 system_server 程式的 ActivityManagerService(AMS)執行緒呼叫 ApplicationThreadProxy(ATP)執行緒,接著 ATP 執行緒通過 Binder 機制將訊息傳輸到 APP 程式中的 ApplicationThread(AT)執行緒,最後 AT 執行緒通過 Handler 訊息機制,將訊息傳送到了主執行緒的訊息佇列中,主執行緒通過 Looper.loop() 遍歷得到該訊息後,將訊息分發給了 ActivityThread 對應的 Handler 中處理,最後呼叫到了 Activity 的 onPause() 方法,方法處理完後,繼續主執行緒繼續迴圈下去。

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

6. 同步屏障 SyncBarrier 是什麼?有什麼作用?

Message 分為兩類:同步訊息、非同步訊息。在一般情況下,兩類訊息處理起來沒有什麼不同。只有在設定了同步屏障後才會有差異。同步屏障從程式碼層面上看是一個 Message 物件,但是其 target 屬性為 null,用以區分普通訊息。在 MessageQueue.next() 中如果當前訊息是一個同步屏障,則跳過後面所有的同步訊息,找到第一個非同步訊息來處理。

我們可以通過 MessageQueue 物件的 postSyncBarrier() 傳送一個同步屏障,通過 removeSyncBarrier(token) 移除同步屏障

Android 應用框架中為了更快的響應 UI 重新整理事件在 ViewRootImpl.scheduleTraversals() 中使用了同步屏障

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 設定同步障礙,確保mTraversalRunnable優先被執行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 內部通過Handler傳送了一個非同步訊息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

上面原始碼中的任務 mTraversalRunnable 呼叫了 performTraversals() 執行 measure()layout()draw() 等方法。為了讓 mTraversalRunnable 儘快被執行,在發訊息之前呼叫 MessageQueue 物件的 postSyncBarrier() 設定了一個同步屏障。

7. IdleHandler 是什麼?有什麼作用?

/**
 * 發現執行緒阻塞等待更多訊息時,回撥的介面
 */
public static interface IdleHandler {
    /**
     * 當訊息佇列沒有訊息並等待更多訊息時呼叫。
     * 返回true以保持IdleHandler處於有效狀態,返回false則將其刪除。
     * 如果佇列中仍有待處理的訊息,但都未到執行時間時,也會呼叫此方法。
     */
    boolean queueIdle();
}

可以看出 IdleHandler 只有當訊息佇列沒有訊息時或者是佇列中的訊息還未到執行時間時才會執行,IdleHandler 儲存在 mPendingIdleHandler 佇列中。queueIdle() 方法如果返回 false,那麼這個 IdleHandler 只會執行一次,執行完後會從佇列中刪除,如果返回 true,那麼執行完後不會被刪除,只要執行 MessageQueue.next() 時訊息佇列中沒有可執行的訊息,即為空閒狀態,那麼 IdleHandler 佇列中的 IdleHandler 還會繼續被執行。

比如我們想實現一個 Android 繪製完成的回撥方法,Android 本身提供的 Activity 框架和 Fragment 框架並沒有提供繪製完成的回撥,如果我們自己實現一個框架,就可以使用 IdleHandler 來實現一個 onRenderFinished 這種回撥了,可以有效的優化啟動時間等需求。

8. HandlerThread 是什麼?

HandlerThread 繼承自 Thread ,所以本質上,它是一個 Thread。它與普通的 Thread 的差別在於其建立了一個執行緒的同時建立了一個含有訊息佇列的 Looper,並對外提供這個 Looper 物件供 Handler 關聯,讓我們可以在該執行緒中分發和處理訊息。當我們想讓一個執行緒和主執行緒一樣具備訊息迴圈機制時,就可以使用這個類。

9. 能否在子執行緒更新 UI ?為什麼 onCreate() 中的子執行緒更新 UI 有時可以成功?

因為 Android 的 UI 訪問是沒有加鎖的,這樣多執行緒操作 UI 是不安全的,會導致介面錯亂,所以 Android 系統限制了只能在主執行緒訪問 UI,其他執行緒直接操作 UI 會丟擲異常。

如果在 onCreate() 中新建一個執行緒立刻執行操作 UI,結果卻可以正常執行,但如果延遲一段時間執行仍舊丟擲異常。這是因為檢測子執行緒的方法 checkThread() 是在 ViewRootImpl 中被呼叫,而 ViewRootImpl 在 ActivityThread 執行了 handleResumeActivity() 時被新增,也就是對應的是 onResume()。所以在 onCreate() 時根本不會執行 checkThread() 方法做判斷。

10. 為什麼非靜態類的 Handler 導致記憶體洩漏?如何解決?

首先,非靜態的內部類、匿名內部類、區域性內部類都會隱式的持有其外部類的引用。也就是說在 Activity 中建立的 Handler 會因此持有 Activity 的引用。

當我們在主執行緒使用 Handler 的時候,Handler 會預設繫結這個執行緒的 Looper 物件,並關聯其 MessageQueue,Handler 發出的所有訊息都會加入到這個 MessageQueue 中。Looper 物件的生命週期貫穿整個主執行緒的生命週期,所以當 Looper 物件中的 MessageQueue 裡還有未處理完的 Message 時,因為每個 Message 都持有 Handler 的引用,所以 Handler 無法被回收,自然其持有引用的外部類 Activity 也無法回收,造成洩漏。

解決的辦法:
  • 使用靜態內部類 + 弱引用的方式
    靜態類不會持有外部類的的引用,當需要引用外部類相關操作時,可以通過弱引用來獲取到外部類。當一個物件只有弱引用時,是可以被垃圾回收掉的。
private Handler sHandler = new TestHandler(this);

static class TestHandler extends Handler {
    private WeakReference<Activity> reference;
    
    TestHandler(Activity activity) {
        reference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Activity activity = reference.get();
        if (activity != null && !activity.isFinishing()) {
            switch (msg.what) {
                // 處理訊息
            }
        }
    }
}
  • 在外部類物件被銷燬時,將訊息佇列清空
@Override
protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

參考資料

Android 非同步訊息處理機制 讓你深入理解 Looper、Handler、Message 三者關係

Android 非同步訊息處理機制完全解析,帶你從原始碼的角度徹底理解

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

Android 的訊息處理機制(圖+原始碼分析)—— Looper、Handler、Message

Android 中為什麼主執行緒不會因為 Looper.loop() 裡的死迴圈卡死?

相關文章