Android訊息機制1-Handler(Java層)
阿新 • • 發佈:2021-07-18
一、概述
在整個Android的原始碼世界裡,有兩大利劍,其一是Binder IPC機制,,另一個便是訊息機制(由Handler/Looper/MessageQueue等構成的)。
Android有大量的訊息驅動方式來進行互動,比如Android的四劍客Activity
,Service
,Broadcast
,ContentProvider
的啟動過程的互動,都離不開訊息機制,Android某種意義上也可以說成是一個以訊息驅動的系統。訊息機制涉及MessageQueue/Message/Looper/Handler這4個類。
1.1 模型
訊息機制主要包含:- Message:訊息分為硬體產生的訊息(如按鈕、觸控)和軟體生成的訊息;
- MessageQueue:訊息佇列的主要功能向訊息池投遞訊息(
MessageQueue.enqueueMessage
)和取走訊息池的訊息(MessageQueue.next
);
- Handler:訊息輔助類,主要功能向訊息池傳送各種訊息事件(
Handler.sendMessage
)和處理相應訊息事件(Handler.handleMessage
);
- Looper:不斷迴圈執行(
Looper.loop
),按分發機制將訊息分發給目標處理者。
1.2 架構圖
- Looper有一個MessageQueue訊息佇列;
- MessageQueue有一組待處理的Message;
- Message
- Handler中有Looper和MessageQueue。
1.3 典型例項
先展示一個典型的關於Handler/Looper的執行緒class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); //【見 2.1】 mHandler = new Handler() { //【見 3.1】 public void handleMessage(Message msg) { //TODO 定義訊息處理邏輯. 【見 3.2】 } }; Looper.loop(); //【見 2.2】 } }
二、Looper
2.1 prepare()
對於無參的情況,預設呼叫prepare(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"); } //建立Looper物件,並儲存到當前執行緒的TLS區域 sThreadLocal.set(new Looper(quitAllowed)); }
這裡的
sThreadLocal
是ThreadLocal型別,下面,先說說ThreadLocal。
ThreadLocal: 執行緒本地儲存區(Thread Local Storage,簡稱為TLS),每個執行緒都有自己的私有的本地儲存區域,不同執行緒之間彼此不能訪問對方的TLS區域。TLS常用的操作方法:
ThreadLocal.set(T value)
:將value儲存到當前執行緒的TLS區域,原始碼如下:
public void set(T value) { Thread currentThread = Thread.currentThread(); //獲取當前執行緒 Values values = values(currentThread); //查詢當前執行緒的本地儲存區 if (values == null) { //當執行緒本地儲存區,尚未儲存該執行緒相關資訊時,則建立Values物件 values = initializeValues(currentThread); } //儲存資料value到當前執行緒this values.put(this, value); }
ThreadLocal.get()
:獲取當前執行緒TLS區域的資料,原始碼如下:
public T get() { Thread currentThread = Thread.currentThread(); //獲取當前執行緒 Values values = values(currentThread); //查詢當前執行緒的本地儲存區 if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; //返回當前執行緒儲存區中的資料 } } else { //建立Values物件 values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); //從目標執行緒儲存區沒有查詢是則返回null }ThreadLocal的get()和set()方法操作的型別都是泛型,接著回到前面提到的
sThreadLocal
變數,其定義如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>()可見
sThreadLocal
的get()和set()操作的型別都是Looper
型別。
Looper.prepare()
Looper.prepare()在每個執行緒只允許執行一次,該方法會建立Looper物件,Looper的構造方法中會建立一個MessageQueue物件,再將Looper物件儲存到當前執行緒TLS。
對於Looper型別的構造方法如下:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); //建立MessageQueue物件. 【見4.1】 mThread = Thread.currentThread(); //記錄當前執行緒. }另外,與prepare()相近功能的,還有一個
prepareMainLooper()
方法,該方法主要在ActivityThread類中使用。
public static void prepareMainLooper() { prepare(false); //設定不允許退出的Looper synchronized (Looper.class) { //將當前的Looper儲存為主Looper,每個執行緒只允許執行一次。 if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
2.2 loop()
public static void loop() { final Looper me = myLooper(); //獲取TLS儲存的Looper物件 【見2.4】 final MessageQueue queue = me.mQueue; //獲取Looper物件中的訊息佇列 Binder.clearCallingIdentity(); //確保在許可權檢查時基於本地程序,而不是呼叫程序。 final long ident = Binder.clearCallingIdentity(); for (;;) { //進入loop的主迴圈方法 Message msg = queue.next(); //可能會阻塞 【見4.2】 if (msg == null) { //沒有訊息,則退出迴圈 return; } //預設為null,可通過setMessageLogging()方法來指定輸出,用於debug功能 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); //用於分發Message 【見3.2】 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } //恢復呼叫者資訊 final long newIdent = Binder.clearCallingIdentity(); msg.recycleUnchecked(); //將Message放入訊息池 【見5.2】 } }loop()進入迴圈模式,不斷重複下面的操作,直到沒有訊息時退出迴圈
- 讀取MessageQueue的下一條Message;
- 把Message分發給相應的target;
- 再把分發後的Message回收到訊息池,以便重複利用。
logging == null
,通過設定setMessageLogging()用來開啟debug工作。
2.3 quit()
public void quit() { mQueue.quit(false); //訊息移除 } public void quitSafely() { mQueue.quit(true); //安全地訊息移除 }Looper.quit()方法的實現最終呼叫的是MessageQueue.quit()方法 MessageQueue.quit()
void quit(boolean safe) { // 當mQuitAllowed為false,表示不執行退出,強行呼叫quit()會丟擲異常 if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { //防止多次執行退出操作 return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); //移除尚未觸發的所有訊息 } else { removeAllMessagesLocked(); //移除所有的訊息 } //mQuitting=false,那麼認定為 mPtr != 0 nativeWake(mPtr); } }訊息退出的方式:
- 當safe =true時,只移除尚未觸發的所有訊息,對於正在觸發的訊息並不移除;
- 當safe =flase時,移除所有的訊息
2.4 常用方法
2.4.1 myLooper
用於獲取TLS儲存的Looper物件public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
2.4.2 post
傳送訊息,並設定訊息的callback,用於處理訊息。public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
三、Handler
3.1 建立Handler
3.1.1 無參構造
public Handler() { this(null, false); } 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.prepare(),才能獲取Looper物件,否則為null. mLooper = Looper.myLooper(); //從當前執行緒的TLS中獲取Looper物件【見2.1】 if (mLooper == null) { throw new RuntimeException(""); } mQueue = mLooper.mQueue; //訊息佇列,來自Looper物件 mCallback = callback; //回撥方法 mAsynchronous = async; //設定訊息是否為非同步處理方式 }對於Handler的無參構造方法,預設採用當前執行緒TLS中的Looper物件,並且callback回撥方法為null,且訊息為同步處理方式。只要執行的Looper.prepare()方法,那麼便可以獲取有效的Looper物件。
3.1.2 有參構造
public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
Handler類在構造方法中,可指定Looper,Callback回撥方法以及訊息的處理方式(同步或非同步),對於無參的handler,預設是當前執行緒的Looper。
3.2 訊息分發機制
在Looper.loop()中,當發現有訊息時,呼叫訊息的目標handler,執行dispatchMessage()方法來分發訊息。public void dispatchMessage(Message msg) { if (msg.callback != null) { //當Message存在回撥方法,回撥msg.callback.run()方法; handleCallback(msg); } else { if (mCallback != null) { //當Handler存在Callback成員變數時,回撥方法handleMessage(); if (mCallback.handleMessage(msg)) { return; } } //Handler自身的回撥方法handleMessage() handleMessage(msg); } }分發訊息流程:
- 當
Message
的回撥方法不為空時,則回撥方法msg.callback.run()
,其中callBack資料型別為Runnable,否則進入步驟2; - 當
Handler
的mCallback
成員變數不為空時,則回撥方法mCallback.handleMessage(msg)
,否則進入步驟3; - 呼叫
Handler
自身的回撥方法handleMessage()
,該方法預設為空,Handler子類通過覆寫該方法來完成具體的邏輯。
3.3 訊息傳送
傳送訊息呼叫鏈: 從上圖,可以發現所有的發訊息方式,最終都是呼叫MessageQueue.enqueueMessage()
;
3.3.1 sendEmptyMessage
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); }
3.3.2 sendEmptyMessageDelayed
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
3.3.3 sendMessageDelayed
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
3.3.4 sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { return false; } return enqueueMessage(queue, msg, uptimeMillis); }
3.3.5 sendMessageAtFrontOfQueue
public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; if (queue == null) { return false; } return enqueueMessage(queue, msg, 0); }該方法通過設定訊息的觸發時間為0,從而使Message加入到訊息佇列的隊頭。
3.3.6 post
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
3.3.7 postAtFrontOfQueue
public final boolean postAtFrontOfQueue(Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); }
3.3.8 enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); 【見4.3】 }
小節
Handler.sendEmptyMessage()
等系列方法最終呼叫MessageQueue.enqueueMessage(msg, uptimeMillis)
,將訊息新增到訊息佇列中,其中uptimeMillis為系統當前的執行時間,不包括休眠時間。
3.4 Handler其他方法
3.4.1 obtainMessage
獲取訊息public final Message obtainMessage() { return Message.obtain(this); 【見5.2】 }
Handler.obtainMessage()
方法,最終呼叫Message.obtainMessage(this)
,其中this為當前的Handler物件。
3.4.2 removeMessages
public final void removeMessages(int what) { mQueue.removeMessages(this, what, null); 【見 4.5】 }
Handler
是訊息機制中非常重要的輔助類,更多的實現都是MessageQueue
, Message
中的方法,Handler的目的是為了更加方便的使用訊息機制。
四、MessageQueue
MessageQueue是訊息機制的Java層和C++層的連線紐帶,大部分核心方法都交給native層來處理,其中MessageQueue類中涉及的native方法如下:private native static long nativeInit(); private native static void nativeDestroy(long ptr); private native void nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr); private native static boolean nativeIsPolling(long ptr); private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
關於這些native方法的介紹,見Android訊息機制2-Handler(native篇)。
4.1 建立MessageQueue
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; //通過native方法初始化訊息佇列,其中mPtr是供native程式碼使用 mPtr = nativeInit(); }
4.2 next()
提取下一條messageMessage next() { final long ptr = mPtr; if (ptr == 0) { //當訊息迴圈已經退出,則直接返回 return null; } int pendingIdleHandlerCount = -1; // 迴圈迭代的首次為-1 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //阻塞操作,當等待nextPollTimeoutMillis時長,或者訊息佇列被喚醒,都會返回 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //當訊息的Handler為空時,則查詢非同步訊息 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; //設定訊息的使用狀態,即flags |= FLAG_IN_USE msg.markInUse(); return msg; //成功地獲取MessageQueue中的下一條即將要執行的訊息 } } else { //沒有訊息 nextPollTimeoutMillis = -1; } //訊息正在退出,返回null if (mQuitting) { dispose(); return null; } //當訊息佇列為空,或者是訊息佇列的第一個訊息時 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { //沒有idle handlers 需要執行,則迴圈並等待。 mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } //只有第一次迴圈時,會執行idle handlers,執行完成後,重置pendingIdleHandlerCount為0. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; //去掉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); } } } //重置idle handler個數為0,以保證不會再次重複執行 pendingIdleHandlerCount = 0; //當呼叫一個空閒handler時,一個新message能夠被分發,因此無需等待可以直接查詢pending message. nextPollTimeoutMillis = 0; } }
nativePollOnce
是阻塞操作,其中nextPollTimeoutMillis
代表下一個訊息到來前,還需要等待的時長;當nextPollTimeoutMillis = -1時,表示訊息佇列中無訊息,會一直等待下去。
當處於空閒時,往往會執行IdleHandler
中的方法。當nativePollOnce()返回後,next()從mMessages
中提取一個訊息。
nativePollOnce()
在native做了大量的工作,想進一步瞭解可檢視 Android訊息機制2-Handler(native篇)。
4.3 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,加入到訊息池 msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { //p為null(代表MessageQueue沒有訊息) 或者msg的觸發時間是佇列中最早的, 則進入該該分支 msg.next = p; mMessages = msg; needWake = mBlocked; //當阻塞時需要喚醒 } else { //將訊息按時間順序插入到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; prev.next = msg; } //訊息沒有退出,我們認為此時mPtr != 0 if (needWake) { nativeWake(mPtr); } } return true; }
MessageQueue
是按照Message觸發時間的先後順序排列的,隊頭的訊息是將要最早觸發的訊息。當有訊息需要加入訊息佇列時,會從佇列頭開始遍歷,直到找到訊息應該插入的合適位置,以保證所有訊息的時間順序。
4.4 removeMessages
void removeMessages(Handler h, int what, Object object) { if (h == null) { return; } synchronized (this) { Message p = mMessages; //從訊息佇列的頭部開始,移除所有符合條件的訊息 while (p != null && p.target == h && p.what == what && (object == null || p.obj == object)) { Message n = p.next; mMessages = n; p.recycleUnchecked(); p = n; } //移除剩餘的符合要求的訊息 while (p != null) { Message n = p.next; if (n != null) { if (n.target == h && n.what == what && (object == null || n.obj == object)) { Message nn = n.next; n.recycleUnchecked(); p.next = nn; continue; } } p = n; } } }
這個移除訊息的方法,採用了兩個while迴圈,第一個迴圈是從隊頭開始,移除符合條件的訊息,第二個迴圈是從頭部移除完連續的滿足條件的訊息之後,再從佇列後面繼續查詢是否有滿足條件的訊息需要被移除。
4.5 postSyncBarrier
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; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
前面小節[4.3]已說明每一個普通Message必須有一個target,對於特殊的message是沒有target,即同步barrier token。 這個訊息的價值就是用於攔截同步訊息,所以並不會喚醒Looper.
public void removeSyncBarrier(int token) { synchronized (this) { Message prev = null; Message p = mMessages; //從訊息佇列找到 target為空,並且token相等的Message while (p != null && (p.target != null || p.arg1 != token)) { prev = p; p = p.next; } final boolean needWake; if (prev != null) { prev.next = p.next; needWake = false; } else { mMessages = p.next; needWake = mMessages == null || mMessages.target != null; } p.recycleUnchecked(); if (needWake && !mQuitting) { nativeWake(mPtr); } } }
postSyncBarrier只對同步訊息產生影響,對於非同步訊息沒有任何差別。
五、 Message
5.1 訊息物件
每個訊息用Message
表示,Message
主要包含以下內容:
資料型別 | 成員變數 | 解釋 |
int | what | 訊息類別 |
long | when | 訊息觸發時間 |
int | arg1 | 引數1 |
int | arg2 | 引數2 |
Object | obj | 訊息內容 |
Handler | target | 訊息響應方 |
Runnable | callback | 回撥方法 |
5.2 訊息池
在程式碼中,可能經常看到recycle()方法,咋一看,可能是在做虛擬機器的gc()相關的工作,其實不然,這是用於把訊息加入到訊息池的作用。這樣的好處是,當訊息池不為空時,可以直接從訊息池中獲取Message物件,而不是直接建立,提高效率。 靜態變數sPool
的資料型別為Message,通過next成員變數,維護一個訊息池;靜態變數MAX_POOL_SIZE
代表訊息池的可用大小;訊息池的預設大小為50。
訊息池常用的操作方法是obtain()和recycle()。
5.2.1 obtain
從訊息池中獲取訊息public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; //從sPool中取出一個Message物件,並訊息連結串列斷開 m.flags = 0; // 清除in-use flag sPoolSize--; //訊息池的可用大小進行減1操作 return m; } } return new Message(); // 當訊息池為空時,直接建立Message物件 }
obtain(),從訊息池取Message,都是把訊息池表頭的Message取走,再把表頭指向next;
5.2.2 recycle
把不再使用的訊息加入訊息池public void recycle() { if (isInUse()) { //判斷訊息是否正在使用 if (gCheckRecycle) { //Android 5.0以後的版本預設為true,之前的版本預設為false. throw new IllegalStateException("This message cannot be recycled because it is still in use."); } return; } recycleUnchecked(); } //對於不再使用的訊息,加入到訊息池 void recycleUnchecked() { //將訊息標示位置為IN_USE,並清空訊息所有的引數。 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++; //訊息池的可用大小進行加1操作 } } }
recycle(),將Message加入到訊息池的過程,都是把Message加到連結串列的表頭;
六、總結
最後用一張圖,來表示整個訊息機制 圖解:- Handler通過sendMessage()傳送Message到MessageQueue佇列;
- Looper通過loop(),不斷提取出達到觸發條件的Message,並將Message交給target來處理;
- 經過dispatchMessage()後,交回給Handler的handleMessage()來進行相應地處理。
- 將Message加入MessageQueue時,處往管道寫入字元,可以會喚醒loop執行緒;如果MessageQueue中沒有Message,並處於Idle狀態,則會執行IdelHandler介面中的方法,往往用於做一些清理性地工作。
- Message的回撥方法:
message.callback.run()
,優先順序最高; - Handler的回撥方法:
Handler.mCallback.handleMessage(msg)
,優先順序僅次於1; - Handler的預設方法:
Handler.handleMessage(msg)
,優先順序最低。