現如今,在android裡面的非同步通訊一般都是用RxJava來完成,這當然是很好的辦法,但是我覺得,有時候學習一下官方提供的解決方案,能讓你更好的理解android的機制,同時通過對官方解決方案的理解,也能促使你更好的理解其它的第三方框架
1、Handler與MessageQueue、Looper之間的關係
其實在平常的開發中,如果我們只需要用Handler來進行UI元件的狀態的更新的話,那麼我們可以不用管MessageQueue和Looper的,因為這兩個物件的例項在UI執行緒建立時在ActivityThread的main()方法中就已經被建立,並不需要我們來管,我們只需要操作Handler就可以,但是我們要探究在子執行緒中Hander呼叫sendMessage()或者post()方法之後為什麼操作就回到了Ui執行緒,就需要理解它們三者的關係了,下面是詳細的介紹
1、簡單而言,Looper的職責在於建立建立一個MessageQueue訊息迴圈物件,並且通過開啟一個無限迴圈,從MessageQueue訊息佇列中獲取訊息,然後傳遞給Handler物件,如果我們要利用三者從頭開始構建一個非同步通訊過程,那麼Looper的初始化將是第一步,下面通過原始碼進行分析:
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));
}
複製程式碼
Looper的構造方法是私有化的,要建立Looper物件例項,需要通過prepare()方法,從上面程式碼可以看出,首先會通過sThreadLocal.get()來獲取Looper物件例項,如果存在,就會丟擲異常,這代表一個執行緒中只能有一個Looper物件例項,其中sThreadLocal是一個ThreadLocal例項,用來儲存Looper物件,以執行緒為key,關於它的用法可查閱相關資料。接下來如果獲取的物件為null,則通過sThreadLocal.set(new Looper(quitAllowed))將建立的Looper物件儲存,下面分析Looper的構造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製程式碼
在Looper的構造方法中,主要操作就是建立一個MessageQueue物件,到這裡,訊息的迴圈佇列就被建立了,Looper物件也被建立了,接下來就是Looper物件怎麼來從MessageQueue佇列中獲取訊息呢?
public static void loop() {
//獲取當前執行緒的Looper物件
final Looper me = myLooper();
//判斷當前Looper物件是否存在,不存在則丟擲異常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//獲取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();
//無限迴圈,從MessageQueue中獲取訊息物件
for (;;) {
//從訊息佇列中獲取訊息物件
Message msg = queue.next(); // might block
//如果為null,則阻塞
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 {
//這裡的msg.target其實就是Handler的物件例項
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
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);
}
msg.recycleUnchecked();
}
}
複製程式碼
以上是Looper物件迴圈獲取訊息佇列中的訊息的方法,本人在程式碼中必要的地方已經做了註釋,應該可以理解,這個方法的主要作用就是構建一個無限迴圈,不斷從MessageQueue佇列中獲取訊息,然後將訊息物件通過dispatchMessage()方法傳遞給Handler物件,到現在,Looper已經開始迴圈從MessageQueue中獲取訊息物件了,也就是說,Looper例項和MessageQueue例項都已經建立,那麼接下來就需要建立Handler的例項了,建立Handler有兩種方式(其實是一樣的方式,只是在傳送訊息時呼叫的方法不同),程式碼如下:
//第一種
private Handler mHandler1 = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Message msg = Message.obtain();
msg.what = 0x111;
msg.obj = ...
mHandler1.sendMessage(msg);
//第二種
private Handler mHandler2 = new Handler();
mHandler2.post(new Runnable() {
@Override
public void run() {
}
});
複製程式碼
以上就是建立Handler物件以及傳送訊息的一般使用,我們進入Handler的原始碼看看在建立例項的過程中做了什麼
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
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物件
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
//獲取MessageQueue物件
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製程式碼
在建立Handler物件的過程中,主要是獲取了當前執行緒Looper物件以及MessageQueen物件,比較好理解,接下來就是第一種傳送訊息的過程分析:
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) {
//在Handler建立時獲取到的MessageQueen物件
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);
}
//最終呼叫MessageQueen中的enqueueMessage()方法,將訊息放進訊息隊//列
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
進過各種return,最終會呼叫到enqueueMessage()方法,在該方法裡,會將訊息,以及傳送時的時間傳遞給MessageQueen佇列,最終放進訊息佇列中。並且,會將自身賦值給msg.target屬性,前面提到的msg.target屬性就在這裡賦值的了。而第二種傳送訊息的方式如下:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
//到這裡,其實跟第一種方式就是一樣的了
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製程式碼
上面的程式碼中,會呼叫到sendMessageDelayed()方法,其實,到這裡,跟第一種方式就是一樣的了,最終呼叫的都是enqueueMessage()方法將訊息放進訊息佇列中,唯一不同的是,這個Message物件是通過getPostMessage(r)方法來構造的
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
複製程式碼
在該方法中,構造了一個Message訊息物件,並且將我們傳進去的Runnable例項賦值給Message物件中的callback屬性,因此,我們在使用post方式時,並不會建立新的執行緒。現在,已經知道了訊息的傳送過程,那麼接下來就看看在MessageQueue中,是怎麼把訊息放進訊息佇列的,程式碼如下:
boolean enqueueMessage(Message msg, long when) {
.........
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on adead 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 {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//迴圈查詢出合適的插入位置
for (;;) {
prev = p;
p = p.next;
//如果已經已經到佇列最後一個或者時間已經比p的時間少了,那麼代表找到了插入的位置,跳出迴圈
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;
}
複製程式碼
上面將訊息物件放進訊息佇列的方法,主要是根據時間的先後將訊息放進佇列中,這樣,傳送訊息的全過程已經分析完了,那麼接下來就是取訊息了,前面說到,Looper物件呼叫loop()方法後,會無限迴圈地從訊息佇列中獲取資料,通過MessageQueen物件中的next()方法來獲取訊息佇列中的訊息,下面分析該方法
Message next() {
....
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//這是一個native方法,實際作用就是通過Native層的MessageQueue阻塞nextPollTimeoutMillis毫秒的時間。若是nextPollTimeoutMillis為-1,這時候訊息佇列處於等待狀態
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//這個判斷是為了過濾不屬於該Handler處理的msg
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 {
//如果訊息佇列中沒有訊息,將nextPollTimeoutMillis設為-1,下次迴圈訊息佇列則處於等待狀態
nextPollTimeoutMillis = -1;
}
}
}
複製程式碼
以上的方法我們注意一下中文註釋的那些就差不多了,主要作用就是根據我們設定的時間取出訊息物件,到目前為止,已經從訊息佇列中獲取到訊息了,那麼就回到了Looper物件中的loop()方法,前面loop()方法中,當獲取到訊息物件之後最終會msg.target.dispatchMessage(msg)操作,將訊息物件傳遞給Handler物件中dispatchMessage()方法,而如果是在更新UI元件這一種的場景下,loop()方法的呼叫是在UI執行緒中的,那麼其實在這裡已經完成了執行緒的切換,回到了UI執行緒中,其它場景類似,所以當執行到Handler中的dispatchMessage()方法,那麼已經回到了主執行緒,下面分析dispatchMessage()方法
public void dispatchMessage(Message msg) {
//首先判斷Message物件的callback屬性是否為null,這個屬性的設定是在Handler呼叫post(Runnable r)方法的時候設定的,就是Runnable物件
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製程式碼
上面的方法會分兩種情況,如果是使用了Handler的post()方法,那麼代表著msg.callback將不會為null,因此就會呼叫handleCallback(msg)方法,在handleCallback(msg)方法中的操作是message.callback.run(),也就是呼叫了post(Runnable r)中的Runnable物件中的run()方法,而第二種使用最終呼叫handleMessage(msg)方法,但是handleMessage(msg)是一個空實現,所以就需要重寫該方法來進行具體的實現。到現在,整個的流程已經分析完畢了
總結
Looper物件和MessageQueue物件在一個執行緒中只能有一個,而Handler物件則可以是多個,一個Looper物件和MessageQueue物件可以被多個Handler繫結,它們之間的關係大概可以理解為:Looper負責訊息佇列的建立,從訊息佇列中獲取訊息,MessageQueue負責儲存訊息物件,Handler負責管理訊息的傳送和收到訊息後的邏輯處理,以上是複習時的一點理解,如有錯誤,還望指出,請多包涵