Android中Handler的理解與總結
android的非同步處理訊息機制Handler這個問題是老生常談哪,這個要追溯到一個面試的場景了,面試官說,handler傳送完訊息後,什麼時候觸發迴圈,這個我說了,handler原始碼中有個looper,這個是用來迴圈取出handler傳送到訊息佇列(messageQueue)中的訊息,一旦Looper開啟Looper.loop()就開啟無限迴圈,直接取出MessageQueue中所有的訊息,然後面試官不知道是他怎麼理解的,我說的是looper.loop()就開啟迴圈,他沒說話,可能不在一個頻道上抑或是我回答的不對?我感覺他也不是特別的理解吧,哎,有點坑哪,這面試真是醉了,什麼樣的面試官都會遇到,面試還真是運氣加上實力,我覺得吧,可能還是緣分未到吧,但是我喜歡總結,我覺得自己要深刻的理解,現在我就去再去深入的看下這個Handler,下次再問到此類問題信手拈來,插一句,可能那個面試官他真的不是特別理解吧!
首先,咱們理解幾個概念,見名知意吧,下面的部落格中會以口語化形式表述出來。
Message(訊息),MessageQueue(訊息佇列),Looper(迴圈,很重要),Handler(用來發送和處理訊息)
1.Handler分析傳送訊息的過程
Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };上面那種寫法咱們是咱們最常用的,new完後,重寫handleMessage方法,裡面的傳遞介質Message就是我們傳送的訊息,我們看看他的內部是如何處理的,直接點選Handler
/** * Default constructor associates this handler with the {@link Looper} for the * current thread. * * If this thread does not have a looper, this handler won't be able to receive messages * so an exception is thrown. */ public Handler() { this(null, false); }看到他的建構函式使用的這類裡面的構造,傳入引數是null 和 false,我們繼續點選this這個函式
/**第一個引數是一個回撥的介面,這個介面也是在Handler內部定義的如下所示:* Use the {@link Looper} for the current thread with the specified callback interface * and set whether the handler should be asynchronous. * * Handlers are synchronous by default unless this constructor is used to make * one that is strictly asynchronous. * * Asynchronous messages represent interrupts or events that do not require global ordering * with respect to synchronous messages. Asynchronous messages are not subject to * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param callback The callback interface in which to handle messages, or null. * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for * each {@link Message} that is sent to it or {@link Runnable} that is posted to it. * * @hide */ 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; }
/** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. * * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public interface Callback { public boolean handleMessage(Message msg); }其實也是最後呼叫handlerMessage(Message msg)這個方法,就像我們剛才使用的那種方式,我們可以這樣理解,這個Handler有好幾種的使用方式,可以傳介面,也可以直接new出來,然後重寫handlerMessage方法,幾個入口吧。
第二個引數是一個boolean型別,這個訊息是否是非同步的,這裡我們new出來的預設是false,也就是不是非同步的訊息,是同步的訊息,非同步的訊息這裡提前說下,非同步訊息就不能保證順序了,因為這裡面還有MessageQueue訊息佇列的概念,下面會繼續說的。
ok,new完handler後,可以看到給mLooper 設定了與當前執行緒相關聯的Looper物件,mQueue為當前looper物件裡面的訊息佇列,而looper和messageQueue是在Looper物件例項化後相關聯的,可以看下兩者的關聯程式碼
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }ok,上面講的是new完Handler後的程式碼追蹤,也就是在newHanlder後,獲取到當前的looper物件並設定到Handler裡面的成員變數mLooper,還有將Handler成員變數訊息佇列mQueue 設定為當前Looper物件所關聯的訊息佇列mLooper.mQueue;
下面這個步驟就是handler傳送訊息了,追蹤下程式碼
/** * Enqueue a message into the message queue after all pending messages * before the absolute time (in milliseconds) <var>uptimeMillis</var>. * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b> * Time spent in deep sleep will add an additional delay to execution. * You will receive it in {@link #handleMessage}, in the thread attached * to this handler. * * @param uptimeMillis The absolute time at which the message should be * delivered, using the * {@link android.os.SystemClock#uptimeMillis} time-base. * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the message will be processed -- if * the looper is quit before the delivery time of the message * occurs then the message will be dropped. */ 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); }不管你是sendMessage還是sendMessageDelayed最終程式碼都會執行到這個sendMessageAtTime方法中,可以看到第一個引數是Message我們的傳遞介質,訊息載體,第二個就是時間,訊息的絕對時間,然後接著追蹤程式碼
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,可以很好的理解,即當前訊息的目標,也就是傳送訊息的控制代碼是當前的物件也就是Handler,將其賦值,然後判斷是否是非同步的訊息,如果是,設定為true,然後將這個訊息入隊返回true 或者false,表示訊息進入佇列是否成功
繼續往下看
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;這是訊息入隊的程式碼,會先判斷當前目標target是否為null,這個訊息是否正在使用等最後返回true,表示訊息入隊成功。
2.疑問:到這裡我們可能要問了,這邏輯已經順著下來了,handler什麼時候去處理訊息哪,現在只有入沒有處理訊息啊?
ok,上述1的過程只是訊息的傳送過程,現在我們來看看訊息的處理,我們不要忘記了Looper這個物件,先檢視其原始碼,其中有這個方法
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() {
當looper呼叫這個方法後會開啟無限迴圈,迴圈從messageQueue中取出message,然後呼叫msg.target.dispatchMessage去處理訊息,可以看下程式碼
try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } }而target就是當前new的handler,我們看handler裡面dispatchMessage的方法是如何處理的
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }看到上面的註釋說明”在這裡處理系統的訊息“,然後我們驚喜的發現了callback,對,沒錯,就是在最初初始化handler的時候,傳的callback引數,但是我們這個是null啊,對的,所有他執行了else,執行了handleMessage,然後這就是我們為什麼重寫handleMessage的原因,好了,但是callback的使用場景是什麼
new Handler().post(new Runnable() { @Override public void run() { new TextView(TestActivity.this).setText("xxxxx"); } });我們直接使用handler.post 傳送了一個runnable,點選post後,可以看到執行的順序和sendMessage是一樣的
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }不過是通過getPostMessage(r)獲取了一個訊息,而此時callback = r;
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
此時的callback不為空,那最後執行到這個dispatchMessage時,由於callback不是null,所以直接執行了runnable的run方法,不信,請看程式碼
private static void handleCallback(Message message) { message.callback.run(); }整個流程走完了,但是回到最初的問題,面試官說,什麼時候觸發迴圈,因為我們已經知道必須呼叫looper.loop()方法才能觸發無限迴圈,說這樣也沒錯啊,但是我們new的時候並沒有使用looper的loop方法啊,ok,問題就在這裡,我們Activity在初始化的時候,系統已經幫我們做好了
Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop();在ActivityThread這個類中,系統初始了mainLooper,所以new Handler後預設為mainLooper,最下面的那個Looper.loop(),系統已經幫助我們開啟訊息迴圈了,比如我們之前經常這樣寫
new Thread(){ public void run(){ Looper.prepare(); handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; Looper.loop(); } }.start(); }上述程式碼是在子執行緒中使用handler,這個時候要注意,因為已經切換了執行緒,不再是預設的UI主執行緒,所以looper也不再是main Looper,所以Looper.prepare是將當前子執行緒繫結到當前looper物件,最後一定要開啟訊息迴圈,這是最經典的寫法和使用。
綜上所述,可能我沒有get到面試官的點吧,抑或是面試官在這個問題上也存在著疑問吧,不管怎麼說吧,自己都要要求自己去理解,不能再知道表層了,與君共勉吧!
參考文章: