目錄
- 思維導圖
- 概述
- 基本使用
- 源碼分析
- Handler
- Message
- Looper
- MessageQueue
- 常見問題匯總
- 總結
- 更新細節(jié)
- 設置同步分割欄
- Native 層實現(xiàn)
- 參考
思維導圖

概述
Handler 的源碼分析文章網(wǎng)上太多了,但是都是掌握一個大概流程,并沒有深入細節(jié)。最開始我也是看一些文章掌握了一個大概流程,然后了解了 Handler、Message、Looper、MessageQueue、ThreadLocal 的作用以及聯(lián)系,現(xiàn)在再自己不看任何資料從頭再看一遍,把一些細節(jié)都理清楚。比如 Handler 的 sendXxx 和 postXxx 兩者的區(qū)別、不同的 Handler 的構造方法的 handleMessage 的優(yōu)先級等等,Message 的排序、回收等。
回到正文:
Android 應用是通過消息驅動運行的,在 Android 中一切皆消息,包括觸摸事件,視圖的繪制、顯示和刷新等等都是消息。Handler 是消息機制的上層接口,平時開發(fā)中我們只會接觸到 Handler 和 Message,內部還有 MessageQueue 和 Looper 兩大助手共同實現(xiàn)消息循環(huán)系統(tǒng)。
基本使用
基本使用不用多說,有以下兩種,但是都存在內存泄漏的情況,如何避免呢?可以通過靜態(tài)內部類 + 弱引用來避免,文末常見問題匯總會有示例。
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.arg1 == 0x01) {
Toast.makeText(HandlerActivity.this, "處理消息", Toast.LENGTH_SHORT).show();
}
return false;
}
});
@SuppressLint("HandlerLeak")
private Handler mHandler1 = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Handler 源碼分析
Handler 源碼其實并不多,我們先來看一下構造方法:
//公開的四種構造函數(shù)
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
//實際上以上前兩種都會調用以下構造方法
//后兩種構造方法其實會在使用 HandlerThread 的時候會用到,就不多闡述了
public Handler(Callback callback, boolean async) {
//初始化 Looper 并關聯(lián) MessageQueue 對象
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;
}
這里可以看到,如果 mLooper 為 null,就是我們常見的運行時異常,提示我們是否忘記調用 Looper.prepare()。
那 Looper.myLooper 到底做了什么呢?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//一個線程只能有一個 Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
可以看到 Looper.myLooper() 方法就是從 ThreadLocal 中取 Looper,而 Looper.prepare() 就是往 ThreadLocal 中存 Looper。ThreadLocal 是用于線程隔離的,它可以在不同的線程中互不干擾的存儲數(shù)據(jù),當某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本時就可以采用 ThreadLocal,下文中會分析 ThreadLocal 的源碼。
回到上面,我們知道在主線程是可以直接使用 Handler 的,可是我們并沒有手動調用 Looper.prepare() 方法呀,為什么沒有報錯呢?原因在于 ActivityThread.main 方法已經(jīng)幫我們做了,所以主線程使用 Handler 時不需要手動調用 Looper.prepare() 方法,而在子線程使用 Handler 時就需要手動調用了。
ActivityThread.main 方法源碼如下:
//ActivityThread 類中的 main 方法
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
//Looper 類中的 prepareMainLooper 方法
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
綜上,Handler 的構造方法就是初始化 Looper,并通過 Looper 關聯(lián) MessageQueue 對象。
熟悉了 Handler 的構造方法,然后就是 Handler 的發(fā)送消息的各種方法,發(fā)生消息可以分為兩種:
- post、postDelayed
- sendMessage、sendMessageDelayed、sendEmptyMessage 等
//第一種方式
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//第二種方式
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到,這兩種方式其實最后都是調用 sendMessageAtTime 方法:
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);
}
//轉到 MessageQueue 的 enqueueMessage 方法
return queue.enqueueMessage(msg, uptimeMillis);
}
這里需要注意的是,在調用 postDelayed 方法時傳入的延遲時間需要再加上 SystemClock.uptimeMillis() 再往下傳,而 SystemClock.uptimeMillis() 表示的是系統(tǒng)開機到當前的時間總數(shù),單位是毫秒,并且當系統(tǒng)進入休眠時時間就會停止,但是不受時鐘縮放或者其他節(jié)能機制的影響。這個時間總和是用來給 Message 排序的,下面會講到。至于為什么是從開機到當前時間呢?這個也很好理解,畢竟只有開機才會有消息的分發(fā);那為什么不用系統(tǒng)時間呢(System.currentTimeMilis),因為這個可能因為用戶調整了系統(tǒng)時間而改變,該值并不可靠。
這里需要注意的是第一種方式,post 一個 Runnable,它不像第二種方式直接傳 Message 對象,所以我們可以看 getPostMessage 到底干了啥?
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
OK,很清晰。只是把我們傳入的 Runnable 賦值給了 Message 的 callback 成員變量。同時,這里實例化 Message 的時候是通過 Message.obtain 的方式,我們知道直接 new Message() 也是可行的,那么這兩種有什么區(qū)別嘛?分析 Message 的時候再說~
這里,為什么我要單獨提一下 Message 的 callback 成員變量呢?其實很有用的,下面會用到。
Message 源碼分析
Message 作為消息的載體,它的源碼也不是很多:
public final class Message implements Parcelable {
//消息的標示
public int what;
//系統(tǒng)自帶的兩個參數(shù)
public int arg1;
public int arg2;
//處理消息的相對時間
long when;
Bundle data;
Handler target;
Runnable callback;
Message next; //消息池是以鏈表結構存儲 Message
private static Message sPool; //消息池中的頭節(jié)點
//公有的構造方法,所以我們可以通過 new Message() 實例化一個消息了
public Message() {
}
//推薦以這種方式實例化一個 Message,
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
//取出頭節(jié)點返回
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
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) {
if (sPoolSize < MAX_POOL_SIZE) {
//鏈表頭插法
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
這里推薦使用 Message.obtain() 方法來實例化一個 Message,好處在于它會從消息池中取,而避免了重復創(chuàng)建的開銷。雖然直接實例化一個 Message 其實并沒有多大開銷,但是我們知道 Android 是消息驅動的,這也就說明 Message 的使用量是很大的,所以當基數(shù)很大時,消息池就顯得非常有必要了。
Looper 源碼分析
前面已經(jīng)分析過了 Looper.myLooper()、Looper.prepare() 、 Looper.prepareMainLooper() 方法了,剩下的源碼為:
public final class Looper {
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
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 (;;) {
//從 MessageQueue 中取消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//通過 Handler 分發(fā)消息
msg.target.dispatchMessage(msg);
//回收消息
msg.recycleUnchecked();
}
}
}
毫不夸張的說,消息機制的核心源碼就在 Looper.loop 方法里,在這個方法里做了三件事:
- 從 MessageQueue 中取出 Message
- 通過 Handler 的 dispatchMessage 分發(fā)消息
- 回收消息
它是一個死循環(huán),不斷地從 Message 里取消息,當消息為空時,根據(jù)注釋可知,消息隊列就停止了,也就是說明應用已經(jīng)關閉了。
在死循環(huán)的第一步通過 MessageQueue.next 取消息,可能會阻塞,具體源碼分析 MessageQueue 的時候再說。
第二步是通過 Handler 來分發(fā)消息,msg.target 即 Handler,看下 Handler.dispatchMessage 做了什么事:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//通過 handler.postXxx 形式傳入的 Runnable
handleCallback(msg);
} else {
if (mCallback != null) {
//以 Handler(Handler.Callback) 寫法
if (mCallback.handleMessage(msg)) {
return;
}
}
//以 Handler(){} 內存泄露寫法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
這里,可以看到分發(fā)消息的優(yōu)先級問題,Message 的 callback 優(yōu)先級最高,它是一個 Runnable,處理消息時直接 run 就好了;然后就是通過 Handler.Callback 寫法,它是由返回值的,如果返回 true,那么在通過 Handler(){} 重寫的方法就不會執(zhí)行到,這種內存泄露的寫法的 handlerMessage 的優(yōu)先級也是最低的。
MessageQueue 源碼分析
接下來就是 MessageQueue 的源碼了,不過 MessageQueue 中存在大量的 native 方法。這里只列舉重要方法:
public final class MessageQueue {
//存消息,從 Handler.sendMessageAtTime 會跳到這
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//當頭節(jié)點為空或當前 Message 需要立即執(zhí)行或當前 Message 的相對執(zhí)行時間比頭節(jié)點早
//則把當前 Message 插入頭節(jié)點
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//根據(jù) when 插入鏈表的合適位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
}
return true;
}
//取消息
Message next() {
for (;;) {
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//取出頭節(jié)點 Message
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());
}
//如果消息不為空,還要看 Message 是要立即取出還是延遲取出
if (msg != null) {
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;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
}
}
MessageQueue 有兩個重要方法,一個是 enqueueMessage 用于存消息,存取消息需要根據(jù)它的 when 相對時間進行鏈表排序。另外一個是 next 方法取出消息,取消息的時候是通過 for 死循環(huán)不斷取消息,也是取得頭節(jié)點,不過需要注意該 Message 是否需要立即取出,如果不是,那就阻塞,等到時間到了在取出消息分發(fā)出去。
到這里,所有的源碼都分析完了,撒花~
常見問題匯總
-
Handler 的 sendXxx 和 postXxx 的區(qū)別?
相信你心中已經(jīng)有了答案。
-
Message 的插入以及回收是如何進行的,如何實例化一個 Message 呢?
相信你心中也已經(jīng)有了答案。
-
Looper.loop 死循環(huán)不會造成應用卡死嘛?
如果按照 Message.next 方法的注釋來解釋的話,如果返回的 Message 為空,就說明消息隊列已經(jīng)退出了,這種情況下只能說明應用已經(jīng)退出了。這也正符合我們開頭所說的,Android 本身是消息驅動,所以沒有消息幾乎是不可能的事;如果按照源碼分析,Message.next() 方法可能會阻塞是因為如果消息需要延遲處理(sendMessageDelayed等),那就需要阻塞等待時間到了才會把消息取出然后分發(fā)出去。然后這個 ANR 完全是兩個概念,ANR 本質上是因為消息未得到及時處理而導致的。同時,從另外一方面來說,對于 CPU 來說,線程無非就是一段可執(zhí)行的代碼,執(zhí)行完之后就結束了。而對于 Android 主線程來說,不可能運行一段時間之后就自己退出了,那就需要使用死循環(huán),保證應用不會退出。這樣一想,其實這樣的安排還是很有道理的。
這里,推薦一個說法:https://www.zhihu.com/question/34652589/answer/90344494
-
Handler 如何避免內存泄漏
Handler 允許我們發(fā)送延時消息,如果在延時期間用戶關閉了 Activity,那么該 Activity 泄漏。這是因為內部類默認持有外部類的引用。
解決辦法就是:將 Handler 定義為靜態(tài)內部類的形式,在內部持有 Activity 的弱引用,并及時移除所有消息。
public class MainActivity extends AppCompatActivity { private MyHandler mMyHandler = new MyHandler(this); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } private void handleMessage(Message msg) { } static class MyHandler extends Handler { private WeakReference<Activity> mReference; MyHandler(Activity reference) { mReference = new WeakReference<>(reference); } @Override public void handleMessage(Message msg) { MainActivity activity = (MainActivity) mReference.get(); if (activity != null) { activity.handleMessage(msg); } } } @Override protected void onDestroy() { mMyHandler.removeCallbacksAndMessages(null); super.onDestroy(); } } -
你知道延時消息的原理嘛?
發(fā)送的消息 Message 有一個屬性 when 用來記錄這個消息需要處理的時間,when 的值:普通消息的處理時間是當前時間;而延時消息的處理時間是當前時間 + delay 的時間。Message 會按 when 遞增插入到 MessageQueue,也就是越早時間的排在越前面。
在取消息處理時,如果時間還沒到,就休眠到指定時間;如果當前時間已經(jīng)到了,就返回這個消息交給 Handler 去分發(fā),這樣就實現(xiàn)處理延時消息了。休眠具體時間是在 MessageQueue 的 nativePollOnce 函數(shù)中處理,該函數(shù)的第二個參數(shù)就是指定需要休眠多少時間。
-
主線程的 Looper#loop() 在死循環(huán),會很消耗資源嘛?
如果沒有消息時,就會調用 MessageQueue 的 nativePollOnce 方法讓線程進入休眠,當消息隊列沒有消息時,無限休眠;當隊列的第一個消息還沒到需要處理的時間時,則休眠時間為 Message.when - 當前時間。這樣在空閑的時候主線程也不會消耗額外的資源了。而當有新消息入隊時,enqueueMessage 里會判讀是否需要通過 nativeWake 方法喚醒主線程來處理新消息。喚醒最終是通過往 EventFd 發(fā)起一個寫操作,這樣主線程就會收到一個可讀事件進而從休眠狀態(tài)被喚醒。
-
你知道 IdleHandler 嘛?
IdleHandler 是通過 MessageQueue.addIdleHandler 來添加到 MessageQueue 的,前面提到當 MessageQueue.next 當前沒有需要處理的消息時就會進入休眠,而在進入休眠之前呢,就會調用 IdleHandler 接口里的 boolean queueIdle 方法。這個方法的返回 true 則調用后保留,下次隊列空閑時還會繼續(xù)調用;而如果返回 false 調用完就被 remove 了。可以用到做延時加載,而且是在空閑時加載。
總結
一張圖說明一切:

圖片來源:
更新細節(jié)
上面已經(jīng)把 Java 層的說清楚了,但是 Native 細節(jié)并沒有講到。其次,還有一些 Java 層的細節(jié)沒有講到。
設置同步分隔欄: MessageQueue.postSyncBarrier()
同步分割欄的原理其實很簡單,本質上就是通過創(chuàng)建一個 target 成員為 null 的 Message 并插入到消息隊列中,這樣在這個特殊的 Message 之后的消息就不會被處理了,只有當這個 Message 被移除后才會繼續(xù)執(zhí)行之后的 Message。
最經(jīng)典的實現(xiàn)就是 ViewRootImpl 調用 scheduleTraversals 方法進行視圖更新時的使用:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 執(zhí)行分割操作后會獲取到分割令牌,使用它可以移除分割欄
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 發(fā)出一個有異步標志的Message,避免被分割
// postCallback 里面會把 Message 設置為異步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
在執(zhí)行 doTraversal 方法后,才會移除分割欄:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
performTraversals();
...
}
}
這樣做的原因是,doTraversal 的操作是通過 Handler 進行處理的,然而這個消息隊列卻是整個主線程公用的,比如說四大組件的各個生命周期的調用,然后 doTraversal 的內容是更新 UI,這個任務無疑是最高優(yōu)先級的,所以在這之前,需要確保隊列中其它同步消息不會影響到它的執(zhí)行。
看一下 MessageQueue.postSyncBarrier() 的實現(xiàn):
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// 注意這里,并沒有為target成員進行初始化
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;
}
}
可以看到,設置分割欄和普通的 post Message 是一樣的,不同的是 target 為空。
分割欄真正起作用的地方是在:
Message next() {
...
for (;;) {
...
// 進行隊列遍歷
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
// 如果target為NULL,將會陷入這個循環(huán),除非是有異步標志的消息才會跳出循環(huán)
} while (msg != null && !msg.isAsynchronous());
}
...
}
}
Native 層實現(xiàn)
首先需要清楚,在 Java 層,在 Looper 的構造方法里面初始化了一個 MessageQueue:
public final class MessageQueue {
private long mPtr;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
}
通過 nativeInit 關聯(lián)了 Native 層的 MessageQueue,在 Native 層的 MessageQueue 中創(chuàng)建了 Native 層的 Looper。
Looper::Looper(bool allowNonCallbacks) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
}
首先調用 epoll_create 來創(chuàng)建一個 epoll 實例,并且將它保存在 mEpollFd 中,然后將前面所創(chuàng)建的管道的讀端描述符添加到這個 epoll 實例中,以便可以對它所描述的管道的寫操作進行監(jiān)聽。
Linux 系統(tǒng)的 epoll 機制是為了同時監(jiān)聽多個文件描述符的 IO 讀寫事件而設計的,它是一個多路復用 IO 接口,類似于 Linux 系統(tǒng)的 select 機制,但是它是 select 機制的增強版。如果一個 epoll 實例監(jiān)聽了大量的文件描述符的 IO 讀寫事件,但是只有少量的文件描述符是活躍的,那么這個 epoll 實例可以顯著減少 CPU 的使用率,從而提高系統(tǒng)的并發(fā)處理能力。
可是前面所創(chuàng)建的 epoll 實例只監(jiān)聽了一個文件描述符的 IO 寫事件,這值得使用 epoll 機制來實現(xiàn)嘛?其實,以后我們還可以調用 C++ 層的 Looper 類的成員函數(shù) addFd 向這個 epoll 實例中注冊更多的文件描述符,以便可以監(jiān)聽它們的 IO 讀寫事件,這樣就可以充分利用一個線程的消息循環(huán)來做其他事情了。在后面分析 Android 應用程序的鍵盤消息處理機制時,我們就會看到 C++ 層的 Looper 類的成員函數(shù) addFd 的使用場景。
在看 MessageQueue 的 next 方法:
public class MessageQueue {
final Message next() {
for(;;){
nativePollOnce(mPtr, nextPollTimeoutMillis);
}
}
}
nativePollOnce 是用來檢查當前線程的消息隊列中是否有新的消息需要處理,nextPollTimeoutMills 用來描述當消息隊列沒有新的消息需要處理時,當前線程需要進去睡眠等待狀態(tài)的時間。
// Native 層的 Looper
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for(;;){
if(result != 0){
return result;
}
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
int result = POLL_WAKE;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {
if (epollEvents & EPOLLIN) {
awoken();
}
}
}
}
如果監(jiān)聽的文件描述符沒有發(fā)生 IO 讀寫事件,那么當前線程就會在 epoll_wait 中進入睡眠等待狀態(tài),等待的時間由最后一個參數(shù) timeoutMillis 來指定。
從函數(shù) epoll_wait 返回來之后,接下來第 11 行到第 21 行的 for 循環(huán)就檢查是哪一個文件描述符發(fā)生了 IO 讀寫事件,如果是 mWakeReadPipeFd 并且發(fā)生的 IO 讀寫事件的類型是 EPOLLIN,就說明其他線程向當前線程所關聯(lián)的一個管道寫入了新的數(shù)據(jù)。
當其他線程向當前線程的消息隊列發(fā)送一個消息之后,它們就會向與當前線程所關聯(lián)的一個管道寫入一個新的數(shù)據(jù),目的就是將當前線程喚醒,以便它可以及時的去處理剛剛發(fā)送到它的消息隊列的消息。
在分析 Java 層的 MessageQueue#enqueueMessage 時,我們知道,一個線程講一個消息插入到一個消息隊列之后,可能需要將目標線程喚醒,這需要分兩種情況來討論:
- 插入的消息在目標隊列中間
- 插入的消息在目標隊列頭部
第一種情況下,由于保存在目標消息隊列頭部的消息沒有發(fā)生變化,因此當前線程無論如何都不需要對目標線程執(zhí)行喚醒操作。
第二種情況,由于保存在目標消息隊列頭部的消息發(fā)生了變化,因此,當前線程就需要將目標線程喚醒,以便它可以對保存在目標消息隊列頭部的新消息進行處理。但是如果這時目標線程不是正處于睡眠等待狀態(tài),那么當前線程就不需要對它進行喚醒,當前線程是否處于睡眠等待狀態(tài)由 mBlocked 來記錄。
// Native 層 MessageQueue#wake
void NativeMessageQueue::wake() {
mLooper->wake();
}
void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
}
也就是調用 write 函數(shù)寫一個 1,這時候目標線程就會因為這個管道發(fā)生了一個 IO 寫事件而被喚醒。
消息的處理過程就簡單了:當一個線程沒有新的消息需要處理時,它就會在 C++ 層的 Looper 類的成員函數(shù) pollInner 中進入睡眠等待狀態(tài),因此,當這個線程有新的消息需要處理時,它首先會在 C++ 層的 Looper 類的成員函數(shù) pollInnter 中被喚醒,然后沿著之前的調用路徑一直返回到 Java 層的 Looper 類的靜態(tài)成員函數(shù) loop 中,最后就可以對新的消息進行處理了。
Native 層分析完畢,總的來說就是利用 Linux 的 epoll 機制。
參考
Android SDK 26 源碼。