我們都知道在UI線程中不能進行耗時操作,例如數據讀寫、網路請求。Android 4.0開始,在主線程中進行網路請求甚至會拋出Android.os.NetworkOnMainThreadException。這個時候,我們就會開始依賴Handler。我們在子線程進行耗時操作後,將請求結果通過Handle ...
我們都知道在UI線程中不能進行耗時操作,例如數據讀寫、網路請求。Android 4.0開始,在主線程中進行網路請求甚至會拋出Android.os.NetworkOnMainThreadException。這個時候,我們就會開始依賴Handler。我們在子線程進行耗時操作後,將請求結果通過Handler的sendMessge**() 方法發送出去,在主線程中通過Handler的handleMessage 方法處理請求結果,進行UI的更新。
後來隨著AsyncTask、EventBus、Volley以及Retrofit 的出現,Handler的作用似乎被弱化,逐漸被大家遺忘。其實不然,AsyncTask其實是基於Handler進行了非常巧妙的封裝,Handler的使用依然是其核心。Volley同樣也是使用到了Handler。因此,我們有必要瞭解一下Handler的實現機制。
神奇的Handler
記得很久之前的一天,我在閱讀別人的代碼時,看到了這樣一段:
- new Handler().postDelayed(new Runnable() {
- public void run() {
- Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();
- }
- }, 1000);
第一印象就是,這不是在子線程中進行UI操作嗎?這代碼有問題吧,於是乎立刻在自己電腦上寫了個demo試了一下,結果發現真的沒有問題。在一陣懵逼過後,我又寫出下麵的代碼,測試一下子線程中到底能不能進行UI操作。
- new Thread(new Runnable() {
- public void run() {
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();
- }
- }).start();
結果很明顯,程式一啟動立刻就奔潰了。並拋出異常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
。
於是乎我又在try block 之前添加了Looper.prepare()這行代碼。再次運行程式雖然沒有奔潰,但也沒有任何反應,Toast也沒顯示。
那麼Handler到底是什麼呢?他怎麼就這麼神奇。
實現機制解析
首先,我們從整體上瞭解一下,在整個Handler機制中所有使用到的類,主要包括Message,MessageQueue,Looper以及Handler。
好了,為了方便後面的敘述,我們就首先瞭解一下這個類圖中使用到幾個類,及其關鍵方法。
Message
首先看一下Message這個類的定義(截取部分)
- public final class Message implements Parcelable {
- public int what;
- public int arg1;
- public int arg2;
- public Object obj;
- /*package*/ Handler target;
- /*package*/ Runnable callback;
- /**
- * Return a new Message instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
- */
- public static Message obtain() {
- synchronized (sPoolSync) {
- if (sPool != null) {
- Message m = sPool;
- sPool = m.next;
- m.next = null;
- m.flags = 0; // clear in-use flag
- sPoolSize--;
- return m;
- }
- }
- return new Message();
- }
- /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
- */
- public Message() {
- }
- }
看到這個類的前四個屬性,大家應該很熟悉,就是我們使用Handler時經常用到的那幾個屬性。用來在傳遞我們特定的信息。其次我們還可以總結出以下信息:
- Message 實現了Parcelable 介面,也就是說實現了序列化,這就說明Message可以在不同進程之間傳遞。
- 包含一個名為target的Handler 對象
- 包含一個名為callback的Runnable 對象
- 使用obtain 方法可以從消息池中獲取Message的實例,也是推薦大家使用的方法,而不是直接調用構造方法。
MessageQueue
MessageQueue顧名思義,就是上面所說的Message所組成的queue。
首先看一下構造方法:
- MessageQueue(boolean quitAllowed) {
- mQuitAllowed = quitAllowed;
- mPtr = nativeInit();
- }
接收一個參數,決定當前隊列是否允許被終止。同時調用 一個native方法,初始化了一個long類型的變數mPtr。
同時,在這個類當中,還定義了一個next 方法,用於返回一個Message 。
- Message next() {
- // Return here if the message loop has already quit and been disposed.
- // This can happen if the application tries to restart a looper after quit
- // which is not supported.
- final long ptr = mPtr;
- if (ptr == 0) {
- return null;
- }
- ……
- }
由於這個方法中有一些native調用,未能完全理解,只知道會返回一個Message對象。
這個next方法相當於是隊列出棧,有出棧必然有進棧,enqueueMessage 方法就是完成這個操作;這個我們後面再說。
Looper
上面說到了MessageQueue,那麼這個Queue又是由誰創建的呢?其實就是Looper。關於Looper有兩個關鍵方法:
prepare() 和 loop()
Looper-prepare()
- 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。也就是說執行prepare方法時,必然執行最後一行代碼
sThreadLocal.set(new Looper(quitAllowed));
我們再看Looper(quitAllowed)方法:
- private Looper(boolean quitAllowed) {
- mQueue = new MessageQueue(quitAllowed);
- mThread = Thread.currentThread();
- }
這樣,MessageQueue 就被創建了。這裡也可以看到,預設情況下,一個MessageQueue的quiteAllow=true。
這裡使用到的sThreadLocal 是一個ThreadLocal對象。簡單來說,使用它可以用來解決多線程程式的併發問題。使用set方法,將此線程局部變數的當前線程副本中的值設置為指定值;使用get方法,返回此線程局部變數的當前線程副本中的值。
Looper-loop()
再看一下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(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- msg.target.dispatchMessage(msg);
- msg.recycleUnchecked();
- }
- }
首先看第一句代碼執行的方法:
- /**
- * Return the Looper object associated with the current thread. Returns
- * null if the calling thread is not associated with a Looper.
- */
- public static
- return sThreadLocal.get();
- }
很明顯,這樣返回的Looper就是剛纔prepare時set進去的那個,因為都是在同一線程。再明確一下,一個線程對應一個Looper。
這樣就確保我們可以在不同的線程中創建各自的Handler,進行各自的通信而不會互相干擾
回到代碼,後面邏輯就很簡單了,在一個死迴圈中,通過隊列出棧的形式,不斷從MessageQueue 中取出新的Message,然後執行msg.target.dispatchMessage(msg) 方法,還記的前面Message類的定義嗎,這個target屬性其實就是一個Handler 對象,因此在這裡就會不斷去執行Handler 的dispatchMessage 方法。如果取出的Message對象為null,就會跳出死迴圈,一次Handler的工作整個就結束了。
Handler
上面說了這麼多終於輪到Handler,那麼就看看在Handler中到底發生了什麼。回到我們一開始的代碼。
- new Handler().postDelayed(new Runnable() {
- public void run() {
- String currentName=Thread.currentThread().getName();
- Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();
- }
- }, 4000);
這裡我們用Toast彈出了當前線程的name,結果發現這個線程的名字居然是main,這也是必然結果
讓我們一步一步看看,神奇的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());
- }
- }
- 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;
- }
這裡做的事情很簡單,就是完成了一些初始化的工作,調用Looper.myLooper()賦值給當前mLooper,關聯MessageQueue;這裡由於代碼中調用的是不帶任何參數的構造函數,因此會創建一個mCallback=null且非非同步執行的Handler 。
接下看postDelayed 方法。
- public final boolean postDelayed(Runnable r, long delayMillis)
- {
- return sendMessageDelayed(getPostMessage(r), delayMillis);
- }
- private static Message getPostMessage(Runnable r) {
- Message m = Message.obtain();
- m.callback = r;
- return m;
- }
這裡通過getPostMessage(Runnable r) 方法,把我們在Activity里寫的Runnable 這個線程賦給了Message 的callback這個屬性。
平時大家使用Handler也發現了,他為我們提供了很多方法
因此,上面的postDelayed經過了各種輾轉反側,最終來到了這裡:
- 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);
- }
經過之前的構造方法,mQueue顯然不為null,繼續往下看
- 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;
前面提到,這個target就是一個Handler對象,因此這裡Message就和當前Handler關聯起來了。enqueueMessage,哈哈,這就是我們之前在MessageQueue中提到的進棧操作的方法,我們看一下:
- 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;
- 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 {
- // Inserted within the middle of the queue. Usually we don't have to wake
- // up the event queue unless there is a barrier at the head of the queue
- // and the message is the earliest asynchronous message in the queue.
- 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;
- }
- // We can assume mPtr != 0 because mQuitting is false.
- if (needWake) {
- nativeWake(mPtr);
- }
- }
- return true;
- }
這個方法就是典型的隊列入隊操作,只不過會根據Message這個對象特有的一些屬性,以及當前的狀態是否inUse,是否已經被quit等進行一些額外的判斷。
這樣,我們就完成消息入隊的操作。還記得我們在Looper中說過,在loop方法中,會從MessageQueue中取出Message 並執行他的dispatchMessage 方法。
dispatchMessage
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
到這裡,就很明確了,在之前的postDelayed 方法中,已經通過getPostMessage,實現了 m.callback = r;這樣這裡就會執行第一個if語句:
- private static void handleCallback(Message message) {
- message.callback.run();
- }
這樣,就會執行我們在Activity 的Runnable 中的run 方法了,也就是顯示Toast。
到了這裡,我們終於明白了,使用Handler 的postDelay 方法時,其Runnable中的run方法並不是在子線程中執行,而是把這個Runnable賦值給了一個Message對象的callback屬性,而這個Message會被傳遞到創建Handler所在的線程,也就是這裡的主線程,所以這個Toast的顯示依舊是在主線程中。這也和postDelay API 中所聲明的內容是一致的。
/**
- * Causes the Runnable r to be added to the message queue, to be run
- * after the specified amount of time elapses.
- * The runnable will be run on the thread to which this handler
- * is attached.
- */
到這裡,一開始所說的第一個代碼塊所執行的邏輯已經理清楚了,但是還是有一點疑問,我們並沒有在Handler的構造方法中看到Looper 的prepare()方法和loop() 方法被執行,那麼他們到底是在哪裡執行的呢?這個問題我也是疑惑了很久,最終才明白是在
ActivityThread的main方法中執行。簡單來說,ActivityThread是Java層面一個Android程式真正的入口。關於ActivityThread更多的內容可以看看這篇文章。
ActivityThread-main方法(截取主要部分)
- public static void main(String[] args) {
- Looper.prepareMainLooper();
- ActivityThread thread = new ActivityThread();
- thread.attach(false);
- if (sMainThreadHandler == null) {
-