工作中,我們可能直接使用我們需要的工具方法,但是不曾瞭解其中的原理內涵,這樣並不能很好的讓我們理解其運行機制,在複雜項目和疑難問題時無從入手。作爲開發想要提高並設計架構,一是要先學習優秀的設計理念,再就是了解其內部原理,爲自己在複雜使用場景和發生疑難問題時能夠透過表象看到本質。
Handler,Message,looper 和 MessageQueue 構成了安卓的消息機制,handler創建後可以通過 sendMessage 將消息加入消息隊列,然後 looper不斷的將消息從 MessageQueue 中取出來,回調到 Hander 的 handleMessage方法,從而實現線程的通信。
從兩種情況來說,第一在UI線程創建Handler,此時我們不需要手動開啓looper,因爲在應用啓動時,在ActivityThread的main方法中就創建了一個當前主線程的looper,並開啓了消息隊列,消息隊列是一個無限循環,爲什麼無限循環不會ANR?因爲可以說,應用的整個生命週期就是運行在這個消息循環中的,安卓是由事件驅動的,Looper.loop不斷的接收處理事件,每一個點擊觸摸或者Activity每一個生命週期都是在Looper.loop的控制之下的,looper.loop一旦結束,應用程序的生命週期也就結束了。我們可以想想什麼情況下會發生ANR,第一,事件沒有得到處理,第二,事件正在處理,但是沒有及時完成,而對事件進行處理的就是looper,所以只能說事件的處理如果阻塞會導致ANR,而不能說looper的無限循環會ANR
另一種情況就是在子線程創建Handler,此時由於這個線程中沒有默認開啓的消息隊列,所以我們需要手動調用looper.prepare(),並通過looper.loop開啓消息
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是爲了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環並不會對CPU性能有過多的消耗。
Handler源碼部分解讀:
初級程序員使用handler時有時會出現異常。Can’t create handler inside thread that has not called Looper.prepare()
爲什麼呢。
在core/java/android/os/Handler.java 中
@hide
*/
public Handler(@Nullable 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());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在其初始化的結構體中,獲取mLooper = Looper.myLooper();通過檢測mLooper是否爲空,爲空則報上述異常。如果不爲空, 再通過mQueue = mLooper.mQueue;獲取消息隊列,可以看到messageQuene對象是在Looper中獲取的。
再往下看,Handler中提供獲取主線程Handler的方法。
/** @hide */
@UnsupportedAppUsage
@NonNull
public static Handler getMain() {
if (MAIN_THREAD_HANDLER == null) {
MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
}
return MAIN_THREAD_HANDLER;
}
之後,我們我們發現了我們常用的obtainMessage方法。爲何建議使用obtainMessage而不是new Message()呢,從obtainMessage的註釋中也可以看出來,其會從一個全局message pool中返回一個新的message,比新建一個實例更有效率。另外的比如obtainMessage(int what)和obtainMessage(int what, @Nullable Object obj)則同步設置了message.what和obj消息。
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
@NonNull
public final Message obtainMessage()
{
return Message.obtain(this);
}
然後是各種發送消息的方法,包括髮送message消息的和post(runnable)的方法。可以看到無論是send還是post還是帶時間參數的,最終都走到了queue.enqueueMessage。這個messagequeue在下邊的小節會解讀。
Looper源碼解讀:
在Handler源碼中我們看到其Looper是用Looper.myLooper獲取的,這裏looper相當於消息的傳送帶,不斷的循環檢測是否有消息發過來。
在Looper類文件的開頭我們就看到了谷歌給我們的註釋和一個例子,清楚的說明了普通現場默認是沒有looper的,需要手動創建,然後創建的方法就是Looper.prepare(),然後下面的例子也清楚的解釋了上節中報錯信息如何防止,Can’t create handler inside thread that has not called Looper.prepare()。然後啓動looper循環用Looper.loop(),以啓動消息循環,直到loop被停止。
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
*
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
然後prepare的方法定義,可以看到sThreadLocal.get() != null的判斷,這裏可以看出,一個線程只能創建一個looper,但是如何保證的呢,代碼如何實現一個線程一個looper的呢。這裏就需要看代碼中的sThreadLocal的get和set方法了。
/** 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);
}
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,當然會走到set那,然後看變量初始化
ThreadLocal sThreadLocal = new ThreadLocal()
通過網絡查詢我們知道,ThreadLocal類提供了線程局部 (thread-local) 變量,多線程數據不能共享。
而其set方法源碼爲
public void set(T value) {
//獲取當前線程
Thread t = Thread.currentThread();
//在ThreadLocal 中,以t來獲取當前線程的一個ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果ThreadLocal中有這個map,就把當前類和對應的值傳進去
if (map != null)
map.set(this, value);
//如果沒有這個map,就新建一個t線程的ThreadLocalMap,並將value值傳進去
else
createMap(t, value);
}
可見,是用hashmap類似的結構存儲了當前線程id,和Looper的對應關係,保證一個線程一個looper, 關於ThreadLocal還有很多知識點,這裏我們只簡單帶過,之後有時間新開一章詳細解讀。
然後根據上述threadlocal的set方法,這裏獲取這個map中此線程對應的唯一looper,這裏就是Handler中獲取looper的實現,保證了唯一性。
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
之後看Looper.loop的代碼,這個是消息傳送帶的動力來源,如果不調用這個方法,消息是沒法獲取到的。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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) {
// No message indicates that the message queue is quitting.
return;
}
........
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
.........
從代碼中可以看到,一個for(; ; ),代表開始了一個無線循環,然後從queue.next中獲取消息,如果爲空則返回。另注意queue.next後註釋了 might block, 這裏是不是類似socket方法中的read,讀不到消息就阻塞在這,這個在下一節中尋找答案。之後我們發現msg.target.dispatchMessage(msg);通過msg對象中的target,分發了消息出去。
最後是looper的quit方法和quitSafely方法,二者區別是,前者立即停止當前消息隊列中消息的處理,後者是待現存消息隊列中的消息處理完後再推出消息循環。
然後懷着激動的心情,我們來看messageQueue的源碼。
MessageQueue源碼解讀
首先把類的註釋貼下,谷歌的註釋最能完整體現這個類的意義了。
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
* <p>You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue {
看源碼的開始部分,沒那麼好看,很多和native code交互的方法和變量,
@UnsupportedAppUsage
Message mMessages;
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
.....
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
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);
這裏我們應該可以不用去太詳細瞭解其內涵,我們需要尋找上述小節中調用到的messagequeue的方法。
@UnsupportedAppUsage
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;
}
...........
}
}
首先,在其 synchronized (this) 代碼塊中,保證messagequeue的隊列不被多線程更改。然後獲取下一條消息時,先獲取當前時間final long now = SystemClock.uptimeMillis();我們可以想到前邊handler中各種sendattime或者postdelay等消息也帶着時間參數。
這裏看到檢測如果msg不爲空,並且msg.target也不爲空,這裏又出現了target變量,什麼東西,我們繼續看。
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());
}
一個do-while句式不斷的尋找下一個消息msg.next,這裏可以看出這是隊列或鏈表的獲取下一個的方法。註釋顯示,尋找下一個異步方法。如果msg.isAsynchronous()爲真則退出循環。
之後就是now 和msg.when的比較,如果時間未到,則設一個超時時間。如果msg可以被處理,然後
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
這裏又可以看出來,如果prevMsg不爲空,就把下一個的指針指向當前處理msg的下一個消息,否則,這個類的mMessage直接設置爲msg.next.可以看出這裏的意思是在隊列中刪除這個msg,相當於出隊列。然後這個出隊的msg就不需要next節點信息了,置空,然後msg.markInUse,字面意思是標記在使用,至此,next()方法返回了消息。
之後是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;
}
一開始又檢測了msg.target爲空,會報異常,又一次出現target,一會兒一定要找下是幹啥的。
第二句就是 if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
有些同學應該出現過這個異常,即在handleMessage時,收到的消息再次發送,會報這個錯誤,就是因爲這個標記。
然後在類的同步塊中,檢測是否當前線程looper在quit狀態中,是的話則報異常並回收msg。
之後msg.when = when, 發送消息時傳過來的時間參數放到了msg裏。
然後如果當前mMessages變量爲空或者延時時間爲0,或者新插入的msg消息的延時時間小於當前消息的時間,則把新來的消息插入到當前消息的前面作爲隊列頭部。
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
否則,不斷的循環在隊列中查找延時時間大於新插入消息延時時間的節點。然後插入到隊列,這裏可以看出,此隊列是按時間順序排列的隊列,時間最短的在隊列最底部,時間長的在尾部,然後先進先出,即延時最少的消息先處理。
prev即
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;
如圖所示,原P指針在最下方,現由於判斷條件新加入消息的when爲時間2,由prev保存原p節點,p節點= p.next, 然後找到msg2消息的延時時間小於Msg3,則插入到當前位置。
Message類源碼
這裏看出Message類繼承了Parcelable方法,是android推薦的序列化方法,用在跨進程傳輸數據上,常見的又aidl中的序列化。而這裏message需要不同的線程間切換,打到從一個線程傳到另一個線程處理的目的。
*
* Defines a message containing a description and arbitrary data object that can be
* sent to a {@link Handler}. This object contains two extra int fields and an
* extra object field that allow you to not do allocations in many cases.
*
* <p class="note">While the constructor of Message is public, the best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
* them from a pool of recycled objects.</p>
*/
public final class Message implements Parcelable
然後我們發現了令我們疑惑的
Handler target;
然後我們尋找Handler中的代碼,發現所有的obtainMessage中已經將Handler.this參數傳到Message, 如果不是obtainMessage,在最終發送消息時,即如下的enqueueMessage,也已經將msg.target 賦值this,即當前Handler。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
其實Handler的整個源碼還有待新的解讀,但是目前我們能瞭解這些已經算是足夠用了,下次有疑問的時候可以自己翻看源碼,這樣在我們以後的工作中,如果遇到什麼異常或者其他相關問題,能夠從原理上說出這種問題的原因,對我們的工作和個人水平會有很大的提高。