最近一直複習Android的知識點,這兩週也一直在看handler的源碼,爲了加深本身的理解的深度遂決定用博客的形式把它記錄下來,handler 你們都用過,這裏不對它的用法進行分析,你們都知道 handler 是用於在 Android 中進行線程間消息切換的,但它是如何作到的呢?爲何在子線程中直接建立handler對象會crash呢?Android系統的開發設計人員爲何不容許在子線程中更新UI呢,又是出於什麼考慮的?帶着這些疑問我從handler發送消息的那塊一步步的分析。安全
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler();
Message message = Message.obtain();
message.obj = "從子線程中建立的消息";
handler.sendMessage(message);
Looper.loop();
}
}).start();
複製代碼
先不要看Looper的部分,這個小節裏主要就看 handler.sendMessage(message); 這行代碼它到底作了什麼,點開代碼跟進去看下。bash
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) {
//省略。。。
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//省略。。。
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
上面一共就摘錄了4個函數,從源碼中能夠得知handler發送消息其實就是按照 sendMessage 、sendMessageDelayed、sendMessageAtTime這樣的順序來的,而且最後調用了 MessageQueue 的 enqueueMessage 方法執行消息的插入,在 sendMessageAtTime 方法中咱們第一次看到了 MessageQueue這個概念,它被mQueue賦值,mQueue是經過 mLooper.mQueue; 獲取的,也就是說Looper對象中維護了一個MessageQueue,這個在下文中會講到,接下來看下消息是如何被插入到消息隊列中的,我把 MessageQueue 的 enqueueMessage 方法複製到下面。多線程
boolean enqueueMessage(Message msg, long when) {
//若是 Message 的 target 屬性爲null則拋出 IllegalArgumentException 異常,這個target其實就是handler,在handler類中
//的enqueueMessage方法中爲其賦值 msg.target = this; 每個消息 Message 對象都維護了一個 handler 對象
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) {
//將消息標記爲使用
msg.markInUse();
//給message的when變量賦值,when是發送消息時用戶本身定義的消息延遲
msg.when = when;
//第一次發送消息時 mMessages 值爲null,因此當第一次發送消息時 p = null
Message p = mMessages;
boolean needWake;
//由於在第一次發送消息時 p == null,因此第一次會執行到if判斷裏,這裏假設第一次發送消息時when的值爲 SystemClock.uptimeMillis()+0
if (p == null || when == 0 || when < p.when) {
//MessageQueue是用單鏈表實現的隊列,這裏將當前message結點的後繼結點next指向null
msg.next = p;
//爲 mMessages 賦值
mMessages = msg;
needWake = mBlocked;
} else {
//假設當你第二次發送消息而且when的值 > Message01.when 時,因爲第一次發送消息時 mMessages 被賦值爲 第一次發送的消息對象(這裏我用Message01指代),
//當你第二次發送消息Message02(when > Message01.when),因爲 p = Message01,因此 when > p.when,條件不成立執行到 else 代碼塊中
//定義 Message prev,此時prev=null
Message prev;
//開啓for循環將消息按when參數的值做爲條件進行排序
for (;;) {
//prev = message01
prev = p;
//p = null
p = p.next;
//條件容許跳出循環
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//message02.next = null
msg.next = p;
//message01.next = message02
//我用message01和message02舉例子來分析handler發送消息到底執行了什麼,上面註釋可能會理解起來有點凌亂,沒事我下面會用圖片的形式在描述下,
//其實你能夠發現MessageQueue這個消息隊列是用單鏈表的形式來體現的,enqueueMessage的執行邏輯就是將消息按照when參數進行排序(when從小到大),
//所謂when其實就是發送消息時定義的消息延遲
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
接下來我會用handler發送4條消息,咱們來看下這4條消息時如何排序的,我會用圖文的方式跟着代碼一步步的走併發
handler.sendMessage(message01);//步驟 1 when = 0
handler.sendMessageDelayed(message03,2000L);//步驟 2 when實際 = SystemClock.uptimeMillis()+2000L,這裏方便就將when理解爲2000,下面也是同樣
handler.sendMessageDelayed(message04,500L);//步驟 3
複製代碼
看到這個圖片可能會犯迷糊,由於我也不知道該用文原本形容這個排序的過程,不過handler發送消息的過程其實就是將消息塞到 MessageQueue 中,而 MessageQueue 是用單鏈表的形式來體現的而且按參數 when 來進行從小到大的排序,到時候取數據的時候也是從這個鏈表中獲取,固然是從鏈表頭開始獲取數據,獲取數據的時候會依據當前時間與這個消息對象所維護的when進行對比,若是當前時間小於要發送的這個when,那麼就會處於「休眠」狀態,這歌休眠其實挺有意思的,咱們在接下來小結中會繼續講解。
文章開頭幾個小題我開的有點大,由於我發現其實和本文沒什麼關係,第一題須要把handler後面的文章所有寫完才能回答,因此咱們看下這題,首先觀察下handler的構造方法async
public Handler(Callback callback, boolean async) {
//省略部分代碼。。。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//省略部分代碼。。。
}
複製代碼
這裏有個 Looper.myLooper(); 它的做用是從 ThreadLocal 中獲取當前線程的 Looper 對象的,因爲在子線程中沒有執行初始化Looper並將其保存到 ThreadLocal 中,因此直接獲取會報RuntimeException,這也是爲何子線程中須要在 handler 初始化以前執行 Looper.prepare(); 了,您能夠點開這代碼本身看下源碼,這裏簡單的講下,在下一小結講Looper中我會按照源碼具體講解。ide
若是容許在子線程中更新UI是否是會遇到多線程併發的問題?若是一個控件在不一樣的線程中均可以更新UI那麼如何保證它的線程安全呢?假設出於這樣的考慮就必須加上鎖機制,鎖機制那是很是耗費資源的,同時還加大了編碼的複雜度,因此還不如只容許在主線程中更新UI呢。函數