天天看點

安卓中的消息循環機制Handler及Looper詳解

我們知道安卓中的UI線程不是線程安全的,我們不能在UI線程中進行耗時操作,通常我們的做法是開啟一個子線程在子線程中處理耗時操作,但是安卓規定不允許在子線程中進行UI的更新操作,通常我們會通過Handler機制來完成該功能,即當子線程中耗時操作完成後,在子線程中通過Handler向主線程發送消息,在主線程中的Handler的handleMessage方法中處理接受到的消息。這就是安卓中的消息機制,安卓中的消息機制主要是指Handler的運作機制,但是Handler的運作需要底層的MessageQueue和Looper機制的支撐。

下面我們來詳解講解安卓中的消息循環機制,即Handler,MessageQueue,Looper機制。注意本部落格屬于安卓進階内容,是以一些基礎性的東西如果看官不懂情請自行百度解決。

首先我們來看一個标準的使用Handler機制的代碼示範例子:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                   // handle messages here
              }
          };

          Looper.loop();
      }
  }      

這段代碼大家肯定都不陌生,因為這是安卓中使用Handler機制的一個最标準的代碼示範,也許看官可能會說我在使用Handler的時候沒使用Looper.prepare()與Looper.loop()

語句,那時因為我們使用Handler通常是在UI線程中,而UI線程已經預設為我們做好了這些準備工作,至于這個待會後面會講到。之是以選擇在一個子線程中建立Handler的例子來講解,是因為這樣能讓我們更加清楚的明白安卓消息循環中Handler,MessageQueue,Looper這三者之間的關系,因為在主線程中很多細節都被掩藏起來了。

首先從這段代碼可以看到這裡涉及兩個概念(事實上安卓中的消息循環機制涉及三個重要概念),這裡先簡單介紹下

Looper:消息循環,用于從MessageQueue消息隊列中取出消息進行處理。

Handler:消息發送與處理,Handler通過sendMessage向消息隊列MessageQueue中發送一條消息,通過handlerMessage來處理消息

MessageQueue:消息隊列,用來存放Handler發送的消息,它僅僅用來存放消息

首先講解一下安卓中的消息循環的整體過程,然後從上述的示範代碼上進行詳細的講解。

安卓中的消息循環是使用Looper這個類來實作的,而Looper是基于線程的,即一個Looper對象與建立它的線程相關聯,當使用 Looper.prepare()語句時它會建立一個Looper對象和一個MessageQueue對象,然後在建立Handler時會先擷取到Handler所在的線程的Looper對象(如果調用的是無參構造函數時),通過這個Looper對象同時可以獲得其MessageQueue對象,正因為如此相當于Handler與Looper相關聯。這樣通過Handler發送的消息才知道應該通過哪個Looper去進行處理。這是了解安卓整個消息循環機制核心,即MeaageQueue與Looper相關聯,Handler與Looper相關聯,從這裡也可以看出Looper是安卓消息循環機制的核心。

下面以上述示範代碼為例進行詳細講解。

首先我們來看一下 Looper.prepare()語句,即看一下prepare的源碼:

public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper());
}      

從這裡可以看到prepare()函數作了兩件事:建立一個looper對象,然後通過 sThreadLocal.set(new Looper());語句将其加入到 sThreadLocal中,這裡就不得不提一下安卓消息循環機制中一個重要的概念:ThreadLocal,因為這涉及到Looper對象與線程相關聯的功能的實作。ThreadLocal它的作用是在不同的線程中通路同一個ThreadLocal對象時通過ThreadLocal擷取到的值是不一樣的,即ThreadLocal中存儲的值是基于線程的。我們來看一下ThreadLocal的set方法:

public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }      

可以看到set函數首先擷取目前的線程,然後通過目前線程産生一個Valuse對象,然後通過values的put方法将value(即Looper對象)與this(即ThreadLocal對象)相關聯,通過這段代碼我們應該知道Looper是與線程相關的,因為set方法會通過線程産生Valuse對象,然後通過Valuse對象put方法,将Looper儲存起來。

接下來我們來看Handler handler=new Handler();這個語句,我們來看一下Handler()這個無參構造函數。

public Handler() {
    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 = null;
}      

從這裡我們可以看到首先調用了 Looper.myLooper();方法,該方法是傳回目前線程所關聯的Looper,且可以看到如果擷取到的mLooper為null則會抛出異常,這也說明建立Handler之前必選先建立Handler對象,獲得了Looper對象之後,我們就可以擷取到與Looper相關聯的MessageQueue對象,即 mQueue = mLooper.mQueue;前面講過建立Looper時會建立消息隊列MessageQueue。這段代碼我們重點來看一下Looper.myLooper();這個方法,通過這個函數擷取到了與目前線程相關聯的Looper對象,正因為如此,Handler對象發送的消息才知道應該被那個Looper處理。我們來看一下其代碼:

public static Looper myLooper() {
        return sThreadLocal.get();
    }      

可以看到他是通過sThreadLocal.get()方法來取得Looper對象,這個get與我們上面講述的ThreadLocal的set方法是相對應的,一個用來存儲資料,一個用來取出資料。

我們來看一下sThreadLocal.get()方法:

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }      

可以看到sThreadLocal.get()方法先擷取目前線程,然後通過目前線程構造Values對象,然後通過valuse傳回其存儲的資料(當然該資料為Looper對象),也正因為如此在Handler中擷取到的Looper對象與我們在目前線程中建立的Looper對象是同一個對象,這是保證Handler對象發送的資訊被正确的Looper所處理的關鍵。

最後看一下Looper.loop();語句,這個語句的作用是開啟Looper循環,我們來看一下其源代碼:

public static final void loop() {
    Looper me = myLooper();
    MessageQueue queue = me.mQueue;
    while (true) {
        Message msg = queue.next(); // might block
        if (msg != null) {
            if (msg.target == null) {
                return;
            }
            if (me.mLogging!= null) me.mLogging.println(
                    ">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what
                    );
            msg.target.dispatchMessage(msg);
            if (me.mLogging!= null) me.mLogging.println(
                    "<<<<< Finished to    " + msg.target + " "
                    + msg.callback);
            msg.recycle();
        }
    }
}      

可以看到loop方法首先擷取目前Looper對象,然後擷取該Looper對象的MessageQueue對象,然後在一個while死循環中不斷的通過 queue.next();從消息隊列中取出一個消息,然後通過消息msg.target這個屬性來調用dispatchMessage(msg);來分派消息,msg.target這個屬性本質上是發送消息給這個MessageQueue的Handler對象,即通過此語句就将Handler發送的消息交給它的dispatchMessage(msg);方法來處理,這樣就将代碼邏輯切換到建立該Handler的線程中去執行了。

我們來看一下Handler的dispatchMessage(msg);方法

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

可以看到在該方法中調用了 handleMessage(msg);,而這個正是我們在Handler中處理消息的回調方法。

最後就是我們使用sendMessage來發送消息的過程,Handler提供了多個發送消息的函數,最終都會調用sendMessageAtTime()方法,我們來看一下其源碼:

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this;
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
    else {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}      

可以看到在該方法中最終調用了MessageQueue的enqueueMessage(msg, uptimeMillis);方法,顧名思義,其作用是将一個消息入隊,它的源碼不重要,我們隻需知道它是通過将一個消息插入到一個單向連結清單中的方式來完成的入隊操作即可。

在前面我們說過在主線程中我們是不需要建立一個Looper的,這是因為這些工作安卓系統幾經幫我們做好了,安卓中的主線程即ActivityThread,主線程的入口函數為main,我們來看一下其源代碼:

public static void main(String[] args) {
    SamplingProfilerIntegration.start();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();
    EventLogger.setReporter(new EventLoggingReporter());
    Process.setArgV0("<pre-initialized>");
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    if (false) {
        Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}      

可以看到在該方法中調用了Looper.prepareMainLooper()方法,而Looper.prepareMainLooper()方法會調用Looper.prepare()方法。是以在主線程中我們不需自己建立一個Looper對象。

好了,通過上述的講解相信看官對安卓中的消息機制已經非常的清楚了,下面總結一下:

1Looper對象是安卓消息循環的核心,Looper對象是基于線程的,我們在建立一個Looper對象時會通過ThreadLocal對象将我們建立的Looper與其所在的線程相關聯起來,具體來說是通過 sThreadLocal.set(new Looper());語句将其儲存起來,另外在建立一個Looper對象時其内部會幫我們自動建立了一個消息隊列MessageQueue對象。

2Looper的作用是通過一個while死循環來不斷的檢視消息隊列MessageQueue中是否存在消息Message,若存在則會通過 msg.target.dispatchMessage(msg);将消息交給發送消息的Handler對象來進行處理。

3我們在建立一個Handler對象時,其内部首先會獲得目前線程所關聯的Looper對象,即調用 Looper.myLooper();方法,具體來說是通過sThreadLocal.get()方法來完成的,

這樣就保證了在Handler中擷取到的Looper對象與我們在目前線程中建立的Looper對象是同一個對象,進而保證Handler對象發送的資訊被正确的Looper所處理。

4另外在建立一個Handler對象時,其内部會通過擷取到的Looper對象來擷取與Looper對象相關聯的MessageQueue對象,這樣通過sendMessage發送的消息會發送到這個MessageQueue對象中,這也是保證Handler對象發送的資訊被正确的MessageQueue所處理的關鍵(其本質是通過3來完成的,因為MessageQueue的建立是在Looper内部完成的,即MessageQueue是與Looper相關聯的)。

5MessageQueue僅僅用來儲存消息而已,消息的分派是通過Looper來完成的,在Looper的loop循環中會通過一個while死循環來不斷的檢視消息隊列MessageQueue中是否存在消息Message,若存在則會通過 msg.target.dispatchMessage(msg);将消息交給發送消息的Handler對象來進行處理,這樣就将代碼的邏輯切換到Handler所在的線程中進行處理。

用圖示表示如下:

安卓中的消息循環機制Handler及Looper詳解

繼續閱讀