從事件驅動程式設計模型分析Handler訊息傳遞機制

zyl409214686發表於2018-03-13

事件驅動程式設計模型

事件驅動程式設計是一種程式設計正規化,它的執行流由外部決定。它的特點是包含一個事件迴圈,當外部事件發生時,使用回撥機制來觸發相應的處理。

我們先來看看為Windows程式設計中事件驅動程式設計模型程式碼是什麼樣子的。

main(){
    while(1) //訊息迴圈
    {
	id=getMessage(...);  //獲取訊息、如無訊息則阻塞
	if(id == quit)
		break;
	translateMessage(...);//處理訊息
    }
}
複製程式碼

它很簡單,就在一個死迴圈中中去獲取和處理訊息。與其對立的還有順序程式設計模型,還記得我們編寫的第一個helloworld嗎?它就是順序程式設計模型,執行完畢程式即結束。試想一下如果我們的GUI程式(圖形介面應用程式)如果是順序驅動程式設計模型,順序執行完語句,執行完程式就結束了。使用者還沒有看到介面程式就結束了, 很憂傷有沒有。事件驅動程式設計模型很好的解決了這個問題,所以幾乎所有的GUI應用,都是採用事件驅動程式設計,比如Windows、 Linux、Android、iOS等。。

簡單說,事件驅動包括事件生產者,事件消費者,訊息迴圈looper。 生產者將訊息傳送給looper,由looper分發相應的事件消費者處理。

Android 中的事件程式設計驅動模型

Android中事件驅動主要包含Handler,MessageQueue,Runnable\Message和Looper,他們之間的互動圖:

android訊息處理機制圖

  • Runnable\Message 可以被壓入MessageQueue中,MessageQueue中實際只允許儲存一種型別物件,而原始碼中對Runnable進行了相應的轉換(轉換為Message)。Message中存有Handler的引用(target),在從MessageQueue中取出Message後交給它所引用的Handler.handleMessage()去處理。

  • MessageQueue 儲存訊息的佇列,實際上是一個單連結串列資料結構。

  • Looper 一個訊息迴圈體,不斷從MessageQueue中取出Message然後傳給Handler處理,如此往復。如果佇列為空,則該Looper所線上程進行休眠。

  • Handler 真正的傳送以及處理訊息的地方。

一句話概括它們:Looper不斷獲取MessageQueue中的一個Message,然後交由Handler進行處理。就好比CPU的工作方式,中央處理器(Looper)不斷地從記憶體(MessageQueue)中讀取指令(Message),執行指令(Handler)並最終產生結果。

原始碼分析

接下來通過原始碼來分析一下Android中的事件驅動程式設計模型,AndroidUI主執行緒入口-ActivityThread.main()的原始碼:

public static void main(String[]args){
    ...
    Looper.prepareMainLooper(); //1  
    ActivityThread thread = new ActivityThread(); //2
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler(); //3
    }
    Looper.loop(); //4
    throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製程式碼

從註釋1到註釋4依次來看,註釋1處初始化looper物件

看註釋1處 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();
        }
    }
    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));
    }
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製程式碼

可以看到prepare()方法此處為對Looper的建立, 並且存入當前執行緒的ThreadLocal物件中。那麼ThreadLocal是什麼?

ThreadLocal是一個用來儲存資料的類,類似HashMap、ArrayList等集合類。它的特點是可以在指定的執行緒中儲存資料,然後取資料只能取到當前執行緒的資料。

註釋2因為在main靜態方法中,所以要建立ActivityThread物件, 然後與WindowManagerService建立聯絡開啟binder執行緒。

註釋3建立主執行緒Handler物件thread.getHandler(),看原始碼:

    final Handler getHandler() {
        return mH;
    }
    private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        ...
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                ...
            }
    }
複製程式碼

LAUNCH_ACTIVITY、PAUSE_ACTIVITY是不是很熟悉,這就是我們Activity中的生命週期,binder執行緒像主執行緒Hander.H物件傳送訊息,最終呼叫Activity.onCreate()等生命週期方法。

註釋4開啟主訊息迴圈,來看原始碼Looper.loop():

public static void loop() {
        final Looper me = myLooper();  //first
        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);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            msg.recycleUnchecked();
        }
    }
複製程式碼

首先通過myLooper()獲取到當前執行緒的looper物件,然後從looper物件中獲取到訊息佇列me.mQueue。接下來就是我們的訊息迴圈了,不斷的從佇列中取出訊息queue.next();,如果訊息佇列沒有訊息就堵塞,等到有訊息的時候會被喚醒。通過msg.target.dispatchMessage(msg);來對訊息進行處理,target為Message中對handler的引用,Hanlder.dispatchMessage()原始碼:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
        message.callback.run();
    }
複製程式碼

所以訊息的執行會呼叫到我們重寫的mCallback.handleMessage()ormessage.callback.run()

訊息入隊順序問題

下面來看一下訊息入隊的,並丟擲一個問題,Handler訊息入隊是按傳送順序執行的嗎?繼續看原始碼:

boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            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 {
                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;
            }
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製程式碼

可以看到實際上訊息入隊是有一個優先順序的條件的。在符合佇列先進先出規則的同時增加了按延遲時間進行排序。

例如:postDelay先傳送了一個五秒延遲的訊息A,再postDelay延遲兩秒傳送訊息B,結果會在2秒後先執行訊息A,然後3秒後執行訊息B。

handler處理訊息所線上程

在handler處理訊息所線上程上的理解,重要的一點:

handler訊息處理所線上程 = handler所引用的looper所在的執行緒

練習

最後可以自己實現的handlerThread 這樣可以加深對handler訊息機制的理解~ 在主執行緒中向子執行緒傳送訊息,然後在子執行緒的handler中進行處理。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        Log.d("MainActivity", "mainThread:"+Thread.currentThread().getName());
        MyThread thread = new MyThread();
        Handler h = new Handler(thread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                Log.d("MainActivity", "handlerThread:"+Thread.currentThread().getName());
            }
        };
        thread.start();
        h.sendEmptyMessage(0);
    }

    class MyThread extends Thread{
        @Override
        public void run() {
            Looper.prepare();
            Looper.loop();
        }
        public Looper getLooper(){
            return Looper.myLooper();
        }
    }
}
複製程式碼

相關文章