1. 程式人生 > >Android中Handler的理解與總結

Android中Handler的理解與總結

android的非同步處理訊息機制Handler這個問題是老生常談哪,這個要追溯到一個面試的場景了,面試官說,handler傳送完訊息後,什麼時候觸發迴圈,這個我說了,handler原始碼中有個looper,這個是用來迴圈取出handler傳送到訊息佇列(messageQueue)中的訊息,一旦Looper開啟Looper.loop()就開啟無限迴圈,直接取出MessageQueue中所有的訊息,然後面試官不知道是他怎麼理解的,我說的是looper.loop()就開啟迴圈,他沒說話,可能不在一個頻道上抑或是我回答的不對?我感覺他也不是特別的理解吧,哎,有點坑哪,這面試真是醉了,什麼樣的面試官都會遇到,面試還真是運氣加上實力,我覺得吧,可能還是緣分未到吧,但是我喜歡總結,我覺得自己要深刻的理解,現在我就去再去深入的看下這個Handler,下次再問到此類問題信手拈來,插一句,可能那個面試官他真的不是特別理解吧!

 首先,咱們理解幾個概念,見名知意吧,下面的部落格中會以口語化形式表述出來。

 Message(訊息),MessageQueue(訊息佇列),Looper(迴圈,很重要),Handler(用來發送和處理訊息)

1.Handler分析傳送訊息的過程

Handler mHandler = new Handler(){
    @Override
public void handleMessage(Message msg) {
        super.handleMessage(msg);
}
};
上面那種寫法咱們是咱們最常用的,new完後,重寫handleMessage方法,裡面的傳遞介質Message就是我們傳送的訊息,我們看看他的內部是如何處理的,直接點選Handler
/**
 * Default constructor associates this handler with the {@link Looper} for the
 * current thread.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 */
public Handler() {
    this(null, false);
}
看到他的建構函式使用的這類裡面的構造,傳入引數是null 和 false,我們繼續點選this這個函式
/**
* Use the {@link Looper} for the current thread with the specified callback interface * and set whether the handler should be asynchronous. * * Handlers are synchronous by default unless this constructor is used to make * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param callback The callback interface in which to handle messages, or null. * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. * * @hide */ 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()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
第一個引數是一個回撥的介面,這個介面也是在Handler內部定義的如下所示:
/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}
其實也是最後呼叫handlerMessage(Message msg)這個方法,就像我們剛才使用的那種方式,我們可以這樣理解,這個Handler有好幾種的使用方式,可以傳介面,也可以直接new出來,然後重寫handlerMessage方法,幾個入口吧。

第二個引數是一個boolean型別,這個訊息是否是非同步的,這裡我們new出來的預設是false,也就是不是非同步的訊息,是同步的訊息,非同步的訊息這裡提前說下,非同步訊息就不能保證順序了,因為這裡面還有MessageQueue訊息佇列的概念,下面會繼續說的。

ok,new完handler後,可以看到給mLooper 設定了與當前執行緒相關聯的Looper物件,mQueue為當前looper物件裡面的訊息佇列,而looper和messageQueue是在Looper物件例項化後相關聯的,可以看下兩者的關聯程式碼

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
ok,上面講的是new完Handler後的程式碼追蹤,也就是在newHanlder後,獲取到當前的looper物件並設定到Handler裡面的成員變數mLooper,還有將Handler成員變數訊息佇列mQueue 設定為當前Looper物件所關聯的訊息佇列mLooper.mQueue;

下面這個步驟就是handler傳送訊息了,追蹤下程式碼

/**
 * Enqueue a message into the message queue after all pending messages
 * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
 * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
 * You will receive it in {@link #handleMessage}, in the thread attached
 * to this handler.
 * 
 * @param uptimeMillis The absolute time at which the message should be
 *         delivered, using the
 *         {@link android.os.SystemClock#uptimeMillis} time-base.
 *         
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.  Note that a
 *         result of true does not mean the message will be processed -- if
 *         the looper is quit before the delivery time of the message
 *         occurs then the message will be dropped.
 */
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);
}
不管你是sendMessage還是sendMessageDelayed最終程式碼都會執行到這個sendMessageAtTime方法中,可以看到第一個引數是Message我們的傳遞介質,訊息載體,第二個就是時間,訊息的絕對時間,然後接著追蹤程式碼
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
}
    return queue.enqueueMessage(msg, uptimeMillis);
}
在這裡可以看到msg.target = this,可以很好的理解,即當前訊息的目標,也就是傳送訊息的控制代碼是當前的物件也就是Handler,將其賦值,然後判斷是否是非同步的訊息,如果是,設定為true,然後將這個訊息入隊返回true 或者false,表示訊息進入佇列是否成功

繼續往下看

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
}
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
}

    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;
這是訊息入隊的程式碼,會先判斷當前目標target是否為null,這個訊息是否正在使用等最後返回true,表示訊息入隊成功。

 2.疑問:到這裡我們可能要問了,這邏輯已經順著下來了,handler什麼時候去處理訊息哪,現在只有入沒有處理訊息啊?

ok,上述1的過程只是訊息的傳送過程,現在我們來看看訊息的處理,我們不要忘記了Looper這個物件,先檢視其原始碼,其中有這個方法

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {

當looper呼叫這個方法後會開啟無限迴圈,迴圈從messageQueue中取出message,然後呼叫msg.target.dispatchMessage去處理訊息,可以看下程式碼

try {
    msg.target.dispatchMessage(msg);
} finally {
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
}
}
而target就是當前new的handler,我們看handler裡面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);
}
}
看到上面的註釋說明”在這裡處理系統的訊息“,然後我們驚喜的發現了callback,對,沒錯,就是在最初初始化handler的時候,傳的callback引數,但是我們這個是null啊,對的,所有他執行了else,執行了handleMessage,然後這就是我們為什麼重寫handleMessage的原因,好了,但是callback的使用場景是什麼
new Handler().post(new Runnable() {
    @Override
public void run() {
       new TextView(TestActivity.this).setText("xxxxx");
}
});
我們直接使用handler.post 傳送了一個runnable,點選post後,可以看到執行的順序和sendMessage是一樣的
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}
不過是通過getPostMessage(r)獲取了一個訊息,而此時callback = r;
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
m.callback = r;
    return m;
}

此時的callback不為空,那最後執行到這個dispatchMessage時,由於callback不是null,所以直接執行了runnable的run方法,不信,請看程式碼

private static void handleCallback(Message message) {
    message.callback.run();
}
整個流程走完了,但是回到最初的問題,面試官說,什麼時候觸發迴圈,因為我們已經知道必須呼叫looper.loop()方法才能觸發無限迴圈,說這樣也沒錯啊,但是我們new的時候並沒有使用looper的loop方法啊,ok,問題就在這裡,我們Activity在初始化的時候,系統已經幫我們做好了
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
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();
在ActivityThread這個類中,系統初始了mainLooper,所以new Handler後預設為mainLooper,最下面的那個Looper.loop(),系統已經幫助我們開啟訊息迴圈了,比如我們之前經常這樣寫
  new Thread(){
        public void run(){
            Looper.prepare();
handler = new Handler(){
                @Override
public void handleMessage(Message msg) {
                    super.handleMessage(msg);
}
            };
Looper.loop();
}
    }.start();
}
上述程式碼是在子執行緒中使用handler,這個時候要注意,因為已經切換了執行緒,不再是預設的UI主執行緒,所以looper也不再是main Looper,所以Looper.prepare是將當前子執行緒繫結到當前looper物件,最後一定要開啟訊息迴圈,這是最經典的寫法和使用。

綜上所述,可能我沒有get到面試官的點吧,抑或是面試官在這個問題上也存在著疑問吧,不管怎麼說吧,自己都要要求自己去理解,不能再知道表層了,與君共勉吧!

參考文章: