1. 程式人生 > >淺談Android中的Handler機制

淺談Android中的Handler機制

Handler是Android中提供的一種非同步回撥機制,也可以理解為執行緒間的訊息機制。為了避免ANR,我們通常會把一些耗時操作(比如:網路請求、I/O操作、複雜計算等)放到子執行緒中去執行,而當子執行緒需要修改UI時則子執行緒需要通知主執行緒去完成修改UI的操作,則此時就需要我們使用Handler機制來完成子執行緒與主執行緒之間的通訊。

1. Handler的一般使用步驟

在明確了Android中只有主執行緒能修改UI介面、子執行緒執行耗時操作的前提後,下面一起來學習下Handler的使用步驟。

  1. 在主執行緒中建立Handler例項,並且重寫handlerMessage方法。

    private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                //執行相關修改UI的操作
                break;
            }
        }
    };
    
  2. 子執行緒中獲取Handler物件,在需要執行更新UI操作的地方使用handler傳送訊息

    Message msg = Message.obtain();
    msg.obj = "content";
    msg.what = 1;
    //傳送訊息給Handler
    handler.sendMessage(msg);   
    
  3. 在handlerMessage方法中的Switch裡面,根據case下不同的常量執行相關操作

以上只是Handler的一種使用方式,由於本文的重點是探究Handlerde原理,故其他使用方式這裡不重點介紹。

2. 重要類的職責

在深入瞭解Handler機制原理之前,我們應該明確在Handler機制中幾個重要類的職責。

  • Handler:負責傳送處理訊息
  • MessageQueue:訊息佇列,負責儲存訊息
  • Message: 具體傳送的訊息
  • Looper: 負責迴圈取出訊息給Handler處理
  • ThreadLocal: 用於執行緒間的資料隔離,在每個執行緒中存放各自對應的Looper

3. Handler機制原理

  • 每個Handler都會關聯一個訊息佇列,訊息佇列又是封裝在Looper物件中,而每個Looper又會關聯一個執行緒。這樣Handler、訊息佇列、執行緒三者就關聯上了。

  • Handler是一個訊息處理器,將訊息傳送給訊息佇列,然後再由對應的執行緒從訊息佇列中逐個取出,並執行。

  • 預設情況下,訊息佇列只有一個,也就是主執行緒的訊息佇列,該訊息佇列通過Looper.prepareMainLooper()方法建立,最後執行Looper.loop()來迴圈啟動訊息。

以上敘述可能有點空洞,下面我們結合原始碼一起來理解:

3.1 Handler是如何關聯訊息佇列和執行緒的?

我們首先看Handler預設的建構函式:

     public Handler(Callback callback, boolean async) {
    //程式碼省略
    mLooper = Looper.myLooper();//獲取Looper
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
        mQueue = mLooper.mQueue;//通過Looper物件獲取訊息佇列
        mCallback = callback;
        mAsynchronous = async;
    }

    //獲取Looper物件
    public final Looper getLooper() {
        return mLooper;
     }

從Handler的建構函式中我們可以發現,Handler在初始化的同時會通過Looper.getLooper()獲取一個Looper物件,並與Looper進行關聯,然後通過Looper物件獲取訊息佇列。那我們繼續深入到Looper原始碼中去看Looper.getLooper()是如何實現的。

    //初始化當前執行緒Looper
    public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
        }
     }

    //為當前執行緒設定一個Looper
    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));
     }

從上面的程式可以看出通過prepareMainLooper(),然後呼叫 prepare(boolean quitAllowed)方法建立了一個Looper物件,並通過sThreadLocal.set(new Looper(quitAllowed))方法將該物件設定給了sThreadLocal。

    //通過ThreadLocal獲取Looper
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
     }

通過Looper中的預備工作,sThreadLocal中已經儲存了一個Looper物件,然後myLooper()方法通過sThreadLocal.get()方法獲取到了Looper。那麼訊息佇列就與執行緒關聯上了,所以各個執行緒只能訪問自己的訊息佇列。

綜上所述,我們可以發現訊息佇列通過Looper與執行緒關聯上了,而Looper又與Handler是關聯的,所以Handler就跟執行緒、執行緒的訊息佇列關聯上了。

3.2 如何執行訊息迴圈?

在建立Looper物件後,通過Handler發來的訊息放在訊息佇列中後是如何被處理的呢?這就涉及到了訊息迴圈,訊息迴圈是通過Looper.loop()方法來建立的。原始碼如下:

    //執行訊息迴圈
    public static void loop() {
    final Looper me = myLooper();
    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(); // 獲取訊息
        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();//訊息回收
         }
    }   

從原始碼中我們可以看出,loop()方法實質上就是通過一個死迴圈不斷的從訊息佇列中獲取訊息,然後又不斷的處理訊息的過程。

3.3 訊息處理機制
msg.target.dispatchMessage(msg);//分發訊息

我們從loop中的 dispatchMessage()方法入手,看看誰是該方法的呼叫者,深入Message原始碼中看看target的具體型別:

    public final class Message implements Parcelable {
        //程式碼省略

        /*package*/ int flags;
        /*package*/ long when;
        /*package*/ Bundle data;
        /*package*/ Handler target;
        /*package*/ Runnable callback;
        /*package*/ Message next;

        //程式碼省略
    }

從原始碼中我們可以看到其實target就是Handler型別。所以Handler是將訊息傳送到訊息佇列暫時儲存下,然後又將訊息傳送給Handler自身去處理。那我們繼續到Handler原始碼中去看看Handler是如何處理訊息的:

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();
}
/**
 * Subclasses must implement this to receive messages.
 * 訊息處理方法為一個空方法,由子類去實現
 */
public void handleMessage(Message msg) {
}

從上面的原始碼中可以看出,dispatchMessage(Message msg)只負責分發Message。從Message原始碼中我麼可以知道callback為Runnable型別,如果callback不為空,則執行 handleCallback方法來處理,而該方法又會呼叫callback.run();如果如果callback為空,則呼叫handleMessage來處理訊息,而該方法又為空,所以我們會在子類中重寫該方法,並將修改UI的程式碼寫在裡面。之所以會出現這兩種情況,是因為Handler傳送訊息有兩種形式:

  • 在本文的一般使用步驟中,我使用的是sendMessage(msg)傳送訊息,此時callback就為空。
  • 當使用post傳送訊息時,callback就不為空。

以上就是Handler機制的原理,大致可以總結為:在子執行緒中Handler將訊息傳送到MessageQueue中,然後Looper不斷的從MessageQueue中讀取訊息,並呼叫Handler的dispatchMessage傳送訊息,最後再Handler來處理訊息。為了更好的幫助大家一起理解,我畫了一個Handler機制的原理圖:
這裡寫圖片描述

關於Handler機制補充如下幾點:

  • Handler建立訊息時用到了訊息池,在建立訊息時會先從訊息池中去查詢是否有訊息物件,如果有,則直接使用訊息池中的物件,如果沒有,則建立一個新的訊息物件。
  • 使用ThreadLocal的目的是保證每一個執行緒只建立唯一一個Looper。之後其他Handler初始化的時候直接獲取第一個Handler建立的Looper

以上就是我對Handler機制原理的理解,希望對你有所幫助。如有不足之處,歡迎指正!