本文基於android 7.1.1的原始碼,來對Handler的訊息機制進行分析
一、概述
Android執行緒間的訊息機制最常用的就是用Handler來實現,而Handler的實現還需要藉助Looper、MessageQueue、Message這三個類,下圖為這4個類之間的關係
1.1 UML類圖關係
![](https://i.iter01.com/images/86904bc785ba52aaeefcc73d2f533fe7f36f14bacaaf971a05900f28f84f5038.png)
訊息的機制主要包含
Handler :主要功能是向訊息池傳送訊息(
sendMessage
)和處理相應的訊息(handleMessage
);Message :主要功能是作為訊息的載體,包括軟體訊息和硬體訊息;
MessageQueue :主要功能是負責將訊息儲存到訊息池裡(
enqueueMessage
)和從訊息池取出訊息(next
);Looper :主要功能是不斷的迴圈執行(
loop
),按分發機制將訊息分發給目標處理者;
在正文開始前,再來普及一些概念
Process : 程式(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。程式之間記憶體是不同享的。
Thread :執行緒,有時被稱為輕量級程式(Lightweight Process,LWP),是程式執行流的最小單元。同一程式下,執行緒共享記憶體。
1.2 Demo
public class MainActivity extends AppCompatActivity {
private static Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//自定義處理
Log.i("TAG","收到的訊息:"+msg.what);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.what = 1;
mHandler.sendMessage(message);
}
}).start();
}
}複製程式碼
上面這段程式碼應該是最簡單的Handler使用例子了。這段程式碼裡面只涉及到了Handler以及Message,並沒有看到Looper以及MessageQueue。我再給段虛擬碼讓大家好理解。
1.3.1 虛擬碼
public class ActivityThread extends Thread{
private Handler mHandler;
@Override
public void run() {
Looper.prepare(); //【見2.1】
ActivityThread thread = new ActivityThrad();
//建立Binder通道(建立新執行緒)
thread.attach(false);
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//TODO 自定義處理訊息【見3.2】
}
};
Looper.loop(); 【見2.2】
}
}複製程式碼
上面的程式碼大致上就相當於我們demo給出的例子的Looper缺失部分。一般如果我們在Activity建立前就會把Looper例項好,所以,我們日常使用Handler時,都沒有發覺到Looper的存在。MessageQueue這類我們放在後面再來跟大家講。我們先來看下這個段虛擬碼做了哪些操作。
二、Looper
2.1 prepare()
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
//quitAllowed 為true的話,在looper執行退出,為false的話,則是Looper不執行退出
private static void prepare(boolean quitAllowed) {
//每個執行緒只允許執行一次該方法,第二次執行時執行緒的TLS已有資料,會丟擲異常。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//給執行緒的TLS賦值
sThreadLocal.set(new Looper(quitAllowed));
}複製程式碼
這裡的sThreadLocal
其實是ThreadLocal型別
ThreadLocal 執行緒本地儲存區(Thread Local Storage ,簡稱TLS),每個執行緒都有自己的私有的本地儲存區域,不同執行緒之間彼此不能訪問對方的TLS區域。 下面來看下ThreadLocal的set
方法和get
方法。
public void set(T value) {
//獲取當前執行緒
Thread t = Thread.currentThread();
//獲取當前執行緒的TLS
ThreadLocalMap map = getMap(t);
if (map != null)
//重新賦值
map.set(this, value);
else
//儲存value到當前執行緒的this
createMap(t, value);
}複製程式碼
public T get() {
//獲取當前執行緒
Thread t = Thread.currentThread();
//獲取當前執行緒的TLS
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
//將儲存的值返回
return (T)e.value;
}
//不存在這個值,則重新例項ThreadLocalMap,並將this作為key,value為null
return setInitialValue();
}複製程式碼
這兩段程式碼都涉及ThreadLocalMap這個類,其實這個類是ThreadLocal的內部類。而這個類裡面又有內部類Entry,Entry就是作為儲存的型別。
ThreadLocal的get()和set()方法操作的型別都是泛型,接著回到前面提到的sThreadLocal變數,其定義如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>()
可見sThreadLocal的get()和set()操作的型別都是Looper型別。
我們現在再回到上面的prepare的方法,在呼叫set方法傳入的是Looper的構造方法。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //建立MessageQueue物件. 【見4.1】
mThread = Thread.currentThread(); //記錄當前執行緒.
}複製程式碼
Looper.prepare()方法到此結束,到這裡我們可以瞭解到,Looper到這裡就跟MessageQueue以及Thread建立關係。
2.2 loop
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//獲取當前執行緒的looper例項
final Looper me = myLooper();
//呼叫loop前一定要先呼叫prepare()方法,否則會報錯
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//將當前執行緒的Looper中的訊息佇列MessageQueue
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
//確保在許可權檢查時基於本地程式,而不是基於最初呼叫程式
final long ident = Binder.clearCallingIdentity();
for (;;) {//進入loope的主迴圈
//從訊息佇列取訊息
Message msg = queue.next(); // 可能會阻塞 【見4.1】
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);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
//呼叫Message自帶的Handler的dispatchMessage()進行分發訊息
msg.target.dispatchMessage(msg);//【見3.1】
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
// 確保分發過程中identity不會損壞
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//將Message放入訊息池
msg.recycleUnchecked();
}
}複製程式碼
到這裡主執行緒就進行死迴圈
,這個迴圈就是不停的取訊息。也就是說當一個Activity例項好,Looper這塊就已經是處於取訊息階段,只要有訊息進來,立馬觸發next取出訊息。而且這裡的訊息不單單侷限於我們自己建立的Handler傳送過來的訊息。其實整個Activity的生命週期也是通過Handler的訊息機制維護的,具體的可以去學下Activity的啟動原始碼。這裡有個問題,就是這個死迴圈執行在UI執行緒卻不會導致ANR的發生。是因為 Android應用程式的主執行緒在進入訊息迴圈過程前,會在內部建立一個Linux管道,這個管道的作用就是使得主執行緒在訊息佇列為空時可以進入空閒狀態,當有訊息來時,再喚醒主執行緒進行工作。 所以,也可以這麼說Handler的底層機制其實是 Linux的管道技術 ,正是由於這個技術,讓傳送訊息以及處理訊息能夠執行於在不同的執行緒上。
當取到訊息時,就會呼叫Message的Handler成員的dispatchMessage
方法進行分發訊息。
三、Handler
我們先從Handler的構造方法進行分析
// 一般情況我們都是呼叫無參的構造方法
public Handler(Callback callback, boolean async) {
//匿名類、內部類或本地類都必須申明為static,否則會警告可能出現記憶體洩露
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//獲取當前執行緒的Looper物件
//必須先執行Looper.prerare(),對能獲取Looper物件,否則會報錯
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//獲取Looper的訊息佇列
mQueue = mLooper.mQueue;
//回撥方法
mCallback = callback;
//設定訊息是否為非同步方式
mAsynchronous = async;
}複製程式碼
這裡可以看出,Handler的MessageQueue由Looper決定,並且Handler所在的執行緒也是由Looper決定的。
3.1 dispatchMessage
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
//msg的callback不為空,就回撥msg的callback方法
if (msg.callback != null) {
handleCallback(msg);
} else {
//handler本身的mCallback不為空,也即是有參建構函式裡面傳入
//回撥mCallbackd的handleMessage方法
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//前兩者不滿足情況下,呼叫handleMessage方法
handleMessage(msg);
}
}複製程式碼
從上面分發機制可以看出,Message本身的回撥優先順序是最高的,而Handler本身的方法級別是最低的。所以按照優先順序從高到低的排序:
1、Message的回撥方法:msg.callback.run()
2、Handler的回撥方法:Handler.mCallback.handleMessage(msg)
3、Handler的預設方法:Handler.handleMessage(msg)
我們平常使用最多的方法其實是Handler自身的HandleMessage方法,到這裡我們已經大致瞭解到從訊息佇列裡面取到訊息,並且將訊息分發到相應的Handler進行處理。這裡已經講完訊息機制的後半段,接著來分析訊息機制的前半段-傳送訊息到訊息佇列。
public final boolean sendMessage(Message msg)
{
//延時為0
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) {
//將當前的Handler賦值給Message
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);【見4.2】
}複製程式碼
這邊特意列出這麼多個方法,就是為了說明訊息的新增是MessageQueue的enqueueMessage
執行的。我們再來看看Handler其他一些常用的方法,是如何將訊息傳遞過去的。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}複製程式碼
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}複製程式碼
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();【見5.1】
m.callback = r;
return m;
}複製程式碼
從最後一個方法裡面可以看出,利用Handler的post、postDelayed方法,最終消費的是Message的回撥方法callback,這個按照3.1的分發機制可以得出。
我們再來看下我們的demo給出的方法又是怎麼實現的
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}複製程式碼
從這裡可以看出,post跟send方法的區別在於post方法需要傳入Runnable
,而send的不需要,最終導致訊息的消費位置不一樣。
四、MessageQueue
4.1 next
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
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();
}
//阻塞操作,當等待nextPollTimeoutMillis時長,或者訊息佇列被喚醒,都會返回
//native方法
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.
//當訊息Handler為空時,查詢MessageQueue中的下一條非同步訊息msg,則退出迴圈。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
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;
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.
//沒有idle handlers 需要執行,則迴圈並等待。
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.
//只有第一次迴圈時,會執行idle handlers,執行完成後,重置pendingIdleHandlerCount為0.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
//去掉handler的引用
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle(); //idle時執行的方法
} 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.
//重置idle handler個數為0,以保證不會再次重複執行
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.
//當呼叫一個空閒handler時,一個新message能夠被分發,因此無需等待可以直接查詢pending message.
nextPollTimeoutMillis = 0;
}
}複製程式碼
上面的程式碼主要的工作就是取出訊息,但是真正的工作是在Native層實現的,呼叫nativePollOnce方法,實現與Native層的通訊。這裡我們不對Native層的實現進行深究,有興趣的可自行查閱資料瞭解。後期我也會寫關於Native層的實現機制。
4.2 enqueueMessage
boolean enqueueMessage(Message msg, long when) {
//Message的target必須不能為空
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) {//正在退出時,回收msg,加入訊息池
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); 【見5.2】
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// p為null(代表MessageQueue沒有訊息)或者msg的觸發時間是佇列中最早的
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 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.
//將訊息按時間順序插入到MessageQueue。正常情況不需要喚醒,除非訊息佇列存在barrier,並且同時Message是佇列中最早的非同步訊息
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.
//訊息沒有退出,我們認為此時mPtr != 0
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}複製程式碼
看到這裡,我們可以瞭解到訊息的產生以及新增到佇列中去。回到上面寫的Demo,handler.sendMessage
到這裡就結束了,也就是說這個新建的執行緒到此就沒了。接下來就要喚醒主執行緒了,這個喚醒主要是喚醒【4.1】提及到的nativePollOnce
方法;當有訊息入隊之後,nativePollOnce方法就會被呼叫,然後就開始分發訊息了。那麼整個Handler的訊息機制就走完了。
五、Message
5.1 obtain
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
//訊息池數量減1
sPoolSize--;
return m;
}
}
return new Message();
}複製程式碼
這個方法是很多人都推薦呼叫的,而不是除錯Message的構造方法。因為這個是重複利用訊息池的空閒訊息,提高效能。
5.2 recycle
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}複製程式碼
5.3 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) {
//將沒用的Message回收到訊息池裡
next = sPool;
sPool = this;
sPoolSize++;
}
}複製程式碼
總結
本文從一開始構建迴圈體Looper、從MessageQueue取出訊息到Handler消費消費的過程,然後再對Handler傳送訊息,緊接著訊息入佇列這一過程的分析。
Looper在初始化的時候會跟當前所在的執行緒繫結,並且例項化訊息佇列MessageQueue。而Handler在例項化的時候,如果沒有指定looper的話,預設呼叫會從當前執行緒的TLS中取出Looper。所以例項Handler之前,必定是呼叫了Looper.prepare的。並且結合管道技術,實現一個執行緒負責取訊息,另外一個執行緒負責新增訊息。實現了執行緒間的通訊。
還是用一張圖來總結整個訊息機制吧。
![](https://i.iter01.com/images/0c5187eaacfa6350b07d4403799352704648c9d73d585c17ef7910ca5a819d3f.png)
1、Handler的sendMessage將Message送到MessageQueue中,此時觸發管道(Pipe)喚醒另外一端開始讀資料
2、MessageQueue的next返回Message,並由Message的Handler成員呼叫dispatchMessage將Message進行處理(消費)
3、Looper在呼叫loop後就一直處於等待狀態,一直到有Message到來,並將其喚醒,然後再將Message發出去