Android訊息處理機制:源碼剖析Handler、Looper,並實現圖片非同步載入,
引言
我們在做 Android 開發時,常常需要實現非同步載入圖片/網頁/其他。事實上,要實現非同步載入,就需要實現線程間通訊,而在 Android 中結合使用 Handler、Looper、Message 能夠讓不同的線程通訊,完成非同步任務。雖然 Android 官方為我們提供了 AsyncTask 類來完成非同步任務,但這個類存在許多問題,並不好用,而且,AsyncTask 也是通過 Handler 和 Thread 來實現非同步載入的,所以學習這方面的知識是有必要的
本文講解思路大致如下:繪製 Android 訊息處理機製圖 -> 源碼剖析訊息處理機制中的組件 -> 實現一個圖片非同步載入 Demo。最終 Demo 如下:
Android 訊息處理機制剖析訊息處理模型
我們不妨先想想,一個訊息處理機制需要什嗎?當然是:
其中訊息管理器又將劃分為三個子模組:訊息擷取、訊息分發、訊息迴圈。我們先不管 Android 內部將如何?訊息處理機制(因為處理機制的抽象結構肯定是一樣的,只是具體實現不一樣),按照我們列出來的4大模組畫出一個簡單的訊息處理模型:
Android 訊息處理組件
現在我們已經知道訊息處理模型需要哪些組件了,那就去 Android SDK 裡面找相應的類吧~然後我們會發現下面四個類:
- Message 類代表訊息
- MessageQueue 類代表訊息佇列
- Handler 代表訊息擷取、訊息處理
- Looper 代表訊息迴圈、訊息分發
可能有人會懷疑我在吹nb,在騙大家,這個時候我只能選擇看源碼了……為了方便大家理解,我將從 Looper 類開始分析,因為 Looper 類在訊息處理機制中是個“承上啟下”的功能模組。
Looper
在解析 Looper 之前,不妨先來想想為什麼需要 Looper 吧。
我們在進行 Android 開發的時候,為了不阻塞主線程(UI 線程),常常需要另開一個線程完成一些操作,而這些操作有一些執行一次就完了,有一些可能需要執行幾次,幾十次,甚至只要程式進程存活就要不斷執行該操作。而普通線程通過 start() 方法只能執行相關動作一次,為了滿足多次執行的需求,於是有了 Looper。
那麼我們就進入 Looper 的源碼,看看 Looper 中有哪些成員吧:
public final class Looper { private static final String TAG = "Looper"; static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; final Thread mThread; private Printer mLogging;}
大家可以看到,Looper 的核心成員是一個訊息佇列,該Looper 對應的線程,ThreadLocal 對象,和一個主線程 Looper 的引用。我們根據 Looper 的使用流程來分析它們的作用:
要使用 Looper,就必須調用 Looper 的 prepare() 方法:
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)); }
我們可以看到,調用 prepare 方法後會通過 ThreadLocal 的 set 方法建立一個 Looper 對象,而且一個線程只能建立一個 Looper,我們不妨看看 ThreadLocal 通過 set 方法對 Looper 對象幹了啥:
public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
實際操作 Looper 對象的是 values() 方法返回對象
Values values(Thread current) { return current.localValues; }
values() 方法返回的對象是一個線程的內部變數,我們再進去看看會發現:在 Thread 類內部是這樣定義 localValues 的 - ThreadLocal.Values localValues。也就是說,set 方法實際完成的操作是,將 Looper 對象與線程綁定,並且該 Looper 對象只在該線程內有效,其他線程無法訪問該 Looper 對象。
執行完 prepare() 方法之後,我們就要調用 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; // 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(); for (;;) { Message msg = queue.next(); // might block 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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. 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(); } }
方法有點長,但實際邏輯比較簡單:首先判斷 prepare() 方法是否被調用,以確保 Looper 和某個線程綁定(需要注意的是:預設情況下,建立的 Looper 對象都與主線程綁定),然後擷取對應線程的訊息佇列,之後就不斷迴圈讀取隊列中的訊息,如果隊列中沒有訊息時 Loop() 方法就會結束(一般不會出現這種情況),否則將訊息交給 msg.target 對象分發。
以上就是 Looper 的核心代碼了,通過分析我們可以瞭解到 Looper 與線程的關係,以及在訊息分發機制中所起的作用,
Message
既然我們在分析 Looper 源碼的最後提到了 Message 類,那麼我們就先來看看 Message 類~那麼 Message 類作為訊息的載體到底儲存了什麼,做了什麼呢?
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; public Messenger replyTo; public int sendingUid = -1; /*package*/ static final int FLAG_IN_USE = 1 << 0; /** If set message is asynchronous */ /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; /** Flags to clear in the copyFrom method */ /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE; /*package*/ int flags; /*package*/ long when; /*package*/ Bundle data; /*package*/ Handler target; /*package*/ Runnable callback; // sometimes we store linked lists of these things /*package*/ Message next;
現在我們可以知道,原來剛剛處理訊息的 target 就是 Handler 類的對象啦。在 Message 類裡,what 用於辨識 Message 的用途,arg1 和 arg2 用於傳遞一些簡單的資料,obj 用於傳遞對象,data 用於傳遞複雜資料。
Message 類的方法我覺得是沒啥好說的,基本上都是 get/set 方法,當然還有回收方法,例如在 Looper 的 loop() 方法中,每一次迴圈結束都會執行 Message 的 recycleUnchecked() 方法,將被分發 Message 對象回收。
可能有人會奇怪,訊息如果沒有被處理就被回收了不會發生訊息丟失的情況嗎?莫慌,等會我會在分析 Handler 處理訊息的時候給大家解釋。
Handler
我們在前面的分析中看到,真正處理訊息的是 Handler 的 dispatchMessage() 方法,那麼我們就從這個方法入手分析 Handler 吧:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
在 dispatchMessage() 方法中,如果 msg 的 callback 不為 null 會調用 Handler 的 handleMessage() 方法處理訊息。也就是說,只要訊息的 callback 不為 null,就會調用 handleCallback() 方法,那麼未處理的訊息會不會回到訊息佇列呢?
private static void handleCallback(Message message) { message.callback.run(); }
看到這裡有沒有恍然大悟的感覺呢?剛剛我們在分析 Message 源碼的時候已經知道,callback 就是 Runnable 介面的執行個體,也就是說,如果訊息沒有被處理,就會回到訊息佇列中啦。那麼 Handler 又是怎樣處理訊息的呢?
public void handleMessage(Message msg) { }
竟然是個空方法……不過也很正常,因為 Handler 類只需要提供抽象,具體的處理邏輯應該由開發人員決定嘛。那我們分析就到此為止了嗎?才沒有!我們還沒有剖析 Handler 能夠實現非同步事件處理的原因呢,回到 Handler 的源碼,我們會看到下面這個程式碼片段:
final MessageQueue mQueue; final Looper mLooper; final Callback mCallback; final boolean mAsynchronous; IMessenger mMessenger;
我靠……Handler 裡面居然擁有訊息佇列、Looper、非同步標誌位,我們回想一下剛剛分析得到過什麼結論:一個 Looper 只能屬於一個線程,Looper 有對應線程的訊息佇列。我們再來看看 Handler 的構造方法,隨便挑一個吧:
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 內的訊息佇列就是 Looper 裡的訊息佇列,也就是說 Handler 能夠與任何一個線程的訊息佇列進行通訊,並處理其中的訊息,或者發送資訊到其他線程的訊息佇列中!
非同步處理 Demo
完成上面的分析以後,我們就知道在 Android 中的訊息處理機制了,那麼現在就來實現一個非同步處理 Demo吧。Demo 非常簡單,就是非同步下載一張圖片,並把它顯示到一個 ImageView 中,代碼比較多,就只給出核心代碼,下面有:
public class DownloadTask implements Runnable{………… private void updateMsg(Message msg){ Bundle data = new Bundle(); Bitmap img = downImage(url); data.putString("url", url); data.putParcelable("img", img); msg.setData(data); } public Bitmap downImage(String url) { Bitmap img = null; try { URL mUrl = new URL(url); HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); img = BitmapFactory.decodeStream(is); } catch (IOException e) { Log.d(TAG, "downloadImg-Exception"); } return img; }}
DownloadTask 類負責處理下載任務,下載開始下載任務,下載任務完成後將圖片地址和圖片存到 msg 裡邊,發送給 DownloadHandler :
public class DownloadHandler extends Handler{…… @Override public void handleMessage(Message msg) { String url = msg.getData().getString("url"); Bitmap img = msg.getData().getParcelable("img"); Log.d(TAG, url); loader.iLoader.update(img); }}
最後通過 ILoader 介面更新外部 UI:
public interface ILoader { public void update(Bitmap img); }}