從源分析Handler、MessageQueue、Looper

QyLost發表於2019-07-25

前言

很長的一段時間我一直在使用Handler,主要是在處理非同步任務的時候來實現執行緒切換,不過對其原理和工作流程並沒有詳細的做過了解,以下我把從分析原始碼得到一些內容做出了一些總結。

從源分析Handler/MessageQueue/Looper的工作流程

首先來看下如下的示意圖,圖中描述的物件之間的基本通訊。

首先是Handler物件傳送了一條Message,然後訊息會被存放到一個列表(佇列:MessageQueue)中,緊接著有一個叫做Looper的物件會不停的去這個佇列中尋找有沒有新的訊息,有的話將訊息分配給Handler物件進行處理(每一個Message物件都會預設持有一個Handler物件的引用,這個Handler物件就是傳送這個Message的物件,在Message物件內部被定義為target變數)。其具體的訊息會被放在target所在的執行緒中執行。接下來詳細介紹訊息的收發和處理過程。

https://imgservice.lost520.cn/InternetImgService/2019-07-24/20190724222357343.pn

Handler的建立過程

首先先來看一下Handler的建構函式,如下圖,Handler一共向外提供了4個建構函式(其實在內部一共提供了是7個建構函式,只不過對外是隱藏的)。

Handler的建構函式

//Step1
public Handler() {
    this(null, false);
}
//Step2
public Handler(Callback callback, boolean async) {
    //.........此處省略了部分程式碼

    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;
    mCallback = callback;
    mAsynchronous = async;
}
複製程式碼

可以看到的是我們在呼叫無引數構造方法時其實的呼叫的內部的具有兩個引數的構造方法,第一個要求傳入一個回撥實現(後便會具體介紹),第二個則是否非同步的識別符號。

Step2中可以看到幾個比較關鍵的內容,第一個關鍵點就是在我們新建的Handler物件內部儲存了一個Looper物件的引用,這個Looper.myLooper()函式獲取的是當前執行緒所持有的Looper物件(執行緒中預設是沒有Looper物件的,只有呼叫Looper.propare()函式之後才會在當前執行緒中建立一個唯一的Looper物件,所以如果沒有則會丟擲一個異常,這個異常就是我們最初在子執行緒中使用Handler提示的異常資訊。);第二個關鍵點則是從Looper物件中拿到了一個訊息佇列物件mQueue,這個物件是一個MessageQueue,它是在Looper被建立時建立的。

Looper/MessageQueue的建立過程/時機

MessageQueue是跟隨Looper的建立而建立的,在一個執行緒中只會存在一個Looper物件,也就是說在一個執行緒中MessageQueue也只會存在一個(理論上來說)。下面從原始碼中來印證如上所說。

1、來看Looper.prepare()函式

這個方法僅僅是提供了一個入口方法,實際上呼叫的是內部的另一個prepare方法。緊接著內部的這個prepare方法通過new的方式建立了一個Looper物件,也就是在Step3的內容。可以清楚的看到這裡為Looper的內部變數mQueue進行了賦值,也就是在這個時候MessageQueue被建立。

在Step2的時候我們發現呼叫了一個叫做sThreadLocal的變數的set函式,這個ThreadLocal並非是一個執行緒,它是用來儲存執行緒中資料的,具體可參考我的另一篇文章:ThreadLocal是什麼?

//Step1
public static void prepare() {
    prepare(true);
}
//Step2
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));
}
//Step3
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
複製程式碼

在Looper物件中有一個非常重要的函式,那就是loop了,中文翻譯過來就是迴圈的意思,這個函式會幫助我們不停的從MessageQueue中來獲取Message

2、來看MessageQueue

目前來說,MessageQueue的建立並沒有什麼值得我們關注的,它只是提供了一個先進先出的機制來幫助我們存取訊息,但是我們需要知道它所提供的兩個非常重要的方法,第一個就是enqueueMessage,這個函式是用於將Message物件新增到佇列中的,第二個就是next函式,該函式是從訊息佇列中取訊息的,取出來後會立刻從佇列中移除。

Handler的訊息傳送過程

如下是一個基本的訊息傳送流程,基本使用這裡不在贅述。

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        //do something...
    }
};
Message message = Message.obtain();
handler.sendMessage(message);
複製程式碼

1、走進handler.sendMessage(Message msg)函式中來一探究竟。

//Step1
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}
//Step2
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//Step3
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.sendMessage(Message msg)函式時,它會呼叫內部的sendMessageDelayed函式,這個函式是用於傳送定時訊息的,因為sendMessage傳送的都是需要立即被處理的訊息,所以傳入的就是0了,緊接著sendMessageDelayed函式又呼叫了sendMessageAtTime函式。

在這個sendMessageAtTime函式中我們需要關注的是enqueueMessage的呼叫,這個enqueueMessage函式是幫助我們把訊息加入到MessageQueue物件中。在如上Hanlder建立過程的描述中,我們說了:這個訊息佇列(mQueue物件)是在Handler建立時從Looper物件中獲取並儲存到區域性變數中的。

2、來看Handler中的enqueueMessage函式

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

這裡有兩點我們需要特別關注,第一個就是把當前Handler物件的引用給了msgtarget變數,這其實就是為之後的訊息處理提供處理者,在加入到訊息佇列之前會預設把Messagetarget設定為傳送MessageHandler物件,即便我們在建立Message物件時設定了target也會在enqueueMessage函式中被重置,由此可以得出,Message物件的傳送者即是Message的處理者。

到這一步訊息已經被新增到了MessageQueue物件中,至此HandlersendMessage任務就算完成了,也就是說它成功的將訊息遞交給了MessageQueue物件。

Message的處理過程

在上一段的結尾我們知道了HandlersendMessage函式會把我們的Message物件加入到一個叫做MessageQueue的物件中,也就是說我們只是把訊息儲存了起來,單純的訊息儲存沒有任何意義,所以引入了Looper物件來不停的從MessageQueue中拿資料並且交給訊息的target物件來進行處理。

1、來看Looperloop函式

public static void loop() {
    final Looper me = myLooper();//核心1
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//核心2

    //.......此處省略無關程式碼

    boolean slowDeliveryDetected = false;

    for (;;) {
        //核心3
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //.......此處省略無關程式碼
        try {
            msg.target.dispatchMessage(msg);//核心4(重點!!!!!!)
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        //.......此處省略無關程式碼
    }
}
//核心1呼叫的myLooper函式
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
複製程式碼

以上就是Looperloop函式了,為了便於觀看,這裡我刪減掉了無關的程式碼,並標註了4條較為重要的程式碼。

第一步:呼叫Looper物件內部的myLooper()函式,這個函式是從ThreadLocal物件中取出當前所線上程的Looper物件,它是我們在建立Looper時儲存的Looper物件,也就是我們在上邊介紹Looper建立時看到的sThreadLocal.set(new Looper(quitAllowed));

第二步:拿到我們當前執行緒中持有的MessageQueue物件,在上邊我們說了MessageQueue是隨著Looper的建立而被建立的。也就是說我們拿到的LooperMessageQueue都是當前執行緒中的。至此你應該要知道LooperMessageQueue在每一個執行緒中都是可以存在的,但是更要知道的是:在每一個執行緒中有且只有一個LooperMessageQueue物件。如下我繪製了一張圖幫助更好記憶和理解。

https://user-gold-cdn.xitu.io/2019/7/25/16c2902e2ad7575b?w=966&h=663&f=png&s=22741

第三步:從MessageQueue中取出我們使用Handler.sendMessage存放進取的訊息。

第四部:這一步其實是最核心的一部了,它通過呼叫Message物件中的target變數的dispatchMessage函式,將訊息交給target物件進行處理,在上邊我們說了target物件就是傳送Message物件的Handler。所以最終的訊息處理會被放在該物件中被處理。

2、來看HandlerdispatchMessage函式

如下就是我們在上一步看到的loop函式中呼叫的dispatchMessage函式。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
 /**
 * Subclasses must implement this to receive messages.
 * 譯:子類必須實現這個函式來接收訊息
 */
public void handleMessage(Message msg) {
}
複製程式碼

首先它會判斷Messagecallback變數是否為NULL(在Message.obtain()函式中可以傳入callback),如果存在callback那麼會優先處理Messagecallback,否則會繼續判斷當前Handler物件的callback是否為NULL(這個callback是在構造Handler物件時是可選傳入的),如果還不行那麼就呼叫Handler中的handleMessage函式,這也是我們常見的訊息處理方式,也就是我們在上邊重寫的handleMessage函式。

這裡我們需要知道的就是訊息處理的優先順序:

1、由Message物件的callback處理(這個callback是一個Runnable物件)

2、由Handler物件的mCallback處理(這個callbackHandler物件中的一個介面提供了一個用於訊息處理的回撥public boolean handleMessage(Message msg);

3、由HandlerhandleMessage處理(這個就是Handler物件中的一個方法了,只有預設實現沒有任何程式碼,通常需要重寫)

另外需要知道的是:dispatchMessage函式中所有的訊息都是在Handler物件所處的執行緒中被執行的。

訊息的傳送和處理總結

1、呼叫Looper.prepare函式

幫助我們建立Looper物件和MessageQueue物件。

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物件並儲存當ThreadLocal中
}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
複製程式碼

2、建立Handler物件

在這一步主要是拿到當前執行緒的Looper物件以及Looper物件中的MessageQueue物件並儲存其引用。

public Handler(Callback callback, boolean async) {
        //........省略不重要程式碼
        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;
        mCallback = callback;
        mAsynchronous = async;
    }
複製程式碼

3、呼叫HandlersendMessage函式

該函式最終會走到Handler物件中的enqueueMessage中,將訊息儲存到當前執行緒的MessageQueue物件中。

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

3、Looper物件的loop函式

當前執行緒的Looper物件不斷的從它內部的MessageQueue物件中取訊息,然後交給Messagetarget來做處理。

public static void loop() {
    final Looper me = myLooper();//核心1
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//核心2

    //.......此處省略無關程式碼

    boolean slowDeliveryDetected = false;

    for (;;) {
        //核心3
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //.......此處省略無關程式碼
        try {
            msg.target.dispatchMessage(msg);//核心4(重點!!!!!!)
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        //.......此處省略無關程式碼
    }
}
//核心1呼叫的myLooper函式
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
複製程式碼

4、Handler處理訊息

到這裡會根據優先順序來處理訊息,且訊息的執行是在當前Handler所在的執行緒中。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製程式碼

至此核心的處理流程及已經完成了。

主執行緒中不用手動建立Looper的原因

Android主執行緒即ActivityThread,在主執行緒的入口方法main方法中呼叫了Looper的prepareMainLooper函式,該函式是專門為主執行緒提供建立Looper使用的。

public static void main(String[] args) {
    //......省略無用程式碼
    Looper.prepareMainLooper();

    // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
    // It will be in the format "seq=114"
    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製程式碼

總結

持有關係:

1、一個執行緒中只有一個Looper物件和一個MessageQueue 2、一個執行緒中可以有多個Handler物件 3、MessageQueue是包含在Looper中的

注意點:

1、Handler必須在持有Looper的執行緒中才能建立。 2、Handler的回撥優先順序(1、Message.callback2、Handler.callback、3、Handler.handleMessage)。 3、在使用Handler傳送Message時,Message的target會被預設設定為Message的傳送者。

最後

HandlerMessageMessageQueueLooper組成了Android強大的訊息機制,以上只是簡述了其中的部分內容,還有很多的知識點等待日後進行挖掘。

原創文章,轉載請標明來源。

相關文章