天天看點

從源碼中看Handler、Looper、MessageQueue

概述

Handler、Looper、MessageQueue

這幾個,相信接觸

android開發

的,基本都能說出來這三者的基本關系,但是很多人可能也是停留在三者的調用關系上,你如果問他

postDelay是如何實作的

為啥子線程中要先prepare

為啥handler容易産生記憶體洩露

等等,他可能會一知半解,筆者還是決定從源碼的角度重新審視這些問題

從問題中看源碼

如果讓你線上程中實作

handler

,你可能會很快的寫下如下代碼,那筆者就還是以這個為切入點,分析前面提出的幾個問題

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(Looper.myLooper());
                Looper.loop();
            }
        }).start();
           

為啥子線程需要

prepare

,如果不調用會有什麼問題?

我們直接進入

Looper.prepare()

的源碼中看看:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare() {
    prepare(true);
}

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

我們可以看到

調用的其實是一個靜态方法

,内部的處理就是

執行個體化了一個Looper對象

,存放在

sThreadLocal

中了,

ThreadLocal

你可以了解為是線程本地存儲,用于儲存線程共享變量,如果不調用

prepare

直接進行

loop操作

,會怎樣呢?

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
        //如果沒有進行prepare操作會抛異常
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //拿到目前線程存儲的MessageQueue
        final MessageQueue queue = me.mQueue;
		......
        for (;;) {
        //如果poseDelay方法有延時參數,這個方法會阻塞,等待延時
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ......
            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
			......
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
            //調用handler的dispatchMessage方法
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......
            msg.recycleUnchecked();
        }
    }
           

我們可以很清楚的看到

第一行調用的實際是myLooper方法

,從線程本地存儲空間中拿到共享變量

Looper的執行個體

,是以如果子線程中沒有進行

prepare操作

,這裡就會抛出異常

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

那麼looper和handler究竟什麼聯系呢?

下面是

Handler的構造方法

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
           

可以看到

handler

在初始化的時候,會持有

looper以及對應looper的MessageQueue

的執行個體

下面是

Message類的屬性

public final class Message implements Parcelable {
    public static final Creator<Message> CREATOR = null;
    public int arg1;
    public int arg2;
    public Object obj;
    public Messenger replyTo;
    public int sendingUid;
    public int what;

	......
    long when;
    
    Handler target;
    
    Runnable callback;
    
    Message next;
	......
}
           

可以很清楚的看到

Message執行個體

本身會持有

handler引用

,也會有

延遲時長屬性when

,以及下一個

Message

的字段

next

相信看了上述

Looper,Handler,MessageQueue

,你對他們的

持有關系

應該有了解了

handler的postDelayed是如何實作的?

public final boolean postDelayed(Runnable r, long delayMillis)
{
    return sendMessageDelayed(getPostMessage(r), 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) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
           

通過上面的源碼,我們可以了解一點:

postDelay

最終是調用MessageQueue的enqueueMessage方法,根據

延遲時長

完成消息排隊

Message

你可以了解為是一個節點,裡面有

next字段辨別下一個消息

,這裡要注意上面的

loop()方法中有一行代碼

是以當

loop方法

循環從

MessageQueue中

讀取消息,這個

MessageQueue

next

方法會根據之前定義的

延遲時長

進行等待,直到達到這個時長才會傳回,然後調用

message.target

即我們目标的

handler

中的

dispatchMessage方法

handler為什麼容易造成記憶體洩露

我們在使用

handler

的時候,如果不注意:很容易會寫出

Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
           

要知道,

匿名内部類會持有外部的引用

,比如是一個

activity引用

,那麼你如果了解上述的

延遲消息的實作原理就會明白

,當我們發送

延遲消息

的時候,這個消息會被存放在

MessageQueue中

,等待

next方法傳回

,而對應的

Message

又會持有

handler對象

,就會導緻我們最終引用的這個

activity一直無法釋放

,進而導緻記憶體洩露的産生

為啥主線程中調用loop不會阻塞呢?

每個應用都有一個主線程,當主線程啟動的時候其實也會調用

loop

方法,但這個過程會不斷的循環從

MessageQueue

中取消息來執行,因為

android的設計

本身是基于

事件驅動的

,是以我們都主線程中的操作都可以看做是不同的

事件

,最終交由主線程的

handler

來執行,是以如果我們在

主線程中做過多耗時操作

,會導緻主線程阻塞,即ANR的産生,這個消息循環的模型跟以前在

Windows

Win32下的消息循環類似