這篇文章應(yīng)該是除夕之前的最后一篇文章壹罚,寫文章的一個很深的體會,就是一個知識點雖然自己能理解恋日,可以說出來,但是在寫的時候要花很多時間嘹狞,因為要讓讀者可以很好理解岂膳,不然寫文章就沒有意義了。
進入正題磅网,Android消息機制基本是面試必問的知識點谈截,今天結(jié)合源碼和面試中常問的點,進行一個分析和總結(jié)涧偷。
開始源碼分析
Handler的使用
我們一般使用Handler是這樣
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what == 1){
//
}
}
};
Message message = Message.obtain();
message.what = 1;
//1
handler.sendMessage(message);
//或者2
handler.sendMessageDelayed(message,1000);
//或者3
handler.post(new Runnable() {
@Override
public void run() {
}
});
接下來開始分析簸喂,首先看下 Handler構(gòu)造方法
Handler 構(gòu)造方法
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
//1
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;
}
注釋1:主線程默認有一個Looper,而子線程需要手動調(diào)用 Looper.prepare() 去創(chuàng)建一個Looper燎潮,不然會報錯喻鳄。
看下主線程Looper在哪初始化的,應(yīng)用的入口是 ActivityThread 的main方法
ActivityThread#main
public static void main(String[] args) {
...
//1
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Trace.traceEnd(64L);
//2
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
注釋1:Looper.prepareMainLooper()确封,
注釋2:Looper開啟消息循環(huán)
接下來分析下
Looper.prepareMainLooper()
public static void prepareMainLooper() {
//1
prepare(false);
Class var0 = Looper.class;
synchronized(Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
} else {
sMainLooper = myLooper();
}
}
}
可以看到調(diào)用prepare(false)除呵,參數(shù)false表示不允許退出。然后是給sMainLooper賦值,看下prepare方法
Looper.prepare()
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal();
...
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
} else {
//1
sThreadLocal.set(new Looper(quitAllowed));
}
}
可以看到 prepare 是創(chuàng)建一個Looper爪喘,并放到 ThreadLocal里竿奏。
準備工作做好了,看下Looper.loop()
Looper.loop()
public static void loop() {
//1 從ThreadLocal 獲取Looper
Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
} else {
//2 Looper里面有一個消息隊列
MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
long ident = Binder.clearCallingIdentity();
while(true) {
//3 死循環(huán)獲取消息
Message msg = queue.next();
if (msg == null) {
return;
}
...
long end;
try {
// 4,獲取到消息腥放,處理
msg.target.dispatchMessage(msg);
end = slowDispatchThresholdMs == 0L ? 0L : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0L) {
Trace.traceEnd(traceTag);
}
}
...
// 5.消息回收泛啸,可復用
msg.recycleUnchecked();
}
}
}
public static Looper myLooper() {
return (Looper)sThreadLocal.get();
}
注釋1: 從ThreadLocal 獲取Looper,子線程需要調(diào)用Looper.prepare,不然會報錯秃症,上面說過候址。
注釋2:獲取消息隊列
注釋3:queue.next(),獲取一個消息
注釋4:處理消息
接下來分成幾個小點分析一下
1.mQueue 是什么
private Looper(boolean quitAllowed) {
this.mQueue = new MessageQueue(quitAllowed);
this.mThread = Thread.currentThread();
}
mQueue是一個消息隊列种柑,在創(chuàng)建 Looper 的時候創(chuàng)建的岗仑。
2.MessageQueue#next
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 1 native方法,應(yīng)該是喚醒線程的
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;
//2.取出下一條消息
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.
//4.獲取到一條消息荠雕,因為是消息隊列是鏈表結(jié)構(gòu),所以需要調(diào)整一下鏈表
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.
...
//5.這里提到 IdleHandler驶赏,是個什么東西炸卑?下面會分析
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);
}
}
}
}
}
next方法,取出一條消息煤傍,如果有設(shè)置延時盖文,計算一下多久后執(zhí)行,然后在下一次循環(huán)蚯姆,注釋1處五续,調(diào)用native方法進行一個類似鬧鐘的設(shè)置洒敏,時間到的話會喚醒next方法。消息隊列是單鏈表的數(shù)據(jù)結(jié)構(gòu)疙驾,所以取出一條消息之后鏈表需要移動凶伙,注釋4處。
消息取出來之后就要處理了它碎,即
msg.target.dispatchMessage(msg);
msg.target是一個Handler
注釋5是擴展分析镊靴,面試映客直播時候被問到Handler中的Idle是什么,這里就寫一下
在獲取不到message的時候才會走注釋5的代碼链韭,也就可以理解為 IdleHandler是消息隊列空閑(或者主線程空閑)時候才會執(zhí)行的Handler偏竟。
IdleHandler 是一個接口,只有一個方法 queueIdle() 敞峭,調(diào)用addIdleHandler(IdleHandler handler) 這個方法的時候會將handler添加到list中
public void addIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.add(handler);
}
}
然后在消息隊列空閑的時候會遍歷這個list踊谋,執(zhí)行里面IdleHandler的queueIdle方法。
有什么應(yīng)用場景呢旋讹?
leakcanary 中使用到這個
// 類:AndroidWatchExecutor
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
leakcanary中檢測內(nèi)存泄漏的耗時任務(wù)會等到主線程空閑才執(zhí)行
3.Handler#dispatchMessage
public void dispatchMessage(Message msg) {
// 1
if (msg.callback != null) {
handleCallback(msg);
} else {
// 2
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 3
handleMessage(msg);
}
}
public void handleMessage(Message msg) {
}
一般走的是注釋3殖蚕,也就是我們重寫的 handleMessage方法,
注釋1 :調(diào)用 Message.obtain(Handler h, Runnable callback)
傳的callback沉迹。
注釋2:創(chuàng)建Handler的時候使用這個構(gòu)造Handler(Callback callback)
Looper開啟循環(huán)睦疫,從MessageQueue取消息并處理的流程分析完了,還差一個消息的入隊
Handler#sendMessage
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) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
sendMessage 調(diào)用了sendMessageDelayed鞭呕,最終調(diào)用了enqueueMessage蛤育,進行入隊
Handler#enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最終調(diào)用了 MessageQueue 的 enqueueMessage
MessageQueue#enqueueMessage
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 1.滿足3個條件則插到鏈表頭部
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 {
// 2.否則插到鏈表中間,需要移動鏈表
// 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) {
//如果需要瓦糕,調(diào)用native方法喚醒
nativeWake(mPtr);
}
}
return true;
}
注釋1:p是隊列頭部,滿足3個條件則把消息放到隊列頭部
1.隊列中沒有消息腋么,p==null
2.入隊的消息沒有延時
3.入隊的消息的延時比隊列頭部的消息延時短
注釋2:消息插入到鏈表中咕娄,需要移動鏈表,對比消息的延時珊擂,插入到合適的位置
好了圣勒,消息機制的分析大概就是這些了,接下來結(jié)合面試中的問題再回顧一下
面試中相關(guān)的問題
1.說一下Handler機制
1.在應(yīng)用啟動的時候摧扇,也就是ActivityThread的main方法里面圣贸,創(chuàng)建了Looper和MessageQueue,然后調(diào)用Looper.loop 開啟消息循環(huán)
2.消息循環(huán)是這樣的扳剿,調(diào)用MessageQueue的next方法旁趟,循環(huán)從消息隊列中取出一條消息昼激,然后交給Handler去處理庇绽,一般是回調(diào)handleMessage方法锡搜,取不到消息就阻塞,直到下一個消息入隊或者其它延時消息到時間了就喚醒消息隊列瞧掺。
3.消息入隊耕餐,通過調(diào)用handler的sendMessage方法,內(nèi)部是調(diào)用MessageQueue的enqueueMessage方法辟狈,進行消息入隊肠缔,入隊的規(guī)制是:隊列沒有消息,或者要入隊的消息沒有設(shè)置delay哼转,或者delay時間比隊列頭的消息delay時間短明未,則將要入隊的消息放到隊列頭,否則就插到隊列中間壹蔓,需要移動鏈表趟妥。
2.發(fā)送延時消息是怎么處理的
這個通過上面的分析應(yīng)該很容易答出來了
根據(jù)消息隊列入隊規(guī)制,如果隊列中沒消息佣蓉,那么不管要入隊的消息有沒有延時披摄,都放到隊列頭。如果隊列不空勇凭,那么要跟隊列頭的消息比較一下延時疚膊,如果要入隊的消息延時短,則放隊列頭虾标,否則寓盗,放到隊列中去,需要移動鏈表璧函。
入隊的規(guī)制的好處是贞让,延時越長的消息在隊列越后面,所以next方法取到一個延時消息時柳譬,如果判斷時間沒有到喳张,就進行阻塞,不用管后面的消息美澳,因為隊列后面的消息延遲時間更長销部。
ok,關(guān)于Handler消息機制的分析到此結(jié)束制跟,有問題歡迎留言交流舅桩。