Handler、MessageQueue、Looper的分析(複習筆記)

chenToV發表於2017-10-03

現如今,在android裡面的非同步通訊一般都是用RxJava來完成,這當然是很好的辦法,但是我覺得,有時候學習一下官方提供的解決方案,能讓你更好的理解android的機制,同時通過對官方解決方案的理解,也能促使你更好的理解其它的第三方框架

1、Handler與MessageQueue、Looper之間的關係

其實在平常的開發中,如果我們只需要用Handler來進行UI元件的狀態的更新的話,那麼我們可以不用管MessageQueue和Looper的,因為這兩個物件的例項在UI執行緒建立時在ActivityThread的main()方法中就已經被建立,並不需要我們來管,我們只需要操作Handler就可以,但是我們要探究在子執行緒中Hander呼叫sendMessage()或者post()方法之後為什麼操作就回到了Ui執行緒,就需要理解它們三者的關係了,下面是詳細的介紹

1、簡單而言,Looper的職責在於建立建立一個MessageQueue訊息迴圈物件,並且通過開啟一個無限迴圈,從MessageQueue訊息佇列中獲取訊息,然後傳遞給Handler物件,如果我們要利用三者從頭開始構建一個非同步通訊過程,那麼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的構造方法是私有化的,要建立Looper物件例項,需要通過prepare()方法,從上面程式碼可以看出,首先會通過sThreadLocal.get()來獲取Looper物件例項,如果存在,就會丟擲異常,這代表一個執行緒中只能有一個Looper物件例項,其中sThreadLocal是一個ThreadLocal例項,用來儲存Looper物件,以執行緒為key,關於它的用法可查閱相關資料。接下來如果獲取的物件為null,則通過sThreadLocal.set(new Looper(quitAllowed))將建立的Looper物件儲存,下面分析Looper的構造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
複製程式碼

在Looper的構造方法中,主要操作就是建立一個MessageQueue物件,到這裡,訊息的迴圈佇列就被建立了,Looper物件也被建立了,接下來就是Looper物件怎麼來從MessageQueue佇列中獲取訊息呢?

public static void loop() {
    //獲取當前執行緒的Looper物件
    final Looper me = myLooper();
    //判斷當前Looper物件是否存在,不存在則丟擲異常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲取MessageQueue物件
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    //無限迴圈,從MessageQueue中獲取訊息物件
    for (;;) {
        //從訊息佇列中獲取訊息物件
        Message msg = queue.next(); // might block
        //如果為null,則阻塞
        if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
        }

       // This must be in a local variable, in case a UI event sets the logger
       final Printer logging = me.mLogging;
       if (logging != null) {
           logging.println(">>>>> Dispatching to " + msg.target + " " +
           msg.callback + ": " + msg.what);
       }

       final long traceTag = me.mTraceTag;
       if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
          Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
       }
       try {
          //這裡的msg.target其實就是Handler的物件例項
          msg.target.dispatchMessage(msg);
       } finally {
          if (traceTag != 0) {
            Trace.traceEnd(traceTag);
          }
       }

       if (logging != null) {
          logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
       }
       
       
       final long newIdent = Binder.clearCallingIdentity();
       if (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
            + Long.toHexString(ident) + " to 0x"
            + Long.toHexString(newIdent) + " while dispatching to "
            + msg.target.getClass().getName() + " "
            + msg.callback + " what=" + msg.what);
    }
    msg.recycleUnchecked();
    }
}
複製程式碼

以上是Looper物件迴圈獲取訊息佇列中的訊息的方法,本人在程式碼中必要的地方已經做了註釋,應該可以理解,這個方法的主要作用就是構建一個無限迴圈,不斷從MessageQueue佇列中獲取訊息,然後將訊息物件通過dispatchMessage()方法傳遞給Handler物件,到現在,Looper已經開始迴圈從MessageQueue中獲取訊息物件了,也就是說,Looper例項和MessageQueue例項都已經建立,那麼接下來就需要建立Handler的例項了,建立Handler有兩種方式(其實是一樣的方式,只是在傳送訊息時呼叫的方法不同),程式碼如下:

//第一種
private Handler mHandler1 = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
Message msg =  Message.obtain();
        msg.what = 0x111;
        msg.obj = ...
        mHandler1.sendMessage(msg);

//第二種
private Handler mHandler2 = new Handler();
mHandler2.post(new Runnable() {
        @Override
        public void run() {

        }
});
複製程式碼

以上就是建立Handler物件以及傳送訊息的一般使用,我們進入Handler的原始碼看看在建立例項的過程中做了什麼

public Handler() {
    this(null, false);
}

public Handler(Callback callback, boolean async) {
    if (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) {
        throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
    }
    //獲取MessageQueue物件
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
複製程式碼

在建立Handler物件的過程中,主要是獲取了當前執行緒Looper物件以及MessageQueen物件,比較好理解,接下來就是第一種傳送訊息的過程分析:

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) {
    //在Handler建立時獲取到的MessageQueen物件
    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);
    }
    //最終呼叫MessageQueen中的enqueueMessage()方法,將訊息放進訊息隊//列
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼

進過各種return,最終會呼叫到enqueueMessage()方法,在該方法裡,會將訊息,以及傳送時的時間傳遞給MessageQueen佇列,最終放進訊息佇列中。並且,會將自身賦值給msg.target屬性,前面提到的msg.target屬性就在這裡賦值的了。而第二種傳送訊息的方式如下:

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

//到這裡,其實跟第一種方式就是一樣的了
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製程式碼

上面的程式碼中,會呼叫到sendMessageDelayed()方法,其實,到這裡,跟第一種方式就是一樣的了,最終呼叫的都是enqueueMessage()方法將訊息放進訊息佇列中,唯一不同的是,這個Message物件是通過getPostMessage(r)方法來構造的

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
複製程式碼

在該方法中,構造了一個Message訊息物件,並且將我們傳進去的Runnable例項賦值給Message物件中的callback屬性,因此,我們在使用post方式時,並不會建立新的執行緒。現在,已經知道了訊息的傳送過程,那麼接下來就看看在MessageQueue中,是怎麼把訊息放進訊息佇列的,程式碼如下:

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

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on adead 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) {
            // 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;
                //如果已經已經到佇列最後一個或者時間已經比p的時間少了,那麼代表找到了插入的位置,跳出迴圈
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            //將訊息物件插入到佇列中
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
複製程式碼

上面將訊息物件放進訊息佇列的方法,主要是根據時間的先後將訊息放進佇列中,這樣,傳送訊息的全過程已經分析完了,那麼接下來就是取訊息了,前面說到,Looper物件呼叫loop()方法後,會無限迴圈地從訊息佇列中獲取資料,通過MessageQueen物件中的next()方法來獲取訊息佇列中的訊息,下面分析該方法

Message next() {
    ....
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //這是一個native方法,實際作用就是通過Native層的MessageQueue阻塞nextPollTimeoutMillis毫秒的時間。若是nextPollTimeoutMillis為-1,這時候訊息佇列處於等待狀態
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //這個判斷是為了過濾不屬於該Handler處理的msg
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            //根據時間取出訊息
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    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,下次迴圈訊息佇列則處於等待狀態
                nextPollTimeoutMillis = -1;
            }
    }
}
複製程式碼

以上的方法我們注意一下中文註釋的那些就差不多了,主要作用就是根據我們設定的時間取出訊息物件,到目前為止,已經從訊息佇列中獲取到訊息了,那麼就回到了Looper物件中的loop()方法,前面loop()方法中,當獲取到訊息物件之後最終會msg.target.dispatchMessage(msg)操作,將訊息物件傳遞給Handler物件中dispatchMessage()方法,而如果是在更新UI元件這一種的場景下,loop()方法的呼叫是在UI執行緒中的,那麼其實在這裡已經完成了執行緒的切換,回到了UI執行緒中,其它場景類似,所以當執行到Handler中的dispatchMessage()方法,那麼已經回到了主執行緒,下面分析dispatchMessage()方法

public void dispatchMessage(Message msg) {
    //首先判斷Message物件的callback屬性是否為null,這個屬性的設定是在Handler呼叫post(Runnable r)方法的時候設定的,就是Runnable物件
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
複製程式碼

上面的方法會分兩種情況,如果是使用了Handler的post()方法,那麼代表著msg.callback將不會為null,因此就會呼叫handleCallback(msg)方法,在handleCallback(msg)方法中的操作是message.callback.run(),也就是呼叫了post(Runnable r)中的Runnable物件中的run()方法,而第二種使用最終呼叫handleMessage(msg)方法,但是handleMessage(msg)是一個空實現,所以就需要重寫該方法來進行具體的實現。到現在,整個的流程已經分析完畢了

總結

Looper物件和MessageQueue物件在一個執行緒中只能有一個,而Handler物件則可以是多個,一個Looper物件和MessageQueue物件可以被多個Handler繫結,它們之間的關係大概可以理解為:Looper負責訊息佇列的建立,從訊息佇列中獲取訊息,MessageQueue負責儲存訊息物件,Handler負責管理訊息的傳送和收到訊息後的邏輯處理,以上是複習時的一點理解,如有錯誤,還望指出,請多包涵

相關文章