前言
關(guān)于這方面的知識,網(wǎng)上已經(jīng)有很多。為啥這里還要寫呢?知其然,也要知其所以然,handler-message-looper的設(shè)計很巧妙,我們了解為啥要這么設(shè)計,對我們設(shè)計程序的時候也有裨益。這篇文章闡述怎么結(jié)合現(xiàn)象與原理分析事件驅(qū)動的本質(zhì),相信即使沒看過相關(guān)知識的同學(xué)也能快速理解。
通過這篇文章你將知道:
1、如何進行線程切換
2、消息循環(huán)原理
3、子線程消息循環(huán)
4、鏈表實現(xiàn)棧和隊列的
線程切換
- Q1 為什么需要線程切換
1、多線程能夠提高并發(fā)效率,多線程的引入勢必會有線程切換
2、多線程同時引入了線程安全問題,Android為了安全、簡單地繪制UI,規(guī)定繪制UI操作只能在一個固定的線程里操作,該線程在app啟動時創(chuàng)建,我們稱之為UI線程(也稱做主線城,后續(xù)簡單起見,統(tǒng)一叫做主線城)。非主線程我們稱之為子線程,當(dāng)我們在子線程里進行耗時操作,并準備好需要展示的數(shù)據(jù)時,這時候需要切換到主線程更新UI
線程安全問題請移步真正理解Java Volatile的妙用
- Q2 如何切換線程
先看如何創(chuàng)建并啟動一個線程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
doTask1();
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
doTask2();
}
});
t2.start();
如上所示,t2需要等待t1執(zhí)行完畢再執(zhí)行,那么t2怎么知道t1完畢呢?有兩種方式
1、t1主動告訴t2自己已執(zhí)行完畢,屬于等待通知方式(wait¬ify、condition await&signal)
2、t2不斷去檢測t1是否完畢,屬于輪詢方式
假設(shè)t1是子線程,t2是主線程,那么t1采用何種方式通知t2呢?采用第一種方式:等待-通知,通過Handler-Message-Looper實現(xiàn)等待-通知。
Handler發(fā)送Message
經(jīng)典例子
Button button;
TextView tv;
private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
tv.setText(msg.arg1 + "");
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.start_thread_one);
tv = findViewById(R.id.tv);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(()->{
Message message = Message.obtain();
message.arg1 = 2;
handler.sendMessage(message);
}).start();
}
});
}
主線程里構(gòu)造handler,子線程里構(gòu)造message,并使用handler將message發(fā)送出去,在主線程里執(zhí)行handleMessage方法就可以更新UI了。很明顯,使用handler來切換線程我們只需要兩步:
1、主線程里構(gòu)造handler,重寫回調(diào)方法
2、子線程里發(fā)送message告知主線程執(zhí)行回調(diào)方法
看到此,你可能疑惑的是:主線程怎么就知道執(zhí)行handleMessage方法?既然子線程發(fā)送了message,那么主線程應(yīng)該有個地方接收到該message啊?看來信使是message,接下來進入源碼一探究竟。Android Studio關(guān)聯(lián)源碼請移步:Android Studio關(guān)聯(lián)Android SDK源碼(Windows&Mac)
Message分析
既然message充當(dāng)信使,那么其應(yīng)當(dāng)有必要的字段來存儲攜帶的數(shù)據(jù)、區(qū)分收發(fā)雙方等,實際上message不只有這些字段。上個例子里,我們通過靜態(tài)方法獲取message實例。
private static Message sPool;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
sPool是靜態(tài)變量,從名字可以猜測出是message存儲池。obtain()作用是:先從存儲池里找到可用的message,如果沒有則創(chuàng)建新的message并返回,那什么時候?qū)essage放到存儲池里呢?我們只需要關(guān)注sPoolSize增長的地方即可,搜索找到:
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
message里有個message類型的next字段,熟悉鏈表的同學(xué)應(yīng)該知道這是鏈表的基本操作,該字段用于指向鏈表里下一個節(jié)點的。現(xiàn)在要將一個message對象放入message存儲池里:
將待存儲的message next字段指向存儲池的頭部,此時該頭部指向的是第一個元素,再將存儲池的頭指向待插入的message,最后將存儲池里個數(shù)+1。這時候message已經(jīng)放入存儲池里,并且當(dāng)作存儲池的頭。
我們再回過頭來看看怎么從存儲池里取出message:
聲明message引用,該引用指向存儲池頭部,將存儲池頭部指向鏈表下一個元素,并將message next置空,并將存儲池里個數(shù)-1,這時候message已經(jīng)從存儲池里取出。
可以看出,每次將message放入鏈表頭,取的時候從鏈表頭取,這不就是我們熟悉的棧結(jié)構(gòu)嗎。也就是說,message對象存儲池是通過棧來實現(xiàn)的。
問題1
至此,我們分析了message存儲池的結(jié)構(gòu),還留了兩個問題后面分析:
1、我們通過obtain()獲取message,那么啥時候調(diào)用recycleUnchecked()放入存儲池呢?
2、可以直接構(gòu)造message嗎?
MessageQueue
從handler.sendMessage(message)分析:
public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
sendMessageAtTime()方法輸入兩個參數(shù),一個是待發(fā)送的message對象,另一個是該message延遲執(zhí)行的時間。這里引入了一個新的類型:MessageQueue,其聲明了一個字段:mQueue。接著調(diào)用enqueueMessage,該方法里msg.target = this,target實際上指向了handler。最后調(diào)用queue.enqueueMessage(msg, uptimeMillis),我們來看看queue.enqueueMessage方法:
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);
}
}
MessageQueue顧名思義:Message隊列。它是怎樣實現(xiàn)隊列的呢?里有個Message類型的mMessages字段,看到這是不是想到了前面的message存儲池(棧)呢?沒錯,MessageQueue是通過message鏈接成鏈表的,只不過該鏈表是實現(xiàn)了隊列的,接下來我們看看怎么實現(xiàn)隊列。
enqueueMessage源碼里標記了第一點、第二點、第三點三個關(guān)鍵點。
第一點
隊列為空、或者延遲時間為0、或者延遲時間小于隊列頭節(jié)點,那么將該message next字段指向隊列頭,并將隊列頭指向該message。此時該message已經(jīng)插入enqueueMessage的隊列頭。
第二點
如果第一點不符合,那么message不能插入隊列頭,于是在隊列里繼續(xù)尋找,直至找到延遲時間大于message的節(jié)點或者到達隊列尾,再將message插入隊列。
從第一點和第二點可以看出,enqueueMessage實現(xiàn)了隊列,并且是根據(jù)message延遲時間從小到大排序的,也就是說隊列頭的延遲時間是最小的,最先需要被執(zhí)行的。
第三點
問題2
喚醒MessageQueue隊列,后續(xù)分析。
什么時候取出message
至此,handler.sendMessage(message)發(fā)送message到MessageQueue流程已經(jīng)分析完畢,那么MessageQueue里的message什么時候取出來呢?
既然MessageQueue里有入隊方法enqueueMessage,試著找找是否存在出隊方法,很遺憾沒有。那么我們有理由相信可能在某個地方操作了MessageQueue mMessages字段,查找后發(fā)現(xiàn)引用的地方蠻多的,不好分析。換個思路,想想MessageQueue對象mQueue在哪里被引用了?
定位到Handler構(gòu)造函數(shù)
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
我們發(fā)現(xiàn)mQueue指向了mLooper里的mQueue,而mLooper = Looper.myLooper()。現(xiàn)在我們將懷疑的目光瞥向了Looper,Looper是何方神圣呢,接下來繼續(xù)分析。
Looper分析
從Looper.myLooper()開始分析
##Looper.java
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
##ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
可以看出,Looper對象存儲在當(dāng)前線程里,我們暫時不管ThreadLocal原理,看到get方法,我們有理由相信有set方法,果不其然。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
查找其引用,該方法引用的地方很多。既然ThreadLocal get方法在Looper.java里調(diào)用,那么ThreadLocal set方法是否也在Looper.java里調(diào)用呢?
Looper.java
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));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
查找prepareMainLooper()方法引用
看到了我們熟悉的ActivityThread,也就是我們app啟動main方法所在的類。
已經(jīng)追溯到源頭了,我們現(xiàn)在從源頭再回溯。
private static Looper sMainLooper; // guarded by Looper.class
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();
}
在prepare方法里,構(gòu)造了Looper對象。此刻我們看到了主角mQueue被構(gòu)造出來了,也就是說主線程啟動的時候就已經(jīng)構(gòu)造了Looper對象,并存儲在靜態(tài)變量sThreadLocal里。同時靜態(tài)變量sMainLooper指向了新構(gòu)建的Looper對象。
接著查找mQueue的引用,發(fā)現(xiàn)
final MessageQueue queue = me.mQueue,此處引用了mQueue,接著調(diào)用了MessageQueue里的next()方法,該方法返回message對象。
MessageQueue.java
next()方法
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;
}
Message msg = mMessages,我們之前分析了handler發(fā)送message后最終是將message掛在mMessages上,也就是我們的message隊列。而next方法后續(xù)操作是:從隊列頭取出message,并將該message從隊列里移除。此時我們已經(jīng)找到message隊列入隊時機與方式、message隊列出隊方式。那么message隊列出隊時機呢?也就是啥時候調(diào)用的。繼續(xù)回溯分析上面提到的Looper loop()方法。
loop()方法簡化一下
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);
}
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked();
}
明顯的看出,loop()方法里有個死循環(huán),循環(huán)退出的時機是隊列里沒有消息或者其它異常。
msg.target.dispatchMessage(msg);msg.target就是我們之前提到的handler,也就是handler.dispatchMessage(msg)
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);(1)
} else {
if (mCallback != null) {
(2)
if (mCallback.handleMessage(msg)) {
return;
}
}
(3)
handleMessage(msg);
}
}
這方法里就是具體執(zhí)行收到message的方法,我們發(fā)現(xiàn)里邊還分了三個方法:
1、handleCallback()調(diào)用的是message里Runnable類型的callback字段,我們經(jīng)常用的方法view.post(runnable)來獲取view的寬高,此處runnable會賦值給callback字段。
2、handler mCallback字段,Callback是接口,接口里有handleMessage()方法,該方法的返回值決定是否繼續(xù)分發(fā)此message。mCallback可選擇是否實現(xiàn),目的是如果不想重寫handler handleMessage()方法,那么可以重寫callback方法。
3、handleMessage,構(gòu)造handler子類的時候需要重寫該方法。
可能上述1、2、3點應(yīng)用場合還造成一些疑惑,我們用例子來說明。
1、handleCallback()
//延時執(zhí)行某任務(wù)
handler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 100);
//view 獲取寬高信息
tv.post(new Runnable() {
@Override
public void run() {
}
});
此種方式,只是為了在某個時刻執(zhí)行回調(diào)方法,message本身無需攜帶附加信息
2、mCallback.handleMessage(msg)
構(gòu)造Handler匿名內(nèi)部類實例
private Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
tv.setText(msg.arg1 + "");
return false;
}
});
3、handleMessage(msg)
內(nèi)部類繼承Handler,重寫handleMessage方法
class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
tv.setText(msg.arg1 + "");
}
}
MyHandler handler = new MyHandler();
此時,我們已經(jīng)知道MessageQueue里的message執(zhí)行時機。你可能還有疑惑,怎么就確保上述的runnable和handleMessage在主線程里執(zhí)行呢?根據(jù)調(diào)用棧:
loop()->msg.target.dispatch()->runnable/handleMessage(),而loop是在main方法里調(diào)用的,該方法是app入口,是主線程。
我們還注意到loop()里有兩條打印語句:
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);
}
可以看到位于dispatchMessage()前后,這給我們提供了一個便利:通過監(jiān)測語句的輸出,計算主線程執(zhí)行message耗時。
loop()里的msg.recycleUnchecked(),現(xiàn)在來回答問題1
1、message通過obtain()從存儲池里獲取實例,在loop()里message被使用后,調(diào)用msg.recycleUnchecked()放入存儲池里(棧)。
2、message對象不僅可以通過obtain()獲取,也可以直接構(gòu)造。最終都會放入存儲池里,推薦用obtain(),更好地復(fù)用message。
MessageQueue next()分析
前面我們簡單過了一遍next()方法,該方法是從MessageQueue里取出message對象。loop()通過next調(diào)用獲取message,如果message為空則loop()退出,假設(shè)這種情況出現(xiàn),那么主線程就退出了,app就沒法玩了。因此從設(shè)計上來說,next返回的message不能為空,那么它需要一直檢測message直到獲取到有效的message,所以next就有可能阻塞。實際上,next使用的一個for循環(huán),確實可能會阻塞,我們來看看細節(jié)之處:
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;
}
}
}
源碼里標記了6個關(guān)鍵點,一一分析一下。
1、nativePollOnce(ptr, nextPollTimeoutMillis),查找native層MessageQueue是否有數(shù)據(jù)。nextPollTimeoutMillis=0 該方法立即返回;nextPollTimeoutMillis=-1;該方法一直阻塞直到被主動喚醒;nextPollTimeoutMillis>0;表示阻塞到特定時間后返回。現(xiàn)在我們來解答之前的問題2,當(dāng)有message入隊列的時候,調(diào)用如下代碼:
if (needWake) {
nativeWake(mPtr);
}
而needWake=mBlocked,當(dāng)阻塞的時候mBlocked=true,執(zhí)行nativeWake(mPtr)后,nativePollOnce()被喚醒返回。這就是最初我們?yōu)槭裁凑fhandler-looper 采用等待通知方式。
2、如果隊頭節(jié)點是屏障消息,那么遍歷隊列直到找到一條異步消息,否則繼續(xù)循環(huán)直到nativePollOnce阻塞。此處的應(yīng)用場景之一是:在對view進行invalidate時候,為了保證UI繪制能夠及時,先插入一條屏障消息,屏障消息delayTime=0,因此插入隊列頭部(注意和普通消息插入不同的是:如果隊列某個節(jié)點和待插入的延遲時間一致,那么待插入的節(jié)點排在已有節(jié)點的后面,而屏障消息始終插在前面)。插入屏障消息后,后續(xù)的刷新消息設(shè)置為異步,再插入隊列,那么該條消息會被優(yōu)先執(zhí)行。
3、如果還未到執(zhí)行時間,則設(shè)置阻塞超時時間。
4、取出message節(jié)點,并調(diào)整隊列頭。
5、隊列里沒有message,那么設(shè)置阻塞
6、設(shè)置阻塞標記位mBlocked=true,并繼續(xù)循環(huán)
Handler線程切換本質(zhì)
上面經(jīng)handler發(fā)送message,到message被消費整個流程已經(jīng)剖析完畢。為了更直觀了解流程,使用流程圖表示。
message對象存儲池
入棧
出棧
MessageQueue隊列
出入隊
調(diào)用棧
發(fā)送消息調(diào)用棧
handler.sendMessage(message)
sendMessage->sendMessageDelayed(msg, 0)->sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)->enqueueMessage(queue, msg, uptimeMillis)->queue.enqueueMessage(msg, uptimeMillis)
取消息調(diào)用棧
msg = loop()->queue.next()
msg->dispatchMessage(msg)->handleCallback(msg)/handleMessage(msg)
將Handler、MessageQueue、Looper用圖表示如下:
由于MessageQueue是線程間共享對象,因此對隊列的操作(入隊、出隊)需要加鎖。
Handler和Looper聯(lián)系
怎么確保handleMessage(msg)在主線程執(zhí)行呢?換句話說收到消息后是怎么切換到主線程的?
1、從取消息調(diào)用棧可知,執(zhí)行l(wèi)oop()的線程就是執(zhí)行handleMessage(msg)的線程,而loop()在main()方法里執(zhí)行,因此這時候取出的消息就在主線程執(zhí)行
2、Handler發(fā)送的message存儲在Handler里的messagequeue,而此隊列指向的是Looper的messagequeue,loop()方法從Looper的messagequeue里取消息。因此Handler和Looper共享同一個queue,handler負責(zé)發(fā)送(入隊),Looper負責(zé)收取(出隊),通過中間橋梁messagequeue實現(xiàn)了線程切換。
3、接著第二點,Handler和Looper是如何共享queue呢?在構(gòu)建Handler的時候,會根據(jù)當(dāng)前線程獲取存儲的looper并獲取其messagequeue,而looper對象需要在同一線程里構(gòu)造,并將該looper引用設(shè)置到當(dāng)前線程。在app啟動的時候,系統(tǒng)已經(jīng)在主線程的looper并賦值給靜態(tài)變量sMainLooper,我們可以通過getMainLooper()獲取主線程的looper對象。
4、handler構(gòu)造時,可以選擇是否傳入looper對象,若不傳則默認從當(dāng)前線程獲取,若取不到則會拋出異常。這就是為什么在子線程里不能簡單用消息循環(huán)的原因,因為handler沒有和looper建立起關(guān)聯(lián)。
子線程消息循環(huán)
以上,我們分析的是子線程發(fā)送消息,主線程在輪詢執(zhí)行。那我們能夠在主線程(其它線程)發(fā)送消息,子線程輪詢執(zhí)行嗎?這種場景是有現(xiàn)實需求的:
A線程不斷從網(wǎng)絡(luò)拉取數(shù)據(jù),而數(shù)據(jù)分為好幾種類型,拿到數(shù)據(jù)后需要分類處理,處理可能比較耗時。為了不影響A線程效率,需要另起B(yǎng)線程處理,B線程一直在循環(huán)處理A拉取回來的數(shù)據(jù)。
是不是似曾相識,我們想要B線程達到類似主線程的looper功能,接下來看看該怎么做。
Handler handlerB = null;
Thread b = new Thread(new Runnable() {
@Override
public void run() {
//子線程里創(chuàng)建looper對象,并保存引用到當(dāng)前線程里
Looper.prepare();
//Handler構(gòu)造方法默認獲取當(dāng)前線程的looper引用
handlerB = new Handler(new Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
//子線程開啟loop()循環(huán)
Looper.loop();
}
});
b.start();
Thread a = new Thread(()->{
//線程發(fā)送消息
handlerB.sendMessage(new Message());
});
a.start();
源碼里已有注釋,不再綴敘。
你可能會說,每次都這么麻煩嗎?實際上Android系統(tǒng)開發(fā)者已經(jīng)考慮了這情況,封裝了HandlerThread類來實現(xiàn)子線程的消息循環(huán),實現(xiàn)方式和上述類似。重點是將Handler和Looper關(guān)聯(lián)起來。
總結(jié)
以上從源碼和應(yīng)用角度剖析了Handler、Message、Looper原理及其三者之間關(guān)系,三者簡稱消息循環(huán)。消息循環(huán)在Android開發(fā)中應(yīng)用廣泛,我們不僅要能夠運用自如,也可以從源碼中學(xué)習(xí)閃光的設(shè)計點。