前言
在Android中砰逻,經(jīng)常會遇到線程間通信的場景九府,下面就說說Android中最重要的異步消息機制Handler
異步消息機制Handler
Handler是Android中最重要的異步消息機制证膨,總共由四部分組成:Handler,Message,MessageQueue,Looper
1民逼、主線程創(chuàng)建 Handler 對象(如果在子線程創(chuàng)建,必須保證調(diào)用了Looper.prepare())涮帘,并重寫 handleMessage() 方法拼苍。
2、子線程創(chuàng)建 Message 對象调缨,通過第一步創(chuàng)建的Handler 發(fā)送消息疮鲫,handler.sendMessage(message),handler將消息發(fā)送到MessageQueue中苟鸯。
3、Looper 通過loop()循環(huán)從 MessageQueue 中取出待處理消息棚点。
4早处、looper將取出的message分發(fā)回 Handler 的 handleMessage() 方法中處理。
Looper
創(chuàng)建 Looper 的方法是調(diào)用 Looper.prepare() 方法瘫析。注意:在 ActivtyThread 中的 main 方法為我們 prepare 了
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
//...
Looper.prepareMainLooper(); //初始化Looper以及MessageQueue
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(); //開始輪循操作
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.prepareMainLopper()
public static void prepareMainLooper() {
prepare(false);//調(diào)用prepare(), 消息隊列不可以quit
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been
prepared.");
}
sMainLooper = myLooper();
}
}
Looper.prepare(boolean quitAllowed)
public static void prepare() {
prepare(true);//消息隊列可以quit
}
// 私有的構(gòu)造函數(shù)
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//不為空表示當(dāng)前線程已經(jīng)創(chuàng)建了Looper
throw new RuntimeException("Only one Looper may be created per thread");
//每個線程只能創(chuàng)建一個Looper
}
// ThreadLocal 多線程中重要的知識點砌梆,線程上下文的存儲變量
sThreadLocal.set(new Looper(quitAllowed));//創(chuàng)建Looper并設(shè)置給sThreadLocal,這樣get的時候就不會為null了
}
注意:一個線程只要一個Looper贬循。如何保證只有一個咸包?
這里用到了ThreadLocal。一句話理解ThreadLocal杖虾,threadlocl是作為當(dāng)前線程中屬性ThreadLocalMap集合中的某一個Entry的key值Entry(threadlocl,value)烂瘫,雖然不同的線程之間threadlocal這個key值是一樣,但是不同的線程所擁有的ThreadLocalMap是獨一無二的奇适,也就是不同的線程間同一個ThreadLocal(key)對應(yīng)存儲的值(value)不一樣坟比,從而到達了線程間變量隔離的目的,但是在同一個線程中這個value變量地址是一樣的嚷往。所以利用ThreadLocal可以保證一個線程只有一個Looper葛账。
為了保證 ThreadLocalMap.set(value) 時,value 不會被覆蓋(即Looper不會改變)皮仁,會先進行上面代碼的 if 操作籍琳,if (sThreadLocal.get() != null) , 不為空表示當(dāng)前線程已經(jīng)創(chuàng)建了Looper,然后直接拋異常結(jié)束prepare贷祈。如果 if 不成立趋急,則 new Looper()。
MessageQueue
MessageQueue的創(chuàng)建是在Looper構(gòu)造函數(shù)中
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//創(chuàng)建了MessageQueue
mThread = Thread.currentThread(); //當(dāng)前線程的綁定
}
MessageQueue(boolean quitAllowed) {
//mQuitAllowed決定隊列是否可以銷毀 主線程的隊列不可以被銷毀需要傳入false, 在MessageQueue的 quit()方法
//省略...
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Looper.loop()
在 ActivtyThread的main 方法中 Looper.prepareMainLooper() 后 Looper.loop() 開始輪詢
public static void loop() {
final Looper me = myLooper();//里面調(diào)用了sThreadLocal.get()獲得剛才創(chuàng)建的Looper對象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}//如果Looper為空則會拋出異常
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 (;;) {
//這是一個死循環(huán)势誊,從消息隊列不斷的取消息
Message msg = queue.next(); // might block
if (msg == null) {
//由于剛創(chuàng)建MessageQueue就開始輪詢呜达,隊列里是沒有消息的,等到Handler sendMessage enqueueMessage后
//隊列里才有消息
// 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);//msg.target就是綁定的Handler,詳見后面Message的部分键科,Handler開始
//后面代碼省略.....
msg.recycleUnchecked();
}
}
loop方法開啟后闻丑,不斷地從MessageQueue中獲取Message,對 Message 進行 Delivery 和 Dispatch勋颖,最終發(fā)給對應(yīng)的 Handler 去處理(通過msg.target找到對應(yīng)的handler)嗦嗡。
說明:MessageQueue隊列中是 Message,在沒有 Message 的時候饭玲,MessageQueue借助Linux的ePoll機制侥祭,阻塞休眠等待,直到有Message進入隊列將其喚醒。
Handler
handler通過發(fā)送和處理Message和Runnable對象來關(guān)聯(lián)相對應(yīng)線程的MessageQueue矮冬。
最常見的創(chuàng)建 handler 的方式:
//第一種方式
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
//第二種方式(looper作為參數(shù)傳入)
Handler handler=new Handler(Looper looper)
第一種方式在內(nèi)部調(diào)用 this(null, false);
public Handler(Callback callback, boolean async) {
//前面省略
mLooper = Looper.myLooper();//獲取Looper谈宛,**注意不是創(chuàng)建Looper**!
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//創(chuàng)建消息隊列MessageQueue
mCallback = callback; //初始化了回調(diào)接口
mAsynchronous = async;
}
Looper.myLooper() 胎署;
//這是Handler中定義的ThreadLocal ThreadLocal主要解多線程并發(fā)的問題
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal.get() will return null unless you’ve called prepare(). 這句話告訴我們 get 可能返回 null 除非先調(diào)用 prepare()方法創(chuàng)建 Looper 吆录。在前面已經(jīng)介紹了
Message
message的創(chuàng)建可以直接 new Message() ,但是有更好的方式 Message.obtain()琼牧。因為可以檢測是否有可以復(fù)用的 Message恢筝,復(fù)用避免過多的創(chuàng)建、銷毀 Message 對象巨坊,達到優(yōu)化內(nèi)存和性能的目的撬槽。
public static Message obtain(Handler h) {
Message m = obtain();//調(diào)用重載的obtain方法
m.target = h;//并綁定的創(chuàng)建Message對象的handler
return m;
}
public static Message obtain() {
synchronized (sPoolSync) {//sPoolSync是一個Object對象,用來同步保證線程安全
if (sPool != null) {
//sPool是就是handler dispatchMessage 后 通過recycleUnchecked回收用以復(fù)用的Message
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
創(chuàng)建 Message 的時候通過 Message.obtain(Handler h) 這個構(gòu)造方法將message和handler綁定趾撵。當(dāng)然也可以在 Handler 中的 enqueueMessage() 綁定侄柔。
Handler 發(fā)送消息(消息入隊)
Handler 發(fā)送消息的重載方法很多,我們用得比較多的方法就是post占调、sendMessage暂题、sendMessageDelay方法,先挨個看看
post方法
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
post接受一個Runnable類型的參數(shù)妈候,并將其封裝為一個Message對象敢靡,并且將Runnable參數(shù)賦值給msg的callback字段,這里要記住苦银,后面有用——Runnable什么時候執(zhí)行的呢?
最后調(diào)用的就是sendMessageDelayed
sendMessage方法
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
最后調(diào)用的也是sendMessageDelay赶站,第二個參數(shù)是0幔虏。
sendMessageDelay方法
可見無論是post還是sendMessage方法,最后都走到了sendMessageDelayed
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 這里注意第二個參數(shù) @param updateMillis 是一個具體的時間點贝椿。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
//……
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
// 【關(guān)鍵點6】這里要注意target指向了當(dāng)前Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 【關(guān)鍵點7】調(diào)用到了queue#enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis);
}
最終會走到handler中的enqueueMessage方法想括,然后走到quene.enqueneMessage(msg, uptimeMillis),將message放入了消息隊列烙博,那么我們來看看瑟蜈,是如何放入隊列的吧。
boolean enqueueMessage(Message msg, long when) {
//……
// 【關(guān)鍵點8】對queue對象上鎖
synchronized (this) {
//……
msg.markInUse();
msg.when = when; // msg的when時刻賦值
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 翻譯:新的頭結(jié)點渣窜,如果queue阻塞铺根,則wakeup喚醒
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 (;;) { // for循環(huán)位迂,break結(jié)束時when < p.when,說明按照when進行排序插入,或者尾節(jié)點
prev = p;
p = p.next;
if (p == null || when < p.when) {
break; // 【關(guān)鍵點9】 找到插入位置掂林,條件尾部或者when從小到大的位置
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 執(zhí)行插入
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr); // native方法臣缀,喚醒
}
}
return true;
}
這里面將msg放到消息隊列中,可以看到這個隊列是一個簡單的單鏈表結(jié)構(gòu)泻帮,按照msg的when進行的排序精置,并且進行了synchronized加鎖,確保添加數(shù)據(jù)的線程安全锣杂。之所以采用鏈表的數(shù)據(jù)結(jié)構(gòu)脂倦,原因是鏈表方便插入。
初看源碼的時候蹲堂,應(yīng)該忽略掉wakeup這些處理狼讨,關(guān)注msg是如何加入隊列即可。
到這里柒竞,我們了解了message是如何加入消息隊列MesssageQueue政供。但是消息什么時候執(zhí)行,以及post(Runnable)中的Runnable什么時候才執(zhí)行朽基。
消息出隊執(zhí)行
前面提到的Looper.loop()中布隔,主要調(diào)用me.mQueue.next()獲取一個消息msg,注意這里可能阻塞稼虎。這里調(diào)用了msg.target.dispatchMessage(msg)衅檀,這里msg.target可以回頭看看前面Message創(chuàng)建賦值的地方。所以這里就將消息分發(fā)給了對應(yīng)的Handler去處理了霎俩。待會兒再看Handler.dispatchMessage哀军,我們接著看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 nextPollTimeoutMillis = 0;
// 【關(guān)鍵點12】繼續(xù)死循環(huán)
for (;;) {
//……
nativePollOnce(ptr, nextPollTimeoutMillis);
// 加鎖
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(); // 當(dāng)前時間
Message prevMsg = null;
Message msg = mMessages; // msg 指向鏈表頭結(jié)點
if (msg != null && msg.target == null) { // 這個if可以先忽略
// 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) {
// 【關(guān)鍵點13】如果當(dāng)前時間小于頭結(jié)點的when打却,更新nextPollTimeoutMillis杉适,并在對應(yīng)時間就緒后poll通知
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; // 更新頭結(jié)點
}
msg.next = null; // 斷鏈處理,等待返回
//……
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;
}
//……
}
//……
// 下面是IdleHandler的處理柳击,還不是很了解
}
}
next方法中也是一個死循環(huán)猿推,不斷的嘗試獲取當(dāng)前消息隊列已經(jīng)到時間的消息,如果沒有滿足的消息捌肴,就會一直循環(huán)蹬叭,這就是為什么會next會阻塞的原因。
看完了next方法状知,獲取到了msg秽五,回到剛才的msg.target.dispatchMessage(msg),接著看Handler是如何處理消息的试幽。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
// 如果消息有CallBack則直接筝蚕,優(yōu)先調(diào)用callback
handleCallback(msg);
} else {
// 如果Handler存在mCallback卦碾,優(yōu)先處理Handler的Callback
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 此方法會被重寫,用戶用于處理具體的消息
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
dispatchMessage方法中優(yōu)先處理Message的callback起宽,再回頭看看Handler.post方法應(yīng)該知道callback是個啥了吧洲胖。
如果Handler設(shè)置了mCallback, 則優(yōu)先判斷mCallback.handleMessage的返回值,這個機制可以讓我們做一些勾子坯沪,監(jiān)聽Handler上的一些消息绿映。
handleMessage(msg)這個方法一般在創(chuàng)建Handler時被重寫,用于接收消息腐晾。
總結(jié)
1叉弦、一個線程只能有一個Looper,如何確保藻糖?
答案:ThreadLocal
2淹冰、Handler.post(Runnable)會將Runnable封裝到一個Message中。
3巨柒、MessageQueue采用單鏈表的實現(xiàn)方式樱拴,并且在存取消息時都會進行加鎖。
4洋满、Looper.loop采用死循環(huán)的方式晶乔,會阻塞線程。那么為什么主線程不會被阻塞牺勾?
因為Android是事件驅(qū)動的正罢,很多的系統(tǒng)事件(點擊事件、屏幕刷新等)都是通過Handler處理的驻民,因此主線程的消息隊列翻具,會一直有消息的。
5回还、Handler是如何實現(xiàn)線程切換的直秆?
Looper和MessageQueue和線程綁定的事富,也就是說這個消息隊列中的所有消息箩做,最后分給對應(yīng)的Handler都是在創(chuàng)建Looper的線程或杠。所以無論Handler在什么線程發(fā)送消息瘫怜,最后都回到創(chuàng)建Looper的線程中執(zhí)行谎柄。
6膏斤、Thread和Looper彻舰、MessageQueue是一對一的關(guān)系糙捺,Looper诫咱、MessageQueue對于Handler是一對多的關(guān)系。這里要注意洪灯,一個具體的Handler實例坎缭,肯定只關(guān)聯(lián)一個Looper和queue的喲。