重溫Android中的消息機制


引入:

      提到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循環,不斷的處理消息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM