前言
網上Handler機制的文章很多,大部分只涉及Jave層部分佑菩,本系列文章會從Java層源碼逐步分析引入到Native層源碼盾沫,讓你徹底了解Handler機制。
關于Handler的使用
一. Handler機制介紹
Handler機制作為一個老生常談的東西我也不多做介紹殿漠,其不外呼就四個類赴精。
Handler:發(fā)送、接收消息
Message:消息載體绞幌,內部保存了發(fā)送消息的Handler和指向下一條Message的引用
MessageQueue:消息隊列蕾哟,實際是一個基于時間的優(yōu)先級隊列,內部保存了一個Message鏈表的頭結點莲蜘,通過對這個頭結點的遍歷實現了Message鏈表的插入谭确、刪除等操作。
Looper:負責創(chuàng)建MessageQueue票渠、開啟消息循環(huán)逐哈、從MessageQueue中取出消息
二. 創(chuàng)建Message
發(fā)送Message首先要創(chuàng)建Message,有人說創(chuàng)建Message還不簡單问顷,直接new一個不就行了嗎昂秃,正常情況下這樣是可行的薯鼠。但是某些情況下,例如開啟了一個線程械蹋,里面在做循環(huán)計算出皇,每次計算完畢后都要通知Handler去更新UI,那么如果每次發(fā)送消息都去new一個Message哗戈,這樣會造成很大的內存浪費郊艘。
Android內部為我們實現了Message的緩存機制,通過Message.obtain()
或者Handler.obtain()
可從消息緩存池獲取到Message唯咬。
由于Handler.obtain()
內部也是調用的Message.obtain()
纱注,所以只要分析Message.obtain()
即可。
廢話不多說胆胰,上源碼狞贱。
public final class Message implements Parcelable {
//#1
//延遲執(zhí)行的時間
long when;
//發(fā)送消息的那個Handler
Handler target;
//Runnable回調,保存的就是Handler.post()里的那個Runnable
Runnable callback;
//指向下一個message
Message next;
//靜態(tài)的對象鎖(關于這里為什么要使用靜態(tài)的對象鎖而不使用Message.class可以自己研究下)
public static final Object sPoolSync = new Object();
//消息緩存池的頭結點
private static Message sPool;
//當前緩存池里Message的個數
private static int sPoolSize = 0;
//緩存池最大容量
private static final int MAX_POOL_SIZE = 50;
//取出緩存消息
public static Message obtain() {
synchronized (sPoolSync) {
//#2
if (sPool != null) {
Message m = sPool;
sPool = m.next;//將下一個節(jié)點設為新的頭結點
m.next = null;//斷開下一個節(jié)點的鏈接
m.flags = 0;
sPoolSize--;//緩存池計數減一
return m;
}
}
return new Message();
}
//暴露給用戶回收消息
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
//回收消息
void recycleUnchecked() {
//一系列的重置操作
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
//#3
if (sPoolSize < MAX_POOL_SIZE) {//緩存池未滿蜀涨,將消息加入鏈表
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
以上是Message的部分源碼瞎嬉,有些地方加了注釋,看著很長厚柳,其實只要關注#1氧枣、#2和#3處代碼即可。
#1部分别垮,是Message保存的變量便监,發(fā)送Message時需要攜帶這些參數,具體作用已經添加了注釋碳想。
#2#3是Message緩存池機制的代碼烧董,可以看到,這部分源碼都是靜態(tài)的胧奔,所謂的緩存池就是一個靜態(tài)單向鏈表逊移,通過儲存靜態(tài)變量sPool
作為緩存池鏈表的頭結點,sPoolSize
表示當前緩存池鏈表的長度葡盗,存取緩存消息只需要通過sPool頭結點操作鏈表即可螟左,實際上緩存池就是一個棧的數據結構啡浊,遵循先進后出的原則觅够。
obtain()
obtain方法用來從緩存池取消息。方法很簡單巷嚣,若緩存池有消息喘先,就將緩存池鏈表的頭結點sPool返回,并將sPool的下一個節(jié)點設為新頭結點廷粒,然后進行sPoolSize--
recycleUnchecked()
系統通過recycleUnchecked方法來回收Message窘拯,當調用此方法红且,會重置當前Message的所有狀態(tài)和變量,并將當前Message設為緩存池鏈表的頭結點涤姊。
Q1:什么時候回收Message
我們知道了發(fā)送消息的時候需要使用obtain()方法進行Message復用暇番,知道了recycleUnchecked()方法能回收當前Message,那么Message在什么時候回收呢思喊,回收需要我們去調用Message.recycle()方法嗎壁酬?
三. 創(chuàng)建Handler
Message已經創(chuàng)建好了,要發(fā)送這個Message還得通過Handler恨课,創(chuàng)建Handler先就要創(chuàng)建Looper舆乔。Android主線程里已經默認創(chuàng)建好了Looper,所以在主線程里使用Handler是不需要我們再去創(chuàng)建Looper的剂公。
主線程創(chuàng)建Handler的過程
public final class ActivityThread extends ClientTransactionHandler {
public static void main(String[] args) {
......
Looper.prepareMainLooper();//創(chuàng)建主線程Looper
......
ActivityThread thread = new ActivityThread();//這里面會創(chuàng)建主線程Handler
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();//獲取主線程Handler
}
......
Looper.loop();//開啟消息循環(huán)
//后面的代碼不會執(zhí)行到希俩,除非當前Lopper的循環(huán)退出了,在主線程就代表當前程序結束運行
}
}
//#Looper
public static void prepareMainLooper() {
prepare(false);//也是調用的prepare()方法
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
可以看到主線程使用Handler也是先通過prepare()創(chuàng)建Looper纲辽,之后創(chuàng)建Handler颜武,最后調用Looper.loop()開啟消息循環(huán)。
Q2:Handler創(chuàng)建流程為什么一定要Looper.prepare()->創(chuàng)建Handler->Looper.loop()
1. 先看第一步拖吼,創(chuàng)建Looper
//#Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//通過prepare創(chuàng)建Looper
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的創(chuàng)建過程非常簡單盒刚,先調用Looper的構造函數,Looper的構造函數里創(chuàng)建了一個MessageQueue绿贞,隨后將創(chuàng)建的Looper設置為ThreadLocal變量因块,通過ThreadLocal可以確保一個線程對應一個Looper(若對ThreadLocal的用法不太了解可以去網上尋找資料)
2. 創(chuàng)建Handler
public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();//獲取當前線程Looper
if (mLooper == null) {//如果當前線程沒有創(chuàng)建過Looper就拋出異常
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//獲取當前線程的消息隊列
mCallback = callback;//Handler Callback寫法的回調
mAsynchronous = async;//是否異步消息
}
}
//#Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Handler構造函數主要做了一件事,先去拿當前線程的Looper籍铁,通過Looper再拿到MessageQueue涡上。
Q2的第一點:創(chuàng)建Handler之前要調用Looper.prepare()
看源碼可知Handler需要拿到MessageQueue,而MessageQueue是在Looper里創(chuàng)建的拒名,所以創(chuàng)建Handler前必須先創(chuàng)建Looper才能拿到MessageQueue吩愧。
3. Looper.loop()開啟消息循環(huán)
//#Looper
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;//由于MessageQueue是Looper里的對象,所以MessageQueue里取出的Message都是在Looper線程里增显,即不管在哪個線程發(fā)送消息雁佳,接收消息都是在調用了Looper.prepare()方法的那個線程接收。
for (;;) {//所謂的消息循環(huán)其實就是一個死循環(huán)同云,作用是不斷的從MessageQueue里面讀取消息
Message msg = queue.next(); //這是一個阻塞方法糖权,當MessageQueue里沒有下一條消息或者下一條消息是延遲消息時會進入阻塞狀態(tài)
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);//內部會回調Handler的handlerMessage()方法或者Runnable的run()方法
} catch (Exception exception) {
throw exception;
} finally {
}
msg.recycleUnchecked();//回收Message
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
以上代碼是精簡過的,loop()方法里開啟了一個無限for循環(huán)炸站,不斷的調用MessageQueue的next()方法嘗試從消息隊列里面讀取Message星澳,而next()方法是一個阻塞方法,當沒有可讀消息時會進入阻塞狀態(tài)且不占用cpu資源旱易,這樣盡管loop中是一個死循環(huán)也不會造成卡死的情況禁偎。
在拿到Message后腿堤,會調用msg.target.dispatchMessage(msg)
,而target其實就是Handler如暖,來看Handler的dispatchMessage(msg)方法
//#Handler
public void dispatchMessage(@NonNull 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();
}
很簡單笆檀,先判斷msg是否有設置Runnable回調,若有設置就去調用Runnable的run(),若沒有設置Runnable盒至,則根據構造Handler的方式去回調Callback的handleMessage(msg)或者回調Handler的handleMessage(msg)
當通過handler.post()設置了Runnable回調時误债,只回調Runnable的run方法,不會去執(zhí)行handler.handleMessage方法
Looper構造函數要創(chuàng)建MessageQueue妄迁、Handler構造函數要拿到Looper里的MessageQueue寝蹈、Looper.loop()開啟消息循環(huán)也是從MessageQueue里取消息,可見消息的傳遞應該都是圍繞MessageQueue進行的登淘,那么MessageQueue是何方大佬竟然這么重要箫老,接下來就要分析它。
Q1:什么時候回收Message黔州。
通過以上源碼可知耍鬓,在系統調用handler.dispatchMessag()后就會調用msg.recycleUnchecked()方法將Message回收,所以一般情況下流妻,并不需要我們去特意回收Message牲蜀。
當然,在調用messageQueue.removeMessages等方法移除消息隊列中的消息時也會將Message回收绅这。
Q2的第二點:為什么要先創(chuàng)建Handler才能去Looper.loop()涣达。
由于loop()方法里是一個死循環(huán),所以循環(huán)后面的代碼都不會執(zhí)行到证薇,所以必須先創(chuàng)建Handler才能去調用Looper.loop()
四. 發(fā)送Message
發(fā)送消息有發(fā)送Message和發(fā)送Runnable兩種方式度苔,其實發(fā)送Runnable也是發(fā)送Message,只不通過getPostMessage()方法過把Runnable變成Message的一個callback參數
//#Handler
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
無論通過Handler的哪種方式發(fā)送浑度,最后都會調用MessageQueue的enqueueMessage(msg, uptimeMillis)方法寇窑。
借用下其它博客的一張圖片
五. MessageQueue
1. 先看enqueueMessage()方法
//#MessageQueue
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(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) {//#1,插入頭結點
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {//#2箩张,根據when插入到合適的位置
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {//#3
nativeWake(mPtr);//nativa方法甩骏,作用是喚醒nativePollOnce方法的阻塞
}
}
return true;
}
首先了解下when是什么:when=SystemClock.uptimeMillis() + delayMillis,SystemClock.uptimeMillis()是開機到現在的毫秒數先慷,那么when就是一個時間節(jié)點
#1:將新消息msg的when和頭結點mMessages的when作比較饮笛,若msg是早于mMessages執(zhí)行的,就將mMessages設為msg的next熟掂,然后將msg設為新的mMessages缎浇,這樣msg就插入到了鏈表的頭結點扎拣。
#2:若msg不能插入鏈表頭結點赴肚,則開啟循環(huán)素跺,不斷比較鏈表下一個元素的when和msg的when大小,直到msg的when小于某個節(jié)點的when誉券,就將msg插入此處指厌。
#3:調用nativeWake(mPtr),喚醒nativePollOnce(ptr, nextPollTimeoutMillis)踊跟。
簡單來說踩验,enqueueMessage()方法的作用就是通過比較when的大小,將新msg消息插入消息鏈表對應的位置(即msg的when小于鏈表某節(jié)點的when時對應的那個位置)
2. MessageQueue.next()
知道了消息是怎么存到MessageQueue里的商玫,那么怎么從MessageQueue里取消息呢箕憾?答案就是之前在Looper.loop()里用到的next()方法,我們知道loop()會不斷循調用MessageQueue.next()拳昌,那么MessageQueue.next()應該就是用來取出MessageQueue里鏈表下一條消息的袭异,具體怎么取呢?請看源碼炬藤。
//#MessageQueue
@UnsupportedAppUsage
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();
}
//#1
//nextPollTimeoutMillis<0 一直阻塞
//nextPollTimeoutMillis=0 不阻塞
//nextPollTimeoutMillis>0 阻塞nextPollTimeoutMillis時間
nativePollOnce(ptr, nextPollTimeoutMillis);//nativa的阻塞方法
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//#2
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) {
//#3
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;
}
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(TAG, "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()方法里也開啟了一個for循環(huán)上真。進入循環(huán)后會調用nativePollOnce(ptr, nextPollTimeoutMillis)方法進入阻塞狀態(tài),該方法是一個nativa的阻塞方法羹膳,喚醒該方法的方式有兩種
第一種方式:nextPollTimeoutMillis指定的延遲時間已到睡互,會自動喚醒該方法。
第二種方式:當有新消息插入后陵像,會調用nativeWake方法喚醒nativePollOnce湃缎,nativeWake方法在之前分析enqueueMessage已經說明。
#2處代碼暫時不分析蠢壹,先看#3處代碼嗓违。
#3:判斷當前時間和msg的時間大小,若當前時間小于msg時間图贸,則說明這條新消息需要延遲返回蹂季,設置nextPollTimeoutMillis 延遲的時間為msg.when - now,之后進入下一次循環(huán)并進入阻塞狀態(tài)疏日,等待延遲時間到或者有新消息加入喚醒阻塞偿洁。
若當前時間大于等于msg時間,說明這條新消息需要立即返回沟优,取出消息鏈表的頭結點返回并將頭結點的next節(jié)點設為新的頭結點涕滋。
這時有人會問loop()里面也是一個for循環(huán),取個消息需要兩個for循環(huán)嗎挠阁?這就要提到我之前說的那句話了宾肺,MessageQueue不是一個隊列溯饵,按照正常的邏輯,若MessageQueue是一個隊列锨用,那么根據先進先出的原則丰刊,next()方法應該立即返回最早push進去的那條消息,實際上并不一定增拥。因為Message中有一個很重要的變量when存在啄巧,假設next()里面沒有循環(huán),使得即使在loop()里調用next()方法掌栅,代碼運行到nativePollOnce方法進入阻塞狀態(tài)秩仆,當有新延遲消息加入喚醒nativePollOnce方法時,由于舊消息延遲時間還未到猾封,而新加入的消息也是延遲消息逗概,這時next()方法便取不到消息只能返回null,而MessageQueue里明顯是有消息的忘衍,顯然這不合理逾苫。
loop的循環(huán)負責從MessageQueue里不斷地取出消息,next()里的循環(huán)負責從消息鏈表正確的取出對應的那條消息
Handler機制大致原理分析完了枚钓,不過肯定還有很多小伙伴云里霧里铅搓,我們先來總結一下。
- 一個線程對應一個Looper搀捷,一個Looper對應一個MessageQueue星掰,創(chuàng)建Looper時會順帶創(chuàng)建了MessageQueue
- Handler機制消息輪詢的三個重要方法,MessageQueue.enqueueMessage嫩舟、MessageQueue.next氢烘、Looper.loop
MessageQueue.enqueueMessage:往鏈表插入一條消息
MessageQueue.next:阻塞方法,從鏈表取出一條消息
Looper.loop:從MessageQueue里循環(huán)讀取消息 - 發(fā)送消息時家厌,Runnable也會被封裝成一個Message播玖,最終會調用MessageQueue的enqueueMessage(msg, uptimeMillis)方法
這里再說兩個常見的問題
Q1:主線程中l(wèi)oop死循環(huán)為什么不會造成程序卡死
A:Android是消息驅動的,每一次事件饭于、更新UI等響應都是在loop里面進行蜀踏,正是由于loop中是一個死循環(huán),這才可以保證程序運行后不會結束掰吕,且在沒有響應事件時果覆,loop會進入阻塞狀態(tài)釋放CPU資源,以上原因保證了程序的正常運行殖熟。
Q2:Handler是怎么切換線程的
A:線程切換的原理就是線程間的資源共享局待。因為Looper是一個ThreadLocal變量且屬于創(chuàng)建Handler時的線程,所以可以保證無論發(fā)送消息在哪個線程,通過Looper從MessageQueue取出的消息都是在Handler線程钳榨。