標籤:
轉載註明出處:http://blog.csdn.net/xiaohanluo/article/details/51994401
簡書連結:http://www.jianshu.com/p/e72aba99012a
簡介
在Android開發中經常會遇見非同步訊息處理的情況,特別是網路請求成功或者失敗之後需要更新UI,但是更新UI只能在UI線程裡面進行,否則就會報錯,這個時候就需要通知UI線程去更新UI,於是Handler出現了。
Handler的使用
Handler的使用非常簡單,直接看例子。
private static final int KEY_REQUEST_SUCCESS = 0x01; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //這裡是在UI線程了 if(msg.what == KEY_REQUEST_SUCCESS) { //更改UI } } }; ... ...//一些其他動作 private void changeUI() { Message message = mHandler.obtainMessage(); message.what = KEY_REQUEST_SUCCESS; mHandler.sendMessage(message); }
聲明了一個Handler成員變數mHandler,並複寫了其中的handleMessage(Message msg)方法,然後使用mHandler發送一條訊息,最終會調用到handleMessage方法,然後進行UI更新操作。
看見這麼好用的非同步訊息處理方法,肯定會去看看內部是怎麼實現訊息的調度的。
Handler內部資訊調度訊息進入隊列
所有的訊息都是使用sendMessage方法發送出去(還有其他類似方法),就從sendMessage開始查看。
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } ... public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } ... public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { //異常處理 } return enqueueMessage(queue, msg, uptimeMillis); } ... private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
我們可以看到,Handler發送訊息最終都會調用到sendMessageAtTime方法,在這個方法裡面就做了一件事,將訊息放入一個訊息佇列中。記住msg.target這個賦值操作,然後我們去看看在放入隊列時候都做了些什麼。
boolean enqueueMessage(Message msg, long when) { //異常處理 synchronized (this) { //異常處理 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 { 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; prev.next = msg; } if (needWake) { nativeWake(mPtr); } } return true; }
從代碼中很清晰的可以看見。
- 如果新訊息首次排入佇列,或者新訊息延遲時間要小於隊列首個訊息延遲時間,就將新訊息放在隊列首部。
- 如果隊列不為空白,新訊息延遲時間並不是最短,就將新訊息插入到隊列的某個位置。
總之,新訊息插入隊列後,訊息佇列是按延遲時間長短排序的一個有序隊列,延遲時間越短,越靠近隊列首部。這樣,就可以猜測,系統一次拿去隊列中的訊息,然後進行分發處理。
訊息調度分發
我們都知道了訊息被加入了一個按延遲長短排序的有序隊列,訊息僅僅加入了隊列還需要有地方處理才行,那我們就看看哪裡處理了這個隊列。
首先我們來查看一下Handler的成員變數中的MessageQueue是在哪裡初始化或者賦值的,從源頭開始尋找,順藤摸瓜。
public Handler() { this(null, false); } ... public Handler(Callback callback, boolean async) { ... 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賦值的,那會不會對這個訊息佇列的處理就在Lopper當中呢?趕緊去看看。Looper的代碼不多,很快就看到了loop()這個方法。
public static void loop() { final Looper me = myLooper(); //異常處理 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(); } }
就是在這裡!!在loop()方法中有一個迴圈一直在取出訊息佇列中的訊息然後進行分發工作。我們注意這一行代碼msg.target.dispatchMessage(msg),調用了msg.tartget中的一個分發訊息的方法,還記得在上面訊息進入訊息佇列時候,注意的msg.target的賦值操作嗎?msg.target其實就是我們發送訊息的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); } }
很容易理解如果Message和Handler的CallBack成員變數都為空白的話,直接調用Handler本身的handlerMessage(Message msg)方法,這個方法就是我們在執行個體化Handler時候覆寫的一個方法,最終訊息的具體處理過程就回到了我們手中。
調度圖
Handler發送Message訊息,訊息進入訊息佇列,Looper不斷的從訊息佇列中取出Message,然後交給Handler處理,處理的代碼交給使用者處理。這樣我們就可以在非UI線程中發送Message訊息,但是最終的處理回到了UI線程,進行一些UI操作。
其他用法
雖然Handler是用於處理非同步訊息的,但是由於它本身有一個延遲的功能,所以Handler也可以做一個定時任務處理器,類似廣告結束倒計時等等都可以用到Handler。
Tips
Handler雖然看起來使用簡單,但是如果仔細的話,會發現,這一套機制對Message的管理是非常嚴格的。將訊息從訊息佇列中刪除或者訊息處理完畢之後,並不是直接將Message捨棄,而是調用了Message本身的recycleUnchecked()方法。
void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
首先將Message裡面一些成員變數進行了重設操作,在最後面我們發現了MAX_POOL_SIZE這個屬性!!這說明在這一套機制中,Message數量是有上限的。心細的人就會發現,如果每次使用Message時候都使用new執行個體化一個的話,總會有一個時間點,Message數量會超出最大限制數。
所以我們在擷取Message的時候,一定要使用Handler.obtainMessage()方法或者Message.obtain(Handler h)方法。
如果使用new去執行個體化Message的話,這裡面有坑,謹慎。
Q&A
Looper類的loop()方法處理完隊列之後就會停止迴圈,如果有新的訊息加入,系統是怎麼調用到Looper的loop()的呢?
在將資訊加入到訊息佇列後,會運行nativeWake(mPtr)這一行代碼,nativeWake()是一個本地的方法,是通過這個方法在系統的底層去調用Looper.loop()方法。
由Handler發送出一個延遲任務訊息,這個訊息還未被處理,怎麼撤回?
Handler撤回訊息有五種方法。
- removeCallbacks(Runnable r)
- removeCallbacks(Runnable r, Object object)
- removeMessages(int what)
- removeMessages(int what, Object object)
- removeCallbacksAndMessages(Object object)
但是實際上是在MessageQueue隊列中刪除了訊息,MessageQueue提供了三種刪除訊息方法
- removeMessages(Handler h, int what, Object object)
- removeMessages(Handler h, Runnable r, Object object)
- removeCallBackAndMessage(Handle h, Runnable r, Object object)
參數都是Message中的成員變數(Handler參數會在Handler調用MessageQueue刪除訊息方法傳入,也就是Handler本身),當傳入參數與Message中屬性相等時候會將訊息從訊息佇列中刪除。參數object是補充條件,object傳入null,則前麵條件滿足即可;object不為空白,所有條件都需要滿足。
void removeCallbacksAndMessages(Handler h, Object object) { if (h == null) { return; } synchronized (this) { Message p = mMessages; // Remove all messages at front. while (p != null && p.target == h && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } // Remove all messages after front. while (p != null) { Message n = p.next; if (n != null) { if (n.target == h && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; } } }
從代碼中很容易看出來,如果調用MessageQueue的removeCallbacksAndMessage方法時候,object傳入是null,會將訊息佇列中所有訊息都給清空。所以這樣調用mHandler.removeCallbacksAndMessage(null)時候,會清空訊息佇列中所有資訊。
待完善…
Handler非同步訊息處理機制,從原始碼看起