我會通過講解 Handler/Looper/MessageQueue/Message 這幾個類的作用以及它們之間的協(xié)作,來簡單的描述 Android 的消息機制。
1. Message
我們說的消息機制中的消息就是 Message 這個類
1.1 消息的組成
消息的標識
- int what: Message 對象的唯一標識
消息攜帶的一些值
- int arg1: 用來存儲一些 integer 值,是 Bundle 的低成本替代方案
- int arg2: 用來存儲一些 integer 值,是 Bundle 的低成本替代方案
- Object obj: 一個任意類型的對象
- Bundle data: Bundle 數(shù)據(jù)
如何處理消息
- Runnable callback: 一段可執(zhí)行代碼塊,如果消息對象攜帶 callback 屬性,則優(yōu)先執(zhí)行 callback 代碼塊
- Handler target: 用來處理消息,可以通過重寫 Handler 對象的 handleMessage 來實現(xiàn)具體的處理邏輯
除此之外,Handler 中也提供了另外一種對消息對象的處理,之后我們會說到。
其他屬性
- long when: 消息期望被處理的時間
- Message next: 指向下一個消息對象,用來支持鏈表
- int flags: 標志位,用來用來標識消息是否為異步,是否在使用中
static final int FLAG_IN_USE = 1 << 0;
static final int FLAG_ASYNCHRONOUS = 1 << 1;
可以看到,最低位為 1 表示消息在使用中,倒數(shù)第二位為 1 表示當前消息是異步消息
1.2 如何創(chuàng)建一個消息對象
Message 提供了一個無參的構(gòu)造函數(shù)來創(chuàng)建 Message 對象
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}
不過官方更推薦使用 obtain() 方法來創(chuàng)建 Message 對象,我們來看看幾個重載的 obtain 方法
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
public static Message obtain(Handler h, int what) {
Message m = obtain();
m.target = h;
m.what = what;
return m;
}
上面列出的兩個重載的方法,都是先通過無參的 obtain() 放來來創(chuàng)建一個 Message 對象,然后在給對應(yīng)的屬性賦值,還有一些多參的重載方法我沒有列出,它們的步驟和上述兩個方法做的事情大致相同。
消息池
接著看這個 obtain() 方法是如何創(chuàng)建消息對象的:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可以看到,Message 內(nèi)部用鏈表實現(xiàn)了一個全局池子,每次調(diào)用 obtain() 方法會從池子中撈取一個消息對象(鏈表頭),如果消息對象為空,則通過構(gòu)造函數(shù)來創(chuàng)建一個消息對象并返回。
- static Message sPool: 消息列表的頭節(jié)點
- static int sPoolSize = 0: 當前消息池的大小
- private static final int MAX_POOL_SIZE = 50: 默認消息池的大小
消息回收
既然可以從消息池中取消息,那消息是如何放進池子中的(消息回收)?
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() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
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) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
- 檢查消息標記位,不在使用狀態(tài)時,可以被回收
- 將標記為設(shè)置為使用中狀態(tài),同時將其他屬性設(shè)置為初始化值
- 回收池不滿的話,則放入回收池(頭插法)
1.3 一些問題
看完上述對于 Message 的介紹之后,你可能能夠回答這些問題?
- 創(chuàng)建一個 Message 對象的推薦做法
- Message 回收池的數(shù)據(jù)結(jié)構(gòu)
也可能會有這些疑問?
- 異步消息有什么用?
在之后我們將介紹異步消息的作用。
2. 一個小例子
我們帶著 Looper 和 Handler 創(chuàng)建有沒有依賴關(guān)系? 這個問題來看這個例子
package com.xiezhen.handlerstudy;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView tvHandler;
private Button btnUpdate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvHandler = (TextView) findViewById(R.id.tv_handler);
new Thread(new Runnable() {
@Override
public void run() {
new Handler();
}
}).start();
}
}
運行上面的代碼, 程序?qū)⒅苯颖罎ⅲe誤日志如下:

在一個沒有調(diào)用 Looper.prepare() 方法的線程中無法創(chuàng)建 Handler,也就是說 Handler 必須在 Looper 線程中創(chuàng)建和使用。
3. Looper
我們來看下源碼中的示例代碼:
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();
}
}
- 先調(diào)用 Looper.prepare() 方法創(chuàng)建一個 Looper 對象
- 創(chuàng)建 Handler 對象,并且重寫 handleMessage 方法來處理輸入的消息
- 調(diào)用 Looper.loop() 方法開啟消息循環(huán),該方法內(nèi)會不斷的取出消息然后進行分發(fā)處理
3.1 Looper.prepare()
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");
}
sThreadLocal.set(new Looper(quitAllowed));
}
無參的 prepare() 方法會調(diào)用 prepare(true) 方法,表示不允許退出消息隊列,false 則表示允許退出。
之后會調(diào)用 Looper 的構(gòu)造函數(shù) new Looper(quitAllowed) 來創(chuàng)建一個 Looper 對象,并且存儲在 ThreadLocal 中(在此之前,我們先會判斷當前線程有沒有創(chuàng)建過 Looper 對象,一個線程只允許創(chuàng)建一個 Looper 對象)
有關(guān)于 TreadLocal 的介紹請看這里 Android的消息機制之ThreadLocal的工作原理
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper 的構(gòu)造函數(shù)比較簡單
- 創(chuàng)建一個 MessageQueue 對象并且賦值給 mQueue 變量
- 獲取當前線程對象并賦值給 mThread 變量
3.2 Looper.loop()
我刪除了部分代碼,僅保留了一些檢查邏輯和消息分發(fā)邏輯
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;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked();
}
}
首先會檢查當前 ThreadLocal 中有沒有設(shè)置過 Looper 對象,沒有的話則會拋出異常,在調(diào)用 loop() 方法之前必須調(diào)用 prepare() 方法。
然后開啟一個循環(huán):
- 通過
queue.next()方法從 MessageQueue 中取出消息 - 通過設(shè)置的 Printer 打印 Dispatching 日志
- 調(diào)用
msg.target.dispatchMessage(msg)進行消息處理 - 通過設(shè)置的 Printer 打印 Finished 日志
- 回收消息
3.3 檢測耗時的消息
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
在 loop 方法的循環(huán)中,在處理消息前后都會通過 Printer 打印日志,我們可以自己實現(xiàn) Printer 并且設(shè)置給 Looper,來統(tǒng)計每個消息的處理耗時(參考 BlockCanary 的實現(xiàn))
package com.example.realxz.handlertest
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.support.v7.app.AppCompatActivity
import android.util.Printer
import android.widget.Toast
const val THRESHOLD_MILLIS = 3000
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val obj = object : IBlockListener {
override fun doOnBlock() {
Toast.makeText(this@MainActivity, "Blocking", Toast.LENGTH_SHORT).show()
}
}
val printer = TestPrinter(obj)
val mainLooper = Looper.getMainLooper()
mainLooper.setMessageLogging(printer)
val handler = Handler(mainLooper)
handler.post {
Thread.sleep(5000)
}
}
class TestPrinter(private var listener: IBlockListener) :Printer {
private var printStarted = false
private var startTime: Long = 0
override fun println(x: String?) {
if (!printStarted) {
startTime = System.currentTimeMillis()
printStarted = true
} else {
val endTime = System.currentTimeMillis()
printStarted = false
if (isBlock(endTime)) {
listener.doOnBlock()
}
}
}
private fun isBlock(endTime: Long) = (endTime - startTime) > THRESHOLD_MILLIS
}
interface IBlockListener {
fun doOnBlock()
}
}
- 自定義 Printer 實現(xiàn) println 方法,來統(tǒng)計 dispatchMessage 消耗的時間,如果超過我們設(shè)置訂的時間(3 秒鐘)則會觸發(fā)我們的回調(diào)函數(shù)
- 獲取當前線程的 Looper 對象,并將自定義的 Printer 對象設(shè)置給 Looper 對象
- 發(fā)送一個消息,讓線程休眠 5 秒鐘來模擬耗時操作
- 執(zhí)行上述代碼,你將會看到 Blocking 的 Toast 提示
3.4 小結(jié)
打個比方,prepare 方法相當于創(chuàng)建了一個行李傳送履帶,loop 方法相當于啟動行李傳送履帶,行李相當于我們發(fā)送的 Message,looper 會將最先放置在履帶上的行李傳送到終點進行處理。
4. Handler
對于 Handler 來說,我們需要關(guān)注的三個點是:
- 如何創(chuàng)建 Handler 對象
- 如何發(fā)送消息
- 如何處理消息
4.1 創(chuàng)建 Handler 對象
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
我們最常使用的無參構(gòu)造函數(shù),默認回調(diào)為空,并且發(fā)送的消息全部為同步消息,可以看到 Handler 的創(chuàng)建是依賴于 Looper 對象的。
同時我們也可以使用指定的 Looper 對象來創(chuàng)建 Handler:
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
4.2 使用 Handler 發(fā)送消息
首先看看我們最常用的 sendMessage(Message msg) 方法的調(diào)用鏈:
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);
}
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(Message msg, long when) 方法來完成消息的入隊,可以想象是在傳送帶上放一個行李,實際上是一個單鏈表的插入操作。
- 將 this(Handler 對象)賦值給 msg.target 屬性
- 根據(jù)創(chuàng)建 Handler 時傳入的 mAsynchronous 標志,來決定消息是否異步
- 根據(jù)當前系統(tǒng)時間插入到鏈表中
4.3 處理消息
在介紹 Looper 的時候,在 loop 方法的 for 循環(huán)中獲取到消息后,會調(diào)用 msg.target.dispatchMessage(msg) 方法進行消息處理,根據(jù)上面的描述,可以知道 msg.target 是一個 Handler 對象,我們看下 Handler 的 dispatchMessage(Message msg) 方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
- 如果 msg.callback 不為空,則執(zhí)行 callback 的
run()方法,這類消息通常使用post(Runnable r)方法創(chuàng)建,會將 Runnable 對象賦值給 Message 的 callback 屬性 - 如果 Handler 的 mCallback 對象不為空,則執(zhí)行 Callback 對象的
handleMessage方法,Callback 對象通過 Handler 的構(gòu)造函數(shù)傳入 - 實現(xiàn)一個 Handler 子類,并重寫
handleMessage(Message msg)方法來對消息進行處理
5. MessageQueue
先回顧一下文章的前半部分說了什么:
- Message 的數(shù)據(jù)結(jié)構(gòu)
- Message 的復(fù)用機制
- Looper 中通過 MessageQueue 的
next()方法來獲取消息 - Looper 中通過 Message 的 target 屬性來處理消息
- Looper 中在處理消息前后打印日志,統(tǒng)計消息處理的耗時
- Handler 中通過 MessageQueue 的
enqueueMessage(Message msg, long when)方法根據(jù)when指定的時間,將 Message 插入鏈表合適的位置
最開始介紹 Message 的時候,我們已經(jīng)知道消息內(nèi)部有個 Message 類型的屬性 next,用來支持鏈表。
Message 的回收池使用的鏈表這種數(shù)據(jù)結(jié)構(gòu),MessageQueue 當中同樣是使用鏈表來存儲消息:
Message mMessages
MessageQueue 內(nèi)部的 mMessages 屬性指向列表的頭節(jié)點。
接下來重點放到消息的存放和取出上
5.1 enqueueMessage(Message msg, long when)
根據(jù) when 指定的時間,將 msg 插入到鏈表合適的位置上
boolean enqueueMessage(Message msg, long when) {
// 注意點1
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) {
// 注意點2
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;
// 注意點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 {
// 注意點 4
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;
}
// 注意點 5
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
- 首先檢測消息的 target 和 flags 屬性,不允許將 target 為空或者被標記為正在使用的消息通過 enqueueMessage 方法入隊
- 如果當前隊列已經(jīng)標記為退出,則將消息回收,入隊失敗
- 滿足以下三種情況的話,會將消息插入鏈表頭部
- 當前鏈表為空(p == null)
- 當前消息需要立刻執(zhí)行(when == 0)
- 當前消息早于根節(jié)點消息的時間(when < p.when)
- 鏈表的比較操作,根據(jù) when 值將鏈表插入合適的位置
- 如果 needWake == true,則喚醒消息隊列
- 消息入隊成功
消息的入隊其實就是單鏈表的插入操作,不難理解,整個插入過程中會對 needWake 這個值進行修改,來決定是否喚醒消息隊列(消息隊列在沒有消息的時候處于阻塞狀態(tài))
- 插入鏈表頭部
- 消息隊列阻塞,needWake = true
- 消息隊列不阻塞,needWake =false
- 插入列表中
- 頭節(jié)點消息 target == null,并且當且入隊消息是第一個異步消息,needWake = true
之前我們說過通過 enqueueMessage 方法插入的消息都會檢查 target 屬性是否為空,如果為空則拋出異常,那么這種 target == null 的消息是怎么插入鏈表中的呢?
5.2 postSyncBarrier()
MessageQueue 內(nèi)部為我們提供了一個方法 postSyncBarrier 方法
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到在方法內(nèi)部,創(chuàng)建了一個 Message 對象,并且根據(jù)時間插入到了鏈表合適的位置上,這個消息的插入不會喚醒消息隊列。
這個消息我們稱為是同步屏障,它的作用是攔截所有屏障之后的同步消息,異步消息則不受影響(我認為在這種情況下,異步消息的優(yōu)先級提高了)
同時記得需要調(diào)用 removeSyncBarrier(int token) 方法來刪除同步屏障,不然我們的同步消息就無法執(zhí)行了
5.3 next()
最后看看,如何從消息隊列中取出消息,我刪除了部分代碼,保留了一些關(guān)鍵信息
Message next() {
int pendingIdleHandlerCount = -1; // IdleHandler 數(shù)量
int nextPollTimeoutMillis = 0; // 下次讀取消息的時間
for (;;) {
//注意點 1
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
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);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
-
nativePollOnce(ptr, nextPollTimeoutMillis);方法是一個阻塞操作(管道機制),當隊列中沒有消息的時候,或者沒有消息現(xiàn)在需要處理的時候,代碼會阻塞,這意味著 nativePollOnce 方法之后的代碼不會執(zhí)行(我理解阻塞是當前線程不再占用 CPU 資源)。 - nextPollTimeoutMillis 默認值為 0 ,意味著 nativePollOnce 方法不會阻塞,后續(xù)代碼得到執(zhí)行,等于 -1 的話意味著 nativePollOnce 方法會阻塞,等待著管道的寫入信號,寫入這一操作其實就是在介紹 enqueueMessage 時講到的
nativeWake(mPtr)方法, - 然后開始從鏈表中取出消息對象
- 首先判斷當前消息是不是一個同步消息屏障,如果遇到了同步消息屏障,則循環(huán)去找鏈表中第一個異步消息
- 找到異步消息后,根據(jù)時間判斷當前是否需要執(zhí)行異步消息,滿足時間條件話,返回消息對象
- 如果異步消息不滿足時間條件,則會將時間差值賦值給 nextPollTimeoutMillis 變量,在下次循環(huán)時,nativePollOnce 方法將會阻塞對應(yīng)的時長
- 如果是同步消息,依然是對時間進行判斷,需要執(zhí)行的話,則返回消息對象,不然則會在下次循環(huán)的時候阻塞對應(yīng)的時間
- 如果當前消息隊列中沒有消息,或者第一個消息對象還沒到執(zhí)行時間,我們會執(zhí)行 IdleHandler 相關(guān)的處理,IdleHandler 的作用就像是它的名字一樣,會在消息隊列空閑時執(zhí)行一些操作
- 如果當前消息隊列沒有設(shè)置 IdleHandler 則進行下次循環(huán),并且設(shè)置 mBlock = true
- 如果當前消息隊列的 IdleHandler 列表中有值,則會遍歷 IdleHandler 列表,執(zhí)行 IdleHandler 的
queueIdle()方法 - 將 pendingIdleHandlerCount 置為 0,這意味 IdleHandler 在
next()方法中只會執(zhí)行一次 - 將 nextPollTimeoutMillis 設(shè)置為 0,下次循環(huán)調(diào)用 nativePollOnce 方法的時候不會阻塞,代碼會繼續(xù)執(zhí)行去消息隊列中取消息,這是因為在 IdleHandler 執(zhí)行的過程中可能會有新的消息傳遞進來。
5.4 異步消息的用法
在我們刷新 UI 的時候會調(diào)用 ViewRootImpl 類中的 scheduleTraversals 方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
注意在該方法中通過 mHandler.getLooper().getQueue().postSyncBarrier();在消息隊列中設(shè)置了一個同步消息屏障,接著在 Choreographer 類中通過發(fā)送異步消息來完成一些繪制操作
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
通過這種方式來保證繪制的優(yōu)先級,在繪制完畢后會刪除同步消息屏障
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
5.5 IdleHandler 的實際運用
ActivityThread 的 Handler 對象 mH 中會對 what == 120 的消息做如下處理
public static final int GC_WHEN_IDLE = 120;
case GC_WHEN_IDLE:
scheduleGcIdler();
break;
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
當收到 120 的消息后,會在消息隊列中加入一個名叫 mGcIdler 的 IdleHander 對象,從名字上看是做一些 GC 操作
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
// + "m now=" + now);
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg");
}
}
從代碼上看,確實是做了一些 GC 操作,并且是在消息隊列空閑的時候執(zhí)行。
6. 小結(jié)
閱讀完文章后你應(yīng)該能夠了解
- Handler 如何發(fā)送消息
- Looper 如何取消息
- MessageQueue 的消息出隊、入隊操作
- 同步消息屏障和異步消息的作用
- IdleHandler 有什么用
- Message 的結(jié)構(gòu)和復(fù)用
- 如何統(tǒng)計消息處理的耗時