關於做者java
郭孝星,程序員,吉他手,主要從事Android平臺基礎架構方面的工做,歡迎交流技術方面的問題,能夠去個人Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。android
文章目錄git
第一次閱覽本系列文章,請參見導讀,更多文章請參見文章目錄。程序員
Android是一個消息驅動型的系統,消息機制在Android系統中扮演者重要的角色,與之相關的Handler也是我平常中經常使用的工具。今天咱們就來聊一聊這個。github
Android消息循環流程圖以下所示:數組
主要涉及的角色以下所示:緩存
整個消息的循環流程仍是比較清晰的,具體說來:架構
事實上,在整個消息循環的流程中,並不僅有Java層參與,不少重要的工做都是在C++層來完成的。咱們來看下這些類的調用關係。框架
注:虛線表示關聯關係,實線表示調用關係。less
在這些類中MessageQueue是Java層與C++層維繫的橋樑,MessageQueue與Looper相關功能都經過MessageQueue的Native方法來完成,而其餘虛線鏈接的類只有關聯關係,並無 直接調用的關係,它們發生關聯的橋樑是MessageQueue。
有了上面這些分析,相信咱們對Android的消息機制有了大體的理解,對於這套機制,咱們很天然會去思考三個方面的問題:
咱們一一來看一下。
消息隊列是由MessageQueue類來描述的,MessageQueue是Android消息機制Java層和C++層的紐帶,其中不少核心方法都交由native方法實現。
既然提到對象構建,咱們先來看看它的構造函數。
public final class MessageQueue {
private long mPtr; // used by native code
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
}
複製代碼
能夠看到它調用的是native方法來完成初始化,這個方法定義在了一個android_os_MessageQueue的C++類類。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
//構建NativeMessageQueue對象
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
//將nativeMessageQueue對象的地址值轉成long型返回該Java層
return reinterpret_cast<jlong>(nativeMessageQueue);
}
複製代碼
能夠看到該方法構建了一個NativeMessageQueue對象,並將NativeMessageQueue對象的地址值轉成long型返回給Java層,這裏咱們知道其實是mPtr持有了這個 地址值。
NativeMessageQueue繼承域MessageQueue.cpp類,咱們來看看NativeMessageQueue的構造方法。
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
//先檢查是否已經爲當前線程建立過一個Looper對象
mLooper = Looper::getForThread();
if (mLooper == NULL) {
//建立Looper對象
mLooper = new Looper(false);
//爲當前線程設置Looper對象
Looper::setForThread(mLooper);
}
}
複製代碼
能夠看到NativeMessageQueue構造方法先檢查是否已經爲當前線程建立過一個Looper對象,若是沒有,則建立Looper對象併爲當前線程設置Looper對象。
咱們再來看看Looper的構造方法。
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
//建立管道
int result = pipe(wakeFds);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
//讀端文件描述符
mWakeReadPipeFd = wakeFds[0];
//寫端文件描述符
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
//建立一個epoll實例,並將它的文件描述符保存在變量mEpollFd中
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeReadPipeFd;
//將前面建立的管道讀端描述符添加到這個epoll實例中,以便它能夠對管道的寫操做進行監聽
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
複製代碼
這裏面提到兩個概念:管道與epoll機制。
關於管道
管道在本質上也是文件,但它不是普通的文件,它不屬於任何文件類型,並且它只存在與內存之中且有固定大小的緩存區,通常爲1頁即4kb。它分爲讀端和寫端,讀端負責從 管道讀取數據,當數據爲空時則阻塞,寫端負責向管道寫數據,當管道緩存區滿時則阻塞。那管道在線程通訊中主要用來通知另外一個線程。例如:線程A準備好了Message放入 了消息隊列,這個時候須要通知線程B去處理,這個時候線程A就像管道的寫端寫入數據,管道有了數據以後就回去喚醒線程B區處理消息。也正是基於管道來進行線程的休眠與 喚醒,才保住了線程中的loop循環不會讓線程卡死。
關於epoll機制
epoll機制用來監聽多個文件描述符的IO讀寫事件,在Android的消息機制用來監聽管道的讀寫IO事件。
關於epool機制,這裏有個簡單易懂的解釋。
epoll一共有三個操做方法:
上面Looper的構造方法裏,咱們已經看到了利用epoll_create()建立一個epoll的實例,並利用epoll_ctl()將管道的讀端描述符操做符添加到epoll實例中,以即可以對管道的 寫操做進行監聽,下面咱們還能夠看到epoll_wait()的用法。
講到這裏整個消息隊列便建立完成了,下面咱們接着來看看消息循環和如何開啓的。
消息循環是創建在Looper之上的,Looper能夠爲線程添加一個消息循環的功能,具體說來,爲了給線程添加一個消息循環,咱們一般會這麼作:
public class LooperThread extends Thread {
public Handler mHandler;
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
複製代碼
能夠看到先調用Looper.prepare()初始化一個Looper,而後調用Looper.loop()開啓循環。
關於Looper,有兩個方法來初始化prepare()/prepareMainLooper(),它們建立的Looper對象都是同樣,只是prepareMainLooper() 建立的Looper是給Android主線程用的,它仍是個靜態對象,以便其餘線程均可以獲取到它,從而能夠向主線程發送消息。
public final class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
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,應用啓動的時候會右系統調用,咱們通常不須要調用這個方法。
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
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
複製代碼
指的一提的是這裏使用的是ThreadLocal來存儲新建立的Looper對象。
ThreadLocal描述的是線程本地存儲區,不一樣的線程不能訪問對方的線程本地存儲區,當前線程能夠對本身的線程本地存儲區進行獨立的修改和讀取。
之因此會採用ThreadLocal來存儲Looper,是由於每一個具有消息循環能力的線程都有本身獨立的Looper,它們彼此獨立,因此須要用線程本地存儲區來存儲Looper。
咱們在接着來看看Looper的構造函數,以下所示:
public final class Looper {
private Looper(boolean quitAllowed) {
//建立消息隊列
mQueue = new MessageQueue(quitAllowed);
//指向當前線程
mThread = Thread.currentThread();
}
}
複製代碼
Looper的構造函數也很簡單,構造了一個消息隊列MessageQueue,並將成員變量mThread指向當前線程,這裏構建了一個MessageQueue對象,在MessageQueue構建 的過程當中會在C++層構建Looper對象,這個咱們上面已經說過。
Looper對象建立完成後就能夠開啓消息循環了,這是由loop()方法來完成的。
public final class Looper {
public static void loop() {
//獲取當前線程的Looper
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;
//確保當前線程處於本地進程中,Handler僅限於處於同一進程間的不一樣線程的通訊。
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//進入loop主循環方法
for (;;) {
//不斷的獲取下一條消息,這個方法可能會被阻塞
Message msg = queue.next();
if (msg == null) {
//若是沒有消息須要處理,則退出當前循環。
return;
}
// 默認爲null,可經過setMessageLogging來指定輸出,用於debug
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.dispatchMessage(msg);
} 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.
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();
}
}
}
複製代碼
能夠看到,這個方法不斷重複着如下三件事:
如此消息循環便創建起來了。
在如此開始中,咱們一般會調用handler的sendXXX()或者postXXX()將一個Message或者Runnable,這些方法實際上調用的MessageQueue的enqueueMessage()方法,該方法 會給目標線程的消息隊列插入一條消息。
注:如何理解這個"目標線程的消息隊列",首先要明白Handler、Looper與MessageQueue這三兄弟是全家桶,綁在一塊兒的,你用哪一個Handler,消息就被插入到了這個Handler所在線程 的消息隊列裏。
public final class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
//每一個消息都必須有個target handler
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) {
//正在退出時,回收Message,加入消息池。
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;
//mMessages表示當前須要處理的消息,也就是消息隊列頭的消息
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;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 喚醒消息隊列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
}
複製代碼
enqueueMessage()以時間爲序將消息插入到消息隊列中去,如下三種狀況下須要插入到隊列頭部:
上面三種狀況很容易想到,其餘狀況以時間爲序插入到隊列中間。當有新的消息插入到消息隊列頭時,當前線程就須要去喚醒目標線程(若是它已經睡眠(mBlocked = true)就執行喚醒操做,不然不須要),以便 它能夠來處理新插入消息頭的消息。
經過這裏的分析,你能夠發現,消息隊列事實上是基於單向鏈表來實現的,雖然咱們總稱呼它爲"隊列",但它並非一個隊列(不知足先進先出)。
一樣利用單向鏈表這種思路的還有對象池,讀者應該有印象,不少文檔都提倡經過Message.obtain()方法獲取一個Message對象,這是由於Message對象會被緩存在消息池中,它主要利用 Message的recycle()/obtain()方法進行緩存和獲取。
具體說來:
recycle()
public final class Message implements Parcelable {
private static final Object sPoolSync = new Object();
private static Message sPool;
public void recycle() {
//判斷消息是否正在使用
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
//對於再也不使用的消息加入消息池
recycleUnchecked();
}
void recycleUnchecked() {
//將消息標記爲FLAG_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) {
//將sPool存放在next變量中
next = sPool;
//sPool引用當前對象
sPool = this;
//消息池數量自增1
sPoolSize++;
}
}
}
}
複製代碼
obtain()
public final class Message implements Parcelable {
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
//sPool當前持有的消息對象將做爲結果返回
Message m = sPool;
//將m的後繼從新賦值給sPool,這其實一個鏈表的刪除操做
sPool = m.next;
//m的後繼置爲空
m.next = null;
//清除 in-use 標誌位
m.flags = 0;
//消息池大小自減1
sPoolSize--;
return m;
}
}
//當對象池爲空時,直接建立新的Message()對象。
return new Message();
}
}
複製代碼
這裏面有個巧妙的設計,這也給咱們如何設計一個對象池提供了一個很好的思路,它是以單向鏈表具體說來:
好了。消息池就聊這麼多,咱們接着來看消息的分發和處理。
消息的分發是創建在消息循環之上的,在不斷的循環中拉取隊列裏的消息,消息循環的創建流程咱們上面已經分析過,經過分析得知,loop()方法 不斷調用MessageQueue的next()讀取消息隊列裏的消息,從而進行消息的分發。
咱們來看看next()方法的實現。
public final class MessageQueue {
Message next() {
final long ptr = mPtr;
//當前消息循環已退出,直接返回。
if (ptr == 0) {
return null;
}
//pendingIdleHandlerCount保存的是註冊到消息隊列中空閒Handler個個數
int pendingIdleHandlerCount = -1;
//nextPollTimeoutMillisb表示當前無消息到來時,當前線程須要進入睡眠狀態的
//時間,0表示不進入睡眠狀態,-1表示進入無限等待的睡眠狀態,直到有人將它喚醒
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//nativePollOnce是阻塞操做,用來檢測當前線程的消息隊列中是否有消息須要處理
nativePollOnce(ptr, nextPollTimeoutMillis);
//查詢下一條須要執行的消息
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//mMessages表明了當前線程須要處理的消息
Message msg = mMessages;
//查詢第一個能夠處理的消息(msg.target == null表示沒有處理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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//設置消息的使用狀態,即flags |= FLAG_IN_USE。
msg.markInUse();
return msg;
}
} else {
// 若是沒有更多消息須要處理,則將nextPollTimeoutMillis置爲-1,讓當前線程進入無限睡眠狀態,直到
//被其餘線程喚醒。
nextPollTimeoutMillis = -1;
}
//全部消息都已經被處理,準備退出。
if (mQuitting) {
dispose();
return null;
}
//pendingIdleHandlerCount指的是等待執行的Handler的數量,mIdleHandlers是一個空閒Handler列表
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//當沒有空閒的Handler須要執行時進入阻塞狀態,mBlocked設置爲true
mBlocked = true;
continue;
}
//mPendingIdleHandler是一個IdleHandler數組
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];
//釋放Handler的引用
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//執行完成後,重置pendingIdleHandlerCount爲0,以保證不會再次重複運行。
pendingIdleHandlerCount = 0;
//當調用一個空閒Handler時,新的消息能夠當即被分發,所以無需再設置超時時間。
nextPollTimeoutMillis = 0;
}
}
}
複製代碼
首先要明確一個概念,MessageQueue是利用對象間的後繼關聯(每一個對象都知道本身的直接後繼)實現的鏈表,其中它的成員變量mMessages變量指的是當前須要被處理消息。
next()方法主要用來從消息隊列裏循環獲取消息,這分爲兩步:
這個查詢當前須要處理的消息能夠分爲三步:
能夠看到這裏調用的是native方法nativePollOnce()來檢查當前線程是否有消息須要處理,調用該方法時,線程有可能進入睡眠狀態,具體由nextPollTimeoutMillis參數決定。0表示不進入睡眠狀態,-1表示 進入無限等待的睡眠狀態,直到有人將它喚醒。
咱們接着來看看nativePollOnce()方法的實現。
nativePollOnce()方法是個native方法,它按照調用鏈:android_os_MessageQueue#nativePollOnce() -> NativeMessageQueue#pollOnce() -> Looper#pollOnce() -> Looper#pollInner() 最終完成了消息的拉取。可見實現功能的仍是在Looper.cpp裏。
咱們來看一下實現。
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
//內部不斷調用pollInner()方法檢查是否有新消息須要處理
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//調用epoll_wait()函數監聽前面註冊在mEpollFd實例裏的管道文件描述符中的讀寫事件。若是這些管道
//文件描述符沒有發生讀寫事件,則當前線程會在epoll_wait()方法裏進入睡眠,睡眠事件由timeoutMillis決定
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
//epoll_wait返回後,檢查哪個管道文件描述符發生了讀寫事件
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
//若是fd是當前線程關聯的管道讀端描述符且讀寫事件類型是EPOLLIN
//就說明當前線程關聯的一個管道寫入了新的數據,這個時候就會調用
//awoken()去喚醒線程
if (fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
//此時已經喚醒線程,讀取清空管道數據
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
}
}
...
}
...
return result;
}
複製代碼
能夠看到這個方法作了兩件事情:
至此,消息完成了分發。從上面的loop()方法咱們能夠知道,消息完成分發後會接着調用Handler的dispatchMessage()方法來處理消息。
咱們接着來聊一聊Handler。
Handler主要用來發送和處理消息,它會和本身的Thread以及MessageQueue相關聯,當建立一個Hanlder時,它就會被綁定到建立它的線程上,它會向 這個線程的消息隊列分發Message和Runnable。
通常說來,Handler主要有兩個用途:
咱們先來看看Handler的構造函數。
public class Handler {
//無參構造方法,最經常使用。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, 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
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//獲取當前線程的消息隊列
mQueue = mLooper.mQueue;
//回調方法,這個Callback裏面其實只有個handleMessage()方法,咱們實現這個
//接口,就不用去用匿名內部類那樣的方式來建立Handler了。
mCallback = callback;
//設置消息是否爲異步處理方式
mAsynchronous = async;
}
}
複製代碼
對於構造方法而言,咱們最經常使用的是無參構造方法,它沒有Callback回調,且消息處理方式爲同步處理,從這裏咱們也能夠看出你在哪一個線程裏建立了Handler,就默認使用當前線程的Looper。
從上面的loop()方法中,咱們知道Looper會調用MessageQueue的dispatchMessage()方法進行消息的處理,咱們來看看這個方法的實現。
public class Handler {
public void dispatchMessage(Message msg) {
//當Message存在回調方法時,優先調用Message的回調方法message.callback.run()
if (msg.callback != null) {
//實際調用的就是message.callback.run();
handleCallback(msg);
} else {
//若是咱們設置了Callback回調,優先調用Callback回調。
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//若是咱們沒有設置了Callback回調,則回調自身的Callback方法。
handleMessage(msg);
}
}
}
複製代碼
能夠看到整個消息分發流程以下所示:
由此咱們也能夠得知方法調用的優先級,從高到低依次爲:
大部分代碼都是以匿名內部類的形式實現了Handler,因此通常會走到第三個流程。
能夠看到因此發送消息的方法最終都是調用MessageQueue的enqueueMessage()方法來實現,這個咱們上面在分析MessageQueue的時候已經說過,這裏就再也不贅述。
理解了上面的內容,相信讀者已經對Android的消息機制有了大體的瞭解,咱們趁熱打鐵來聊一聊實際業務開發中遇到的一些問題。
在平常的開發中,咱們一般在子線程中執行耗時任務,主線程更新UI,更新的手段也多種多樣,如Activity#runOnUIThread()、View#post()等等,它們之間有何區別呢?若是個人代碼了 既沒有Activity也沒有View,我該如何將代碼切換回主線程呢?🤔
咱們一一來分析。
首先,Activity裏的Handler直接調用的就是默認的無參構造方法。能夠看到在上面的構造方法裏調用Looper.myLooper()去獲取當前線程的Looper,對於Activity而言當前線程就是主線程(UI線程),那主線程 的Looper是何時建立的呢?🤔
在03Android組件框架:Android視圖容器Activity一文 裏咱們就分析過,ActivityThread的main()函數做爲應用的入口,會去初始化Looper,並開啓消息循環。
public final class ActivityThread {
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
複製代碼
主線程的Looper已經準備就緒了,咱們再調用Handler的構造函數去構建Handler對象時就會默認使用這個Handler,以下所示:
public class Activity {
final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
}
複製代碼
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
public boolean post(Runnable action) {
//當View被添加到window時會添加一些附加信息,這裏面就包括Handler
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
//Handler等相關信息尚未被關聯到Activity,先創建一個排隊隊列。
//這其實就至關於你去銀行辦事,銀行沒開門,大家在門口排隊等着同樣。
getRunQueue().post(action);
return true;
}
}
複製代碼
這裏面也是利用attachInfo.mHandler來處理消息,它事實上是一個Handler的子類ViewRootHandler,一樣的它也是使用Looper.prepareMainLooper()構建出來的Looper。
因此你能夠看出Activity#runOnUIThread()、View#post()這兩種方式並無本質上的區別,最終還都是經過Handler來發送消息。那麼對於那些既不在Activity裏、也不在View裏的代碼 當咱們想向主線程發送消息或者將某段代碼(一般都是接口的回調方法,在這些方法裏須要更新UI)post到主線程中執行,就能夠按照如下方式進行:
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
//TODO refresh ui
}
})
複製代碼
好了,到這裏整個Android的消息機制咱們都已經分析完了,若是對底層的管道這些東西感受比較模糊,能夠先理解Java層的實現。