1.前言
本篇所要講的是Android消息機制,在開發和面試中經常會涉及的內容。有些同學可能知道消息機制是創建Handler、發送消息、更新UI一系列的操作,但并沒有了解到底是為什么要這樣做或者內部到底如何實現。
本篇文章,將由基礎深入源碼,為大家全面解析Android的消息機制。俗話說得好“知己知彼百戰百勝”,接下來咱們深入淺出的交流一下!
2.概念
本節都是基礎,我化身十萬個為什么提出以下幾個問題!如果讀者都明了那就直接跳向下一節!
Android的消息機制是什么?
Android的消息機制主要是指Handler的運行機制,主要作用是將當前線程任務切換到指定線程中去執行。-
為什么會有消息機制?
因為Android在內部規定訪問UI必須在主線程中去執行,如果在子線程中執行UI會拋出異常。有的同學會說:“那我們全部在主線程中操作不就好啦?”。這樣也是不行的,如若該UI有訪問網絡等耗時操作在主線程中,會阻塞程序,并且導致ANR異常。這樣我們只能在子線程中執行耗時操作,在主線程中更新UI,所以這就是Android提供消息機制的原因。
總結一下,主要有兩個原因:- 子線程訪問UI會拋出異常
- 主線程中執行耗時操作會發生ANR異常
-
消息機制的原理是什么?
說到原理,就不得不說3+1兄弟!
Handler、MessageQueue、Looper + ThreadLocal
3+1兄弟是消息機制的核心,只要掌握了這幾兄弟,掌握消息機制就不在話下了!- Handler:它的工作主要包含消息的發送和接收過程。
- MessageQueue:它是消息隊列,是用來存儲消息Handler發來的消息。它的內部并不是真正的隊列,而是采用單鏈表來存儲消息。
- Looper:MessageQueue只能存儲消息,但并不能發送消息,這點就由Looper來解決。Looper內部是一個無限循環,從MessageQueue查找是否有新消息,如果有就將消息傳遞給目標線程的Handler,如果沒有就一直等待。
- ThreadLocal:為啥把ThreadLocal說成+1兄弟呢?因為這小伙子起到一個輔助作用。Handler創建的時候都會使用當前線程的Looper來構建循環系統,那么怎么樣在Handler內部找到這個線程的Looper呢?那就用到了ThreadLocal,ThreadLocal可以在不同線程中互不干擾的存儲并提供數據,那找到不同線程Looper就很簡單啦。
-
消息機制的流程?
說完了3+1兄弟,是時候把他們合起來了。以訪問網絡獲取數據并展示到TextView為例:- 創建Handler:在Activity的主線程中創建Handler,主線程自帶Looper,所以不需要創建。
- 發送消息到MessageQueue:創建子線程獲取登錄信息,成功后,使用Handler的send或者post方法發送結果到MessageQueue的enqueueMessage方法將這個消息放入消息隊列中。
- Looper處理消息:此時Looper發現了這個消息,然后將它轉發給目標Handler。此時已經到了主線程中,將數據擺放在TextView上。
消息機制的原理到這已經差不多有了頭緒。具體的深入分析咱們下一章跟著源碼一起來!
3.原理分析
上一節對Android的消息機制做了一個概括的描述,本節會對Android消息機制的源碼和實現原理做一個全面的分析,主要包括Handler、MessageQueue、Looper和ThreadLocal。通過本節的學習,同學們會對消息機制有一個深入的理解。
3.1 ThreadLocal
咱們先來介紹ThreadLocal,會對Looper有一個更好的理解。
ThreadLocal是一個數據儲存類,并保證在每個線程的數據互相獨立,可以在指定的線程儲存數據,并且只有在指定的線程能夠獲取數據,對于其他線程無法獲取到。
ThreadLoacl在什么情況下使用呢?
平時在開發的時候使用的并不多,以下兩種情況可以考慮使用:
1.當某些數據是以線程為作用域并且不同線程具有不同的數據的時候
2.如果某些數據需要貫穿線程的執行過程,就可以考慮使用ThreadLoaclThreadLocal為什么會有數據獨立效果呢?
因為ThreadLocal的內部有set()和get()方法,并且set()和get()方法操作的數據源都保存在各個Thread(線程)中,互相獨立,互不影響。
那這樣接下來咱們就來分析一下源碼看看我所說的到底對不對!
首先來看ThreadLoacl的set()方法。
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
這個方法非常的簡單,根據當前線程獲取當前的數據,但是有幾點疑問
-
傳入參數的T是什么?
ThreadLocal是一個泛型類,它的定義是public class ThreadLocal<T>,所以T是泛型,是咱們想要保存的類 。 -
Values是什么類?
Values是ThreadLocal的內部類,用來在Thread中存儲ThreadLocal的數據。在Thread中有一個成員變量ThreadLocal.Values localValues。 -
values(currentThread)方法做了什么?
將當前線程的Values取出。
Values values(Thread current) {
return current.localValues;
}
-
initializeValues(currentThread)方法做了什么?
給當前線程的localValues,賦值一個初始值。
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
解答完以上這幾點疑問,set()的源碼就可以理解的非常清晰了。
先獲取當前線程的Values數據,如果數據為null,則給當前線程的Values賦一個初值,然后將要傳遞的數據傳入這個Values。
那又有一個問題來了,Values是究竟如何保存這些數據的呢?
下面是Values中put的源碼。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
從源碼中可以看到一個數組table[]出現頻率非常的高,可能大家也明白了,沒錯Values中就是用數組來保存數據的!在代碼中的定義是這樣的private Object[] table。
這個邏輯比較簡單,咱們不再去分析邏輯,但是可以看出其中數據存儲的規則。根據table[index] = key.reference; table[index + 1] = value,可以發現要保存的數據總是保存在ThreadLocal的reference字段的下一個位置。比如ThreadLocal的reference作為key保存在index位置,那咱們保存的數據就保存在index+1的位置。
上面詳解了set()方法,那咱們繼續來看get()方法,如下所示。
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
get()的方法就比較簡單了,獲取當前線程的Values,如果Values是空的就創建一個新的Values。如果不是空的就從中取出table[],獲取key的index并進行對比,如果table[index]是該ThreadLocal的reference,那么table[index+1]就是我們所保存的數據。
從ThreadLocal的 set 和 get 和方法可以看出,操作的數據就是ThreadLocal里面Values的table[]數組,并且每個線程都有獨立的Values,互不干擾。這樣就算不同線程使用同一個ThreadLocal的set和get方法,也不會互相干擾,所做的都是在各自線程中獨立的。
3.2 MessageQueue
MessageQueue是接收Handler數據保存并傳遞的地方。之前介紹它叫消息隊列,然而它的內部并不是一個隊列,實際上是通過一個單鏈表的數據結構來保存刪除數據的,因為單鏈表在插入和刪除上比較快。
MessageQueue主要有兩個操作:插入和讀取。插入使用enqueueMessage方法來向消息隊列中插入消息,讀取使用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("MessageQueue", 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() {
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 (false) Log.v("MessageQueue", "Returning message: " + msg);
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("MessageQueue", "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;
}
}
可以發現next方法是一個無限循環,如果隊列中沒有消息的時候就會堵塞,當隊列中有消息就把這條消息返回并從隊列中刪除。
3.3 Looper
Looper是消息循環器,它的內部是一個無限循環,從MessageQueue中不停查找是否有新消息,如果沒有就堵塞,如果有就立刻處理。
3.3.1 Looper的創建
線程如果想要使用Handler那必須有Looper,否則會報錯,那么如何為一個線程創建Looper呢?創建過程如下所示。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}).start();
Looper.prepare()的功能是創建Looper,具體原理,直接看一下源碼。
public static void prepare() {
prepare(true);
}
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
代碼中Looper.prepare()直接調用了prepare(boolean quitAllowed)方法,prepare(boolean quitAllowed)中有之前所說的ThreadLocal,每個線程只保存自己的Looper。
- 如果當前線程中已經創建過Looper,重復創建會拋出異常,所以在同一個線程中只能調用一次Looper.prepare()。
- 如果沒有創建過Looper,就向ThreadLocal中保存一份新的Looper。
- Looper的構造方法中創建了MessageQueue,也保存了當前的線程的對象。
以上是Looper的初始化過程,創建Looper時同時創建了MessageQueue,保存了當前線程的對象,并將其放到ThreadLocal中保證每個線程中都使用自己的Looper。
3.3.2 Looper的循環
在上面的創建過程中,并沒有看到Looper的工作機制,那Looper的工作在哪里開始進行呢?
創建過程當中,Handler創建之后,有一行代碼Looper.loop(),字面翻譯就是消息循環器開始循環吧!所以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();
}
}
上面代碼是不是看起來也很清晰?
myLooper()方法會從ThreadLocal中獲取當前Thread的Looper,如果沒有就會拋出異常,所以在執行loop方法之前一定要先執行prepare方法進行初始化。
再往下看loop()方法內部有一個死循環,死循環中會不停的執行MessageQueue 的 next 方法獲取消息,唯一能跳出死循環的方式是 next 方法返回了 null 。
Looper 的 loop 方法是一個死循環,等待next方法返回消息,而MessageQueue 的 next 方法也是一個死循環,當沒有消息的時候處于堵塞狀態,所以 next 方法的阻塞導致了 loop 方法的阻塞。
當有消息傳遞過來,Looper 的 loop 方法執行msg.target.dispatchMessage(msg) 來處理這條消息,msg.target 是發送這條消息的 Handler 對象,會將消息傳遞給該 Handler 的 dispatchMessage 方法。這個傳遞會從發送消息的線程的 Looper 傳遞給創建 Handler 的線程的 Looper,這樣就完成了線程的切換。
3.3.3 Looper的退出
Looper雖然是死循環但也是可以退出的,上面說到唯一退出循環的方式就是 MessageQueue 的 next 方法返回了 null。
那 Looper 退出的意義是啥?舉個栗子,如果一個線程已經結束任務了,然而 Looper 依然在不停地循環,那這個 Thread 就無法釋放處于等待狀態,只有 Looper 退出,這個線程才會終止。推薦在線程執行完畢的時候結束Looper。
Looper 提供了一個退出方法叫 quit(boolean safe),會讓 Looper 退出循環,傳入參數若為 true,會等所有的消息執行完畢再退出,若為false,則直接退出。
3.4 Handler
Handler的工作主要包括發送和接收消息,本章主要介紹handler的發送接收和創建。
3.4.1 Handler的創建
Handler的創建一定要有Looper,咱們看源碼來揭示原因。
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;
}
咱們看到了熟悉的代碼,Looper.myLooper(),用來獲取當前線程的Looper,如果當前線程沒有Looper則拋出異常。
有的同學會問了,為什么主線程創建Handler不需要創建Looper呢?因為主線程在創建的時候已經在內部創建了Looper,咱們來看一下ActivityThread的main方法。
public static void main(String[] args) {
......
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper內部提供了 prepareMainLooper 方法來創建主線程的 Looper,然后在其后執行了 Looper.loop()。具體 ActivityThread 何時執行這里就不講了,大家有興趣可以去學習下。
3.4.2 Handler發送消息
Handler 的發送有 post 和 send 兩類方法,所有的 post 方法都會執行到 send 方法中,所以咱們直接來看send的代碼,以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) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
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);
}
最后消息被傳遞到 enqueueMessage 方法中,將 Message 的 target 設置為當前 Handler,然后將 Message 傳遞給 MessageQueue 的 enqueueMessage當中,插入到消息隊列里面。這就是發送的全部邏輯。
3.4.3 Handler接收消息
當 Looper 將消息傳遞給 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);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
當 Handler 初始化的時候如果 msg.callback 不為空的時候就執行 handleCallback 方法,callback 是一個 Runnable ,直接執行run方法。
如果 msg.callback 為 null,那再判斷是否有 mCallBack,它是一個Callback的對象,它的賦值是在 Handler 初始化的時候,Handler handler = new Handler(callback) 。如果 mCallback 不為空,就調用 mCallback 的 handleMessage 方法。
那 Callback 存在的意義是什么?可以用來創建一個 Handler 的實例并且不需要創建新的子類。平時我們在做的時候一定會創建一個新的子類來接受數據。這給了我們創建 Handler 一種新的方式。
如果 mCallback 為 null,那就執行Handler的 handleMessage 方法。
到這里所有的 Handler、Looper、MessageQueue、ThreadLocal 都分析完了。
4.總結
咱們再來把消息機制的流程順一遍!
- 在線程中創建 Looper ,主線程除外,Looper.prepare(); Looper.loop()。在Looper的內部,初始化 Looper 的同時初始化MessageQueue。
- 使用 Handler 的 send 或者 post 方法發送消息,調用 MessageQueue 的 enqueueMessage 方法插入到消息隊列中。
- Looper 無限循環調用 MessageQueue 的 next 方法獲取消息,如果獲取到就傳遞給 Handler 的 dispatchMessage 方法,如果沒有消息就堵塞住。
這樣消息機制的流程就走完一遍了,希望大家讀完這篇文章,會對消息機制有一個更深入的了解。如果我的文章能給大家帶來一點點的福利,那在下就足夠開心了。
下次再見!