代碼分析Android消息機(jī)制
我們知道在編程時(shí)許多操作(如更新UI)需要在主線程中完成,而且,耗時(shí)操作(如網(wǎng)絡(luò)連接)需要放在子線程中,否則會(huì)引起ANR。所以我們常使用Handler來實(shí)現(xiàn)線程間的消息傳遞,這里討論的也就是Handler的運(yùn)行機(jī)制。
Handler的運(yùn)行主要由兩個(gè)類來支撐:Looper與MessageQueue。熟悉開發(fā)的朋友都知道在子線程中默認(rèn)是無法創(chuàng)建Handler的,這是因?yàn)樽泳€程中不存在消息隊(duì)列。當(dāng)需要?jiǎng)?chuàng)建一個(gè)與子線程綁定的Handler時(shí),標(biāo)準(zhǔn)代碼如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
在創(chuàng)建Handler前,需要先調(diào)用Looper.prepare()方法,之后再調(diào)用Looper.loop()方法。也就是說Handler的功能實(shí)現(xiàn)建立在Looper之上。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
final MessageQueue mQueue;
final Thread mThread;
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的消息循環(huán)是一個(gè)死循環(huán),一個(gè)線程最多只能有一個(gè)Looper,所以Looper.prepare()函數(shù)首先檢查該線程是否已經(jīng)擁有一個(gè)Looper,如果有則拋出異常。Looper通過ThreadLocal類為每個(gè)線程儲(chǔ)存獨(dú)立的Looper實(shí)例,簡單說一下ThreadLocal的實(shí)現(xiàn)原理:
Java并發(fā)編程:深入剖析ThreadLocal
首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本。
初始時(shí),在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。
然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。
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;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
在4行可以看到一個(gè)我們熟悉的異常信息,說明并沒有Looper與當(dāng)前線程相關(guān)聯(lián),也就無法進(jìn)行消息傳遞。Looper.loop()方法本身是一個(gè)死循環(huán),不斷在MessageQueue中取出Message對(duì)象進(jìn)行處理,然后調(diào)用Message.recycleUnchecked()方法對(duì)其回收,這也是為什么官方推薦使用Message.obtain()方法來獲取Message實(shí)例,而不是直接新建對(duì)象。當(dāng)沒有消息可處理時(shí),MessageQueue.next()方法將阻塞,直到新的消息到來。
對(duì)于MessageQueue,我們只需要關(guān)注兩個(gè)函數(shù)即可,一個(gè)是MessageQueue.enqueueMessage()另一個(gè)是MessageQueue.next(),它們分別對(duì)應(yīng)著隊(duì)列的插入與取出操作。MessageQueue中隊(duì)列是使用單鏈表實(shí)現(xiàn)的,由Message.next屬性指向其下一個(gè)元素。
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
}
return true;
}
向MessageQueue中插入元素時(shí),需要根據(jù)Message.when屬性的大小決定插入的位置,它代表了Meesage需要被處理的時(shí)間,拿Handler.sendMessage()函數(shù)為例。
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;
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);
}
從調(diào)用流程來看,Handler.sendMessage()函數(shù)其實(shí)就是向MessageQueue的消息隊(duì)列中插入了一個(gè)Message.when屬性為當(dāng)前時(shí)間的元素。
對(duì)于MessageQueue.next()函數(shù),簡單來說它的作用就是在MessageQueue的頭部取出元素,然后執(zhí)行Handler.dispatchMessage()函數(shù)。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
如果我們使用Handler.post()函數(shù)發(fā)送一個(gè)Runnable對(duì)象,那么最終Runnable對(duì)象會(huì)在Handler.handleCallback()函數(shù)中執(zhí)行。如果是一個(gè)普通Message,那么它會(huì)被分發(fā)到一個(gè)我們熟悉的函數(shù)中,Handler.handleMessage(),這就是為什么一般我們都需要重寫這個(gè)函數(shù)對(duì)消息進(jìn)行處理。
相關(guān)文章
Android 使用viewpager實(shí)現(xiàn)無限循環(huán)(定時(shí)+手動(dòng))
這篇文章主要介紹了Android 使用viewpager實(shí)現(xiàn)無限循環(huán)(定時(shí)+手動(dòng))的相關(guān)資料,需要的朋友可以參考下2015-11-11
Android使用ViewPager實(shí)現(xiàn)無限滑動(dòng)效果
相信在大家開發(fā)Android的時(shí)候,我們常常用ViewPager來為自己的應(yīng)用創(chuàng)建廣告條幅,并且常常會(huì)遇到ViewPager無限滑動(dòng)這樣的需求。下面來一起看看吧。2016-09-09
Android實(shí)現(xiàn)登錄注冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)登錄注冊(cè)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Android ListView自動(dòng)顯示隱藏布局的實(shí)現(xiàn)方法
這篇文章主要介紹了Android ListView自動(dòng)顯示隱藏布局的實(shí)現(xiàn)方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09
android獲取屏幕的長與寬實(shí)現(xiàn)代碼(手寫)
android中獲取屏幕的長于寬,參考了網(wǎng)上有很多代碼,但結(jié)果與實(shí)際不符,如我的手機(jī)是i9000,屏幕大小是480*800px,得到的結(jié)果卻為320*533,于此問題很是疑惑,于是自己寫了幾行代碼,親測(cè)一下,效果還不錯(cuò),需要了解的朋友可以參考下2012-12-12
Android Jetpack 狠活Lifecycles與LiveData使用詳解
這篇文章主要為大家介紹了Android Jetpack 狠活Lifecycles與LiveData使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Android 關(guān)機(jī)彈出選擇菜單的深入解析
本篇文章是對(duì)Android 關(guān)機(jī)彈出選擇菜單進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android實(shí)現(xiàn)隨意拖動(dòng)View效果的實(shí)例代碼
這篇文章主要介紹了Android實(shí)現(xiàn)隨意拖動(dòng)View效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-07-07

