引入:
提到Android中的消息機制,大家應該都不陌生,我們在開發中不可避免的要和它打交道。從我們開發的角度來看,Handler是Android消息機制的上層接口。我們在平時的開發中只需要和Handler交互即可。通過Handler我們可以將一個任務切換到Handler所在的線程中執行。日常中我們使用Handler的場景可能大多是在子線程中進行了耗時操作,比如網絡請求數據,拿到數據后我們可能會更新UI控件,因為Android規定只能在主線程進行UI操作,這時候我們通常會在主線程中創建一個Handler,然后通過在子線程中使用Handler發送消息發送到主線程,然后在主線程中消息回調的handleMessage()中處理我們的消息。但是本質來說,Handler並不是專門用來更新UI的,這是它的一個用途而已。
為什么需要這樣的一個消息機制呢?
我們知道每一個應用程序都有一個主線程,如果我們直接與主線程交互,訪問他的一些變量,對其進行一些修改操作,可能會帶來線程安全問題,並且不利於Android系統的整體運作。通過Android系統提供的一條消息處理機制,我們通過在子線程發送消息形式,讓主線程進行處理,然后我們的邏輯代碼就可以執行了。
具體的消息有兩種:我們自己定義的Handler和系統的Hander,其實我們Android中四大組件的運作也都是系統Handler進行着消息的處理,從而實現各個生命周期的回調,當然這里的消息種類很多,就不一一列舉了。這里注意一點,我們應用退出程序得到過程,應用程序退出應用其實就是讓主線程結束,換句話說也就是讓我們這里的Looper循環結束,這樣我們的四大組件生命周期就不會執行了,應為四大組件生命周期的回調依賴於Handler處理,Looper循環都沒了,四大組件還玩毛線哦。所以有時候我們的onDestroy方法不一定會回調。
Android消息機制概述:
Android的消息機制其實主要是指Handler的運行機制,而Handler的運行又需要底層MessageQueue和Looper的支撐。MessageQueue中文翻譯是指消息隊列,顧名思義,它的內部存儲了一組消息,以隊列的形式對外提供插入和刪除的工作。雖然叫消息隊列,但是它的內部存儲結構並不是真正的隊列,而是采用單鏈表的數據結構來存儲消息列表。這也可以想到,因為鏈表的結構對於插入刪除操作執行的效率比較高。Looper中文翻譯為循環,由於MessageQueue只是提供了一個消息的存儲,它並不能去處理消息,Looper就填補了這個功能,Looper會以無限循環的方式去查找是否有新消息,如果有的話就處理否則就會一直等待着。
其次Looper中還要一個特殊的概念ThreadLocal,用它可以在不同的線程中存儲數據,互不干擾。Handler創建的時候使用當前的Looper來構造消息循環系統,那么在Handler如何獲取到當前線程的Looper呢。就是通過這個ThreadLocal,ThreadLocal可以在不同線程中互不干擾的存儲和讀取數據,通過ThreadLocal就可以輕松獲取到每個線程的Looper。需要注意的是線程默認是沒有Looper的,我們在子線程使用Handler必須先創建Looper否則會發生異常。至於主線程為什么可以直接使用Handler呢?那是因為主線程在入口的main方法中就已經幫我們創建了一個Looper對象,並開啟了一個Looper循環,幫我構建好了這樣一個消息消息循環的環境。這里了解一下Android規定訪問UI只能在主線程中進行,如果在子線程中訪問UI就會發生異常,這是因為ViewRootImpl對UI的操作做了驗證,這個驗證是在ViewRootImpl的checkThread()方法中完成的。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
因此由於這個的限制對AndroidUI的操作必須在主線程,但是Android又不建議在主線程中執行耗時的操作,因為會阻塞主線程導致ANR異常。因此Handler就應運而生了,系統提供Handler主要原因是解決在子線程中無法更新UI的矛盾。那么剛才提到為什么不允許子線程中訪問UI呢?那是因為Android的UI控件並不是線程安全的。如果存在多線程的並發訪問可能會導致UI控件處於不可預期的狀態。那么系統為什么不對UI控件加鎖呢。一方面加鎖使邏輯更復雜,二來要進行鎖判斷,影響效率,阻塞了其它線程,所以簡單高效的模型就是單線程。對於我們來說也不麻煩一個Handler就哦了。
消息隊列MessageQueue的工作原理:
MessageQueue主要包含兩個操作:插入和讀取。讀取本身伴隨着刪除操作,對應着兩個方法分別是enqueueMessage()和next()。enqueueMessage是插入一條消息到消息隊列中,next是取出一條信息並將其從隊列移除。看他的方法源碼可以看到enqueueMessage主要是進行單鏈表的插入操作。next是一個無線循環,如果么有消息,next會一直阻塞在這里,如果有消息到來,next會返回這個消息並將其從隊列移除。
enqueueMessage方法:
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; }
next方法:
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; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
Looper的工作原理:
Looper會不停的從MessageQueue中查看是否有新消息,有的話立刻處理,沒有就會一直阻塞,它的構造函數初始化了一個MessageQueue,並獲取到當前線程。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
Handler的運行需要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)); }
緊接着通過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 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long traceTag = me.mTraceTag; if (traceTag != 0) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } 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(); } }
Looper還提供了一個getMainLooper()這個是用來便捷的獲取主線程的Looper的。Looper也是可以退出的,他提供了quit和quitSafely方法來退出Looper,quit是直接退出Looper,而quitSafely會先處理完畢消息隊列的消息再退出。
public void quit() { mQueue.quit(false); } public void quitSafely() { mQueue.quit(true); }
Looper退出后,通過Handler發送的消息會失敗,如果在子線程中創建Looper我們應該在不需要的時候終止Looper。Looper的loop方法工作流程:loop方法是一個死循環,唯一跳出循環的方式是MessageQueue的next方法返回null。當Looper執行quit方法時,會調用MessageQueue的quit或者quitSafely來通知消息隊列的退出,當隊列被標記退出狀態時,它的next會返回null.通過MessageQueue.next來獲取新消息,否則會一直阻塞,MessageQueue的next返回新消息,Looper就會處理這條消息調用msg.target.dispatchMessage()方法。其實這個target就是Message持有的Handler引用。所以最終是交給Handler調用dispatchMessage()來處理這個消息。因為dispatchMessage是在創建Handler時所使用Looper中執行的,這樣就把代碼邏輯切換到了指定線程中執行了。
Handler的工作原理:
其實Handler的工作就是消息的發送和接收,發送消息可以通過一系列的post方法和send方法,而post方法最終也是通過send來發送的。那么最終都是走下面這個方法。
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); }
Handler發送消息的過程就是僅僅是向消息隊列中插入了一條消息,MessageQueue的next方法返回了這個消息交給Looper,Looper接收到消息就開始處理了。最終的消息是交由Handler處理,即Handler的disaptchMessage會被調用,這時候Handler就進入了消息處理的過程,Handler處理消息的過程如
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
首先檢查Message的callBack是否為空,不為空就通過handleCallback來處理消息,Message的CallBack就是一個Runnable對象,實際上就是通過post方法傳遞的Runnable參數,其次是檢查mCallBack是否為null,不為空就調用mCallBack的handleMessage()方法,通過CallBack我們可以如下創建Handler。 Handler handler=new Handler(callback).這里不需要派生一個子類對象,在日常中我們就是創建Handler子類對象,並重寫handleMesaage方法。不過注意callback的handleMessage方法的返回值boolean會影響到Handler自己的handleMessage的調用,用到的時候要注意。
程序的入口分析
public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); 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")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
這是Android應用程序啟動的入口main方法。注意到這里面比較關鍵的幾行代碼
- Looper.prepareMainLooper();點進方法里可以看到在該方發中首先從ThreadLocal中獲取到looper對象,如果存在則拋出異常(只能創建一次),然后new Looper()創建一個Looper對象並與當前的線程綁定,具體的綁定方式請隨我看。首先獲取到當前所在的線程,然后通過該線程對象獲取到該線程的 ThreadLocalMap集合,然后以當前線程對象做為key,所創建的Looper對象作為value進行map結合的存儲。這樣Looper與線程之間通過ThreadLocal就進行綁定了起來。
sThreadLocal.set(new Looper(quitAllowed)); public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
- ActivityThread thread = new ActivityThread();這句話創建了ActivityThread對象, 大家如果看源碼的話可以看到該類定義了一個H mH的成員變量,沒錯這個mH就是系統的Handler,很特別,該變量在定義的時候就進行了初始化,這行代碼走完之后,mH就相應的有值了。
- Looper.loop();這個方法就比較牛了,可以說我們應用程序的運行就依賴於它,點進該方法可以看到,它里面是一個for死循環。不斷的進行消息的獲取處理,從而使我們程序一直運行下去。
補充說明消息機制中涉及的幾個要素
- Looper:Looper其實是充當着一個循環器的作用,它的內部持有MesageQueue、Thread、和ThreadLocal(其內部的map集合用於存儲Looper和線程,實現兩者的綁定)其中prepare()方法用於創建Looper對象並進行存儲(存儲方式前面說過),loope方法即是一個消息處理的循環器。,不斷的從MessageQueue中取出消息交由Handler處理。
- Message消息對象,它的內部有持有Handler和Runnable的引用以及消息類型。其中有一個比較重要的方法obtain(),取消息,該方法的內部是先通過消息池來獲取池中的消息,沒有則創建,實現了Message對象的復用。其內部有一個target引用,該引用是一個Handler對象的引用,在Looper中提到的消息處理就是通過Message持中的target引用來調用Handler的dispatchMessage()方法來實現消息的處理。還有這個Runnable引用。這個引用會在消息的處理中看到,在dispatchMessage()方法中會先判斷這個Message得callBack是否為空,如果不為空則走handleCallBack方法最終將走到Runnable的run方法
這個我們經常遇到,比如我們通過Handler.post().....等一系列post方法,該方法實現消息發送的原理就是將線程任務Runnable封裝到消息對象Message中,最終會走到這個Runnable的run方法中執行,所以這個過程中並沒有開啟一個線程,仍然是在主線程中運行的。因此不要有耗時的操作,否則會阻塞主線程。
- MessageQueue:消息隊列,其實不應該這樣稱呼,應為他的結構並不是一個隊列,而是一個鏈表(這樣說方便理解)它實現了對消息Message的存儲,以及消息在鏈表中的排列以取出消息的順序。
- Handler:Handler主要是扮演者消息發送和消息處理的角色,這里我將對他的一些方法進行介紹:
(1)構造方法:Handler有一系列的構造方法,這個自行查閱,他也可以有自己的回調處理CallBack,在消息處理dispatchMessage中我們可以看到有這樣一個判斷,如果mCallback!=null會執行它自己的handleMessage方法,該方法的返回值直接決定了下面handleMessage的執行與否。
(2)一系列的Post()方法(原理上面說過啦)其實最終都是將任務進行了消息的封裝插入到MessageQueue中,最終Runnable不為空,處理消息時會回調自己的run()方法。
(3)sendMessage()....等方法也是對消息就行了封裝最后插入到了消息對列中
(4)removeCallbacks(Runnabler)改方法是對通過post發送方式進行消息的清除,還有removeMessage(int what)通過消息類型進行移除,還removeAllbacksAndAMessages()將會移除所有的callbacks和messages
總結
在主程序的入口main方法中進行了主線程Looper的創建以及Handler的創建,以及將改Looper與主線程綁定。然后通過Looper.loop方法進行消息的循環,不斷的從消息隊列(在初始化Looper的構造函數中進行了MessageQueue的初始化)取出消息,然后交給Message所持有的Handler來處理,Handler通過調用dispatchMessage()方法來處理消息。從而形成了整個消息系統機制。注意:因為我們一般使用Handler都是在主線程中,不用考慮Looper的創建,因為剛才說了,啟動程序時候默認給我們創建了一個Looper對象,所在在這個環境下我們可以自由使用Handler,但是如果我們要在子線程中使用Handler就必須先通過Looper.prepare()方法創建一個Looper對象,然后創建handler對象然后通過Looper.loop()方法實Loop循環,不斷的處理消息。