上一章我們講解SystemServer時涉及到了消息機制,因此這一章我們先介紹一下消息循環機制,幫助大家弄清楚消息循環的原理,有助于代碼的編寫和優化。
Looper-Message-MessageQueue-Handler消息處理機制
在Android系統有兩個通信機制,一個是Binder,一個是消息機制,前者是跨進程通信,后者是進程內部通信。消息通信主要包括幾個部分:
- 消息發送者和處理者:Handler
- 消息循環器:Looper
- 消息隊列:MessageQueue
- 消息:Message
我們先看一個時序圖:
圖中,1-11步是Looper的準備過程,12-17步是獲取消息,處理消息,回收消息的循環過程。
下面是一張消息循環過程圖,圖片來自網絡博客(blog.mindorks.com),Looper會通過loop方法不斷從消息隊列去取消息,然后交給handler處理,處理完成就回收消息,要注意的是只有一個looper,但是可能有多個handler:
1、Looper
Looper是一個循環器,通過里面的loop方法不斷去取消息,發送給Handler進行處理。根據上面時序圖以及SystemServer啟動代碼我們開始分析Looper的調用過程:
private void run() {
try {
...
// 準備主線程的Looper
Looper.prepareMainLooper();
...
} finally {
...
}
...
// Loop(循環) forever.
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我們先看Looper.prepareMainLooper方法:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
上面有段注釋,我翻譯一下,就是:初始化當前線程作為一個looper,并把它標記為應用的主looper。這個looper是被Android環境(系統)創建的,因此你不需要自己調用這個方法。也就是系統創建了這個looper,你不需要再創建了。我們接著看里面的內容,首先調用了prepare方法,需要注意的是Looper.prepare()在每個線程只允許執行一次,該方法會創建Looper對象:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
...
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {// 確保ThreadLocal中只有一個Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
上面的ThreadLocal是聲明在類里的,并且是靜態的,因此,隨著類創建了該對象,get方法是獲取Looper的,如果能獲取到,則拋出異常,也就是確保當前線程只有一個Looper。如果是空,那么我們創建一個Looper放到里面去。
我們先看一下ThreadLocal:線程本地存儲區(Thread Local Storage,簡稱為TLS),每個線程都有自己的私有的本地存儲區域,不同線程之間彼此不能訪問對方的TLS區域。(來自Android消息機制1-Handler(Java層))我們看一下它的set和get方法:
ThreadLocal的set方法:
public void set(T value) {
//獲取當前線程
Thread t = Thread.currentThread();
// 獲取當前線程里的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
如果map不為空,則以鍵值對放入進行存儲,此處map不是HashMap,而是其他,這里不詳細解釋。如果map為空,則通過下面代碼創建map:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal的get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
我們看到獲取的時候也是根據當前線程去獲取的。因此每個線程會保存一個Looper。
我們接著看Looper的構造函數有哪些操作,也就是創建Looper做了哪些處理:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
首先是創建了MessageQueue對象,接著創建一個線程,也就是當前線程,currentThread是一個native方法,我們不再分析,我們看一下MessageQueue創建做了哪些事情:
MessageQueue(boolean quitAllowed) {
// 是否可以退出消息隊列
mQuitAllowed = quitAllowed;
// 返回底層的MessageQueue對象的內存地址,如果為空返回0
mPtr = nativeInit();
}
上面的nativeInit是調用的jni,我貼一下代碼,不再解釋:
我們回到prepareMainLooper方法接著看,如果sMainLooper不為null,則拋出異常,提示sMainLooper已經創建了,如果是null,那么調用myLooper方法回去sMainLooper:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
其實這個get方法就是我們上面new完Looper放進去的,到此prepareMainLooper就完成了,相關信息也準備好了。接下來就是調用Looper.loop方法,方法下面是一個異常,怎么樣才能保證異常不會拋出,就是loop方法永遠執行不完。是不是只有我們接著看代碼:
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) { // message為空為結束信號,退出循環
// No message indicates that the message queue is quitting.
return;
}
...
try {
// 將真正的處理工作交給message的target,即handler
msg.target.dispatchMessage(msg);
} finally {
...
}
...
// 回收Message
msg.recycleUnchecked();
}
}
首先是通過myLooper方法獲取Looper,如果為空,則拋出異常,提示還沒有調用Looper.prepare方法,如果不為空,則通過looper獲取MessageQueue對象,然后進入for循環,因為for語句中沒有條件,因此該for循環為無限循環,在這個循環中有三件事,一個是獲取消息隊列中的下一個消息,然后處理該消息,最近處理完消息,回收消息。這三個過程就是Looper的主要作用:取消息,處理消息,回收消息。
2.Message
Message是整個循環中信息的載體,它是一個鏈表結構,關于鏈表結構可以參考下面文章:
Android自助餐--Handler消息機制完全解析--系列
鏈表數據結構圖解 和 代碼實現
基本數據結構:鏈表(list)
鏈表結構之單鏈表
我們看個圖:
上面就是一個示例圖,每個Message中都有一個后面Message的引用next,鏈表最后一個next為空,sPool是第一個Message。但是每個Message的內存地址不是挨著的,這樣可以占用零碎的內存。
我們先來看Message包含的參數:
public int what;
public int arg1;
public int arg2;
public Object obj;
/*package*/ int flags;
/*package*/ long when;
/*package*/ Handler target;
// 消息隊列中下一個消息的引用
/*package*/ Message next;
// sPool這個變量可以理解為消息隊列的頭部的指針,也就是當前消息對象
private static Message sPool;
// sPoolSize是當前的消息隊列的長度
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
前四個參數很熟悉,不再解釋,flags是一個標簽,表示是否正在使用;when是處理消息的時間;target就是我們上面提到的Handler;next是下一個Message的引用;sPool是一個靜態變量,說明只有一個,其實這個是消息隊列的頭消息;sPoolSize是消息隊列中消息個數;MAX_POOL_SIZE是消息隊列最大消息數量。
Message中有多個用來獲取Message對象的obtain復寫方法。因為后面的obtain方法都是通過第一個obtain方法獲取Message對象的,因此我們只看第一個參數為空的方法:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
// 避免多線程進行爭搶資源,給sPoolSync進行加鎖
synchronized (sPoolSync) {
// 如果消息隊列的頭部不為空,則可以取出頭部重用
if (sPool != null) {
Message m = sPool;
// 頭部消息取出后,將sPool指向后面的消息對象
sPool = m.next;
// next(隊列尾部)設置為null
m.next = null;
m.flags = 0; // clear in-use flag
// 消息隊列長度減一
sPoolSize--;
return m;
}
}
// 如果消息隊列的頭部為空,則創建新的Message對象
return new Message();
}
系統提示盡量用這種方法獲取Message對象,避免創建大量新的對象,其實也可以通過Handler來獲取Message,這個我們在將Handler時候再講。
在上面Looper中我們講到最后消息處理完后需要回收,這個回收方法recycleUnchecked也在Message類中:
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
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;
// 避免多線程進行爭搶資源,給sPoolSync進行加鎖
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
// 回收當前消息后時,將sPool消息后移
next = sPool;
// 將當前消息放到頭部
sPool = this;
// 隊列長度加一
sPoolSize++;
}
}
}
消息回收時,將對應消息的標簽設置為使用中,其他標簽設置為空或者默認值,如果消息隊列沒有超過最大值,那么將sPool賦值給next,將這個Message賦值給sPool,消息隊列長度加一。也就是將處理完的消息清空,重新放回消息隊列等待使用。
3.Handler
Handler是發送消息和處理消息的工具。我們先看構造方法:
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;
}
Handler中的looper是獲取當前線程中的looper,looper不能為空,MessageQueue也是looper中的。
首先是發送消息,發送消息的方法很多,我們看一張圖:
我們看到Handler中有多個發送消息的方法,但是最終調用了enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
從代碼我們可以看到msg.target就是Handler,也就是在這里進行賦值的,然后是調用MQ(MessageQueue)的enqueueMessage方法,這個方法是添加消息隊列的,具體內容我們后面再講。因此,發送消息就是講消息添加到消息隊列。我們前面還講過獲取Message對象可以通過Message中的obtain方法,也可以通過Handler中的方法,我們先看一張圖:
Handler是通過多個復寫方法obtainMessage來獲取Message的,只是傳入參數不同,我們看一個沒有參數的方法代碼:
public final Message obtainMessage(){
return Message.obtain(this);
}
我們看到其實還是調用了Message.obtain方法,并且傳入了this,也就是Handler,通過Message.obtain方法將Handler賦值給Message中的target。從這,我們基本對Handler與Message的關系基本明確了,獲取Message的方法我們也完全知道了,因此我們在以后用的時候不需要再去new一個Message對象,而是通過obtain方法去獲取,如果有就不需要new了,如果沒有系統會自己去創建。
4.MessageQueue
MessageQueue是消息隊列,其實是管理消息鏈表的。它主要功能是取出消息--next方法,將消息加入隊列--enqueueMessage方法。
我們先看加入消息隊列方法enqueueMessage,也就是Handler中發送消息后加入隊列的方法:
boolean enqueueMessage(Message msg, long when) {
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) {
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;
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 {
// 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) {
nativeWake(mPtr);
}
}
return true;
}
插入消息隊列有兩種情況,一種是消息隊列處于空閑狀態,直接將消息放在消息隊列前面,可能需要喚醒主線程,另一種是消息隊列處于忙碌狀態,就不需要喚醒,而是根據消息處理時間將消息插入到消息隊列的對應位置中。
第一種狀態:插入隊列頭
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}
if語句的三個條件是:一、隊列為空,二、插入消息需要立即處理,三、插入消息處理時間比消息隊列頭消息早,這三個條件說明消息隊列處于閑置狀態,此時要把消息放置到消息隊列頭部,即將插入消息的next指向消息隊列的頭p,然后將消息隊列要處理的消息指向插入消息對象,最后判斷是否需要喚醒,如果隊列阻塞則需要喚醒,否則不需要。
第二種狀態:插入隊列中間或者后面,這種情況比較復雜
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;
因為不是在隊列頭,所以需要for循環去查找應該的位置,首先將第一個消息用prev進行緩存,然后當前消息引用指向下一個消息對象,依次類推,直到p == null(到隊列最后),或者當前消息觸發時間小于后面這個消息的觸發時間,停止循環,說明找到了位置,此時執行最后兩行代碼,也就是將當前出入消息的next指向p,也就是,如果p==null,則說明插入到最后一個,如果不為空,則插入到p前面,然后將前一個prev的next指向插入的消息,此時插入成功。最后的if語句中如果需要喚醒消息隊列,則調用底層方法nativeWake喚醒消息隊列開始循環。到此,消息插入就講完了。我們上面說到loop方法是通過MessageQueue的next方法取出消息,那么下面我們看一下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();
}
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;
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.
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.
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
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.
nextPollTimeoutMillis = 0;
}
}
mPtr是MessageQueue初始化的時候通過調用底層方法獲取的底層NativeMessageQueue的對象,如果底層不能初始化則返回0,如果可以初始化返回對象地址,此處判斷,如果沒有初始化也就沒有底層的NativeMessageQueue對象,因此返回null。緊接著開始for循環,開始遍歷消息隊列,查找需要處理的消息,在這里,如果消息隊列為空,或者沒有需要立即處理的消息都要使線程開始等待。接著調用nativePollOnce方法來查看當前隊列中有沒有消息,傳入參數nextPollTimeoutMillis表示要等待的時間,如果nextPollTimeoutMillis為0則說明不需要等待。接著獲取當前時間now,初始化prevMsg來緩存消息,初始化msg來緩存當前消息mMessages,下面if語句判斷消息不為空但target為空,則說明該消息為“同步分隔欄”(關于“同步分隔欄”請參看聊一聊Android的消息機制一文),如果該消息為同步分隔欄,則后面的同步消息都不會被查找,只能查找異步消息來處理,也就是do-while語句中的代碼,如果沒有同步分割欄或者找到了后面的異步消息(可能沒有),則接著判斷。
如果消息不為空,則還有消息,開始判斷時間,如果當前時間小于下一個消息的執行時間,說明還需要等待,那么計算需要等待的時間nextPollTimeoutMillis,如果當前時間不小于當前消息執行時間時,并且前一個消息prevMsg不為空,說明出現了“同步分隔欄”,也就是執行了do-while代碼,do-while執行完,說明找到了異步消息或者遍歷完整個隊列沒有異步消息,如果有異步消息,此時prevMsg.next = msg.next,也就是跳過同步消息,將異步消息msg.next賦值給prevMsg.next,然后將取出的msg的next賦值為null,因為要處理了,所以不再指向后面隊列的消息對象,然后將msg設置為正在使用,并且返回,如果prevMsg為空,則說明沒有出現“同步分隔欄”,此時將當前消息mMessages的下一個消息賦值給mMessages,然后將msg.next設置為空,就是不再引用,然后設置為正在使用,返回該消息。
如果消息為空,則nextPollTimeoutMillis = -1,說明沒有消息了,則接著向下執行,如果退出消息隊列,則說明所有消息都執行完了,最終調用nativeDestroy方法,如果不退出消息隊列,則要進入等待狀態。如果第一次進入,并且當前消息為空或者消息不為空,但是處于等待狀態,那么要獲取IdleHandler個數,如果小于等于0,則說明沒有IdleHandler運行,調用continue執行下一次循環,如果IdleHandler個數大于0,但是等待的Handler(mPendingIdleHandlers)為空,則要創建IdleHandler數組,將mIdleHandlers放入數據,然后for循環調用每個IdleHandler的queueIdle方法,如果這個方法返回false,則從數組移除這個對象,否則保留改對象,下次空閑繼續執行,最后將pendingIdleHandlerCount置為0,nextPollTimeoutMillis置為0,繼續下一次循環。
那么到此,整個循環就講完了,因為不懂C++代碼,所以底層沒法分析,只能分析framework層代碼,說了很多還是需要自己對比代碼多理解。
5.Handler的使用方法
我們在使用Handler的時候軟件會提示我們有問題,那么到底該怎么寫Handler呢,我從Stack Overflow找到了答案,在這就分享一下:
首先,定義一個靜態MxHandler繼承Handler,里面使用弱引用:
public abstract class MxHandler<T> extends Handler {
private WeakReference<T> weak;
public MxHandler(T t) {
this.weak = new WeakReference<T>(t);
}
@Override
public void handleMessage(Message msg) {
if (null == weak || null == weak.get()) {
return;
}
handleMessage(msg, weak);
super.handleMessage(msg);
}
protected abstract void handleMessage(Message msg, WeakReference<T> weak);
}
然后我們再寫具體的MyHandler繼承這個MxHandler:
private static final class MyHandler extends MxHandler<HandlerDemo> {
public MyHandler(HandlerDemo handlerDemo) {
super(handlerDemo);
}
@Override
protected void handleMessage(Message msg, WeakReference<HandlerDemo> weak) {
switch (msg.what) {
case 0:
HandlerDemo h = weak.get();
h.doSomething();
break;
default:
break;
}
}
}
這樣我們在Activity中使用是不會出現內存泄漏之類的錯誤。
參考:
android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
Android中Thread、Handler、Looper、MessageQueue的原理分析
Android 異步消息處理機制 讓你深入理解 Looper、Handler、Message三者關系
Android應用程序消息處理機制(Looper、Handler)分析
注
Android開發群:192508518
微信公眾賬號:Code-MX
注:本文原創,轉載請注明出處,多謝。