大家想想這個知識點一般是怎么考察的?請解釋一下Handler的原理?
不不不,這個問題已經爛大街了,我要是面試官,我會這么問,
我們知道在Handler中,存在一個方法叫 sendMessageDelay , 作用是延時發送訊息,請解釋一下Handler是如何實作延時發送訊息的?
Looper.loop是一個死回圈,拿不到需要處理的Message就會阻塞,那在UI執行緒中為什么不會導致ANR?
也請各位讀者先自己思考一下這兩個問題,換做是你該怎么回答,
Handler
我們先從Handler的定義來認識它,先上谷歌原文:
/**
* A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread's {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread's message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it -- from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
*
* <p>There are two main uses for a Handler: (1) to schedule messages and
* runnables to be executed at some point in the future; and (2) to enqueue
* an action to be performed on a different thread than your own.
*
* <p>Scheduling messages is accomplished with the
* {@link #post}, {@link #postAtTime(Runnable, long)},
* {@link #postDelayed}, {@link #sendEmptyMessage},
* {@link #sendMessage}, {@link #sendMessageAtTime}, and
* {@link #sendMessageDelayed} methods. The <em>post</em> versions allow
* you to enqueue Runnable objects to be called by the message queue when
* they are received; the <em>sendMessage</em> versions allow you to enqueue
* a {@link Message} object containing a bundle of data that will be
* processed by the Handler's {@link #handleMessage} method (requiring that
* you implement a subclass of Handler).
*
* <p>When posting or sending to a Handler, you can either
* allow the item to be processed as soon as the message queue is ready
* to do so, or specify a delay before it gets processed or absolute time for
* it to be processed. The latter two allow you to implement timeouts,
* ticks, and other timing-based behavior.
*
* <p>When a
* process is created for your application, its main thread is dedicated to
* running a message queue that takes care of managing the top-level
* application objects (activities, broadcast receivers, etc) and any windows
* they create. You can create your own threads, and communicate back with
* the main application thread through a Handler. This is done by calling
* the same <em>post</em> or <em>sendMessage</em> methods as before, but from
* your new thread. The given Runnable or Message will then be scheduled
* in the Handler's message queue and processed when appropriate.
*/
下面由我這枚英語渣上線,強行翻譯一波,
-
Handler是用來結合執行緒的訊息佇列來發送、處理Message物件和Runnable物件的工具,每一個Handler實體化之后會關聯一個執行緒和該執行緒的訊息佇列,當你創建一個Handler的時候,它就會自動系結到到所在的執行緒或執行緒的訊息佇列,并陸續把Message/Runnable分發到訊息佇列,然后在它們出隊的時候去執行, -
Handler主要有兩個用途:
(1) 調度在將來某個時候執行的
Message和Runnable,
(2)把需要在另一個執行緒執行的操作加入到訊息佇列中去,
- 當
post runnable或send message到handler時,您可以在訊息佇列準備就緒后立即處理該事務,也可以延遲一段時間執行,或者指定某個特定時間去執行,
我們先從Handler的構造方法來認識一下它:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)
Handler的構造方法有很多個,但最終呼叫的就是上述構造方法,
老規矩,先上官方解釋,再上學渣翻譯,
* Use the provided {@link Looper} instead of the default one and take a callback
* interface in which to handle messages. Also set whether the handler
* should be asynchronous.
*
* Handlers are synchronous by default unless this constructor is used to make
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
* with respect to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by conditions such as display vsync.
1.使用提供的Looper而不是默認的Looper,并使用回呼介面來處理訊息,還設定處理程式是否應該是異步的,
2.默認情況下,Handler是同步的,除非此建構式用于生成嚴格異步的Handler,
3.異步訊息指的是不需要進行全域排序的中斷或事件,異步訊息不受同步障礙(比如display vsync)的影響,
Handler中的方法主要分為以下兩類:
- 獲取及查詢訊息,比如
obtainMessage(int what),hasMessages(int what), - 將message或runnable添加/移出訊息佇列,比如
postAtTime(@NonNull Runnable r, long uptimeMillis),sendEmptyMessageDelayed(int what, long delayMillis),
在這些方法中,我們重點需要關注一下enqueueMessage這個方法,
為什么呢?
無論是 postAtTime、sendMessageDelayed 還是其他的 post、send方法,它們最終都會調到enqueueMessage 這個方法里去,
比如:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到,sendMessageDelayed方法里將延遲時間轉換為訊息觸發的絕對時間,最終呼叫的是sendMessageAtTime方法,
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
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);
}
而sendMessageAtTime方法呼叫了enqueueMessage方法,
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);
}
enqueueMessage方法直接將message交給了MessageQueue去執行,
Message
在分析MessageQueue之前,我們應該先來認識一下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>
下面是渣翻譯:
-
定義一條包含描述和任意資料物件的訊息,該物件可以發送到Handler,此物件包含兩個額外的int欄位和一個額外的object欄位,
-
盡管Message的構造方法是public,但獲取一個Message的最好的方法是呼叫Message.obtain或者Handler.obtainMessage方法,這些方法會從可回收的執行緒池中獲取Message物件,
我們來認識一下Message里的欄位:
public final class Message implements Parcelable {
//用戶定義的標識碼,以便接收者能夠識別這條訊息是關于什么的,
//每個Handler都有自己的命名空間,因此不需要擔心標識碼與其他Handler的沖突,
public int what;
//如果只需要存盤幾個整數值,則arg1和arg2是使用setData(Bundle) setData()的低成本替代方案,
public int arg1;
public int arg2;
//要發送給接收者的任意物件
public Object obj;
//通常在跨行程通訊中使用,讓服務端能夠得到客戶端的信使物件,給客戶端發訊息
public Messenger replyTo;
//可選欄位,指示發送訊息的uid,僅對Messenger發布的訊息有效,否則默認為-1
public int sendingUid = UID_NONE;
//可選欄位,指示導致此訊息排隊的uid,
public int workSourceUid = UID_NONE;
//此標識在訊息入隊時設定,在創建或者獲取新訊息時清除
//嘗試入隊或回收已在使用的訊息會發送錯誤
/*package*/ static final int FLAG_IN_USE = 1 << 0;
//設定是否是異步訊息
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
//copyFrom方法中要清除的標志
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
@UnsupportedAppUsage
/*package*/ int flags;
//Message發送的時間,基于SystemClock#uptimeMillis
@UnsupportedAppUsage
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
/*package*/ Bundle data;
//目標handler
@UnsupportedAppUsage
/*package*/ Handler target;
@UnsupportedAppUsage
/*package*/ Runnable callback;
//使用單向鏈表儲存下一個訊息
@UnsupportedAppUsage
/*package*/ Message next;
}
在Message中,我們需要關注一下Message的回識訓制,
先來看下recyclerUnchecked方法:
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
在這個方法中,有三個關鍵變數,
- sPoolSync :主要是給Message加一個物件鎖,不允許多個執行緒同時訪問Message類和recycleUnchecked方法,
- sPool:存盤我們回圈利用Message的單鏈表,這里sPool只是鏈表的頭節點,
- sPoolSize:單鏈表的鏈表的長度,即存盤的Message物件的個數,
當我們呼叫recycleUnchecked方法時,首先會將當前Message物件的屬性清空,然后判斷Message是否已到達快取的上限(50個),如果沒有,將當前的Message物件置于鏈表的頭部,
那么取快取的操作呢?
我們來看下obtain方法:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可以看出,Message會嘗試取出sPool鏈表的第一個元素,并將sPool的頭元素往后移動一位,如果sPool鏈表為空,將會回傳一個新的Message物件,
Message里提供obtain方法獲取Message物件,使得Message到了重復的利用,減少了每次獲取Message時去申請空間的時間,同時,這樣也不會永無止境的去創建新物件,減小了Jvm垃圾回收的壓力,提高了效率,
MessageQueue
MessageQueue用于保存由Looper發送的訊息的串列,訊息不會直接添加到訊息佇列,而是通過Handler物件中關聯的Looper里的MessageQueue完成添加的動作,
您可以使用Looper.myQueue()檢索當前執行緒的MessageQueue,
我們先來看看MessageQueue如何實作添加一個Message的操作,
boolean enqueueMessage(Message msg, long when) {
//判斷msg是否有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) {
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標識為正在使用
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 如果佇列為空,或者when==0,表示需要立即執行,或者執行時間早于鏈表第一個元素時間
//將新的msg加入mMessage鏈表的第一位
msg.next = p;
mMessages = msg;
//如果處于阻塞狀態,需要喚醒佇列
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//此訊息是一條延時訊息,根據訊息的when,通過for回圈找到訊息的插入點
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;
}
mMessages是一個按照訊息實際觸發時間msg.when排序的鏈表,越往后的越晚觸發,enqueueMessage方法根據新插入訊息的when,將msg插入到鏈表中合適的位置,如果是及時訊息,還需要喚醒MessageQueue,
我們接著來看看nativeWake方法,nativeWake方法的原始碼位于\frameworks\base\core\jni\android_os_MessageQueue.cpp,
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
繼續看NativeMessageQueue里的wake函式,
void NativeMessageQueue::wake() {
mLooper->wake();
}
它又轉交給了Looper(原始碼位置/system/core/libutils/Looper.cpp)去處理,
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
mWakeEventFd.get(), nWrite, strerror(errno));
}
}
}
Looper里的wake函式很簡單,它只是向mWakeEventFd里寫入了一個 1 值,
上述的mWakeEventFd又是什么呢?
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
...
}
從Looper的建構式里可以找到答案,mWakeEventFd本質上是一個eventfd,至于什么是eventfd,這里只能說是eventfd是Linux 2.6提供的一種系統呼叫,它可以用來實作事件通知,更具體的內容需要各位讀者自行查閱學習檔案了,
既然有發送端,那么必然有接收端,接收端在哪呢?
void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ awoken", this);
#endif
uint64_t counter;
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}
可以看到,awoken函式里的內容很簡單,只是做了一個讀取的動作,它并不關系讀到的具體值是啥,為什么要這樣設計呢,我們得結合awoken函式在哪里呼叫去分析,
awoken函式在Looper的pollInner函式里呼叫,pollInner函式里有一條陳述句
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
它在這里起到阻塞的作用,如果沒有呼叫nativeWake函式,epoll_wait將一直等待寫入事件,直到超時為止,
如此,便回到我們文章一開始提出的問題了,
Looper.loop是一個死回圈,拿不到需要處理的Message就會阻塞,那在UI執行緒中為什么不會導致ANR?
首先,我們需要明確一點,Handler中到底有沒有阻塞?
答案是有!!!那它為什么不會導致ANR呢?
這得從ANR產生的原理說起,
ANR的本質也是一個Message,這一點很關鍵,我們拿前臺服務的創建來舉例,前臺服務創建時,會發送一個
what值為ActivityManagerService.SERVICE_TIMEOUT_MSG的延時20s的Message,如果Service的創建 作業在上述訊息的延時時間內完成,則會移除該訊息,否則,在Handler正常收到這個訊息后,就會進行服務超時處理,即彈出ANR對話框,
為什么不會ANR,現在各位讀者清楚了嗎?ANR訊息本身就是通過Handler去派發的,Handler阻塞與否與ANR并沒有必然關系,
我們看了MessageQueue是如何加入一條訊息的,接下來,我們來看看它是如何取出一條訊息的,
Message next() {
//如果訊息回圈已退出并已被釋放,則return
//如果應用程式在退出后嘗試重新啟動looper,則可能發生這種情況
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命令重繪到內核驅動程式,
//在執行可能會阻塞很長時間的操作之前呼叫此函式非常有用,以確保已釋放任何掛起的物件參考,
//從而防止行程保留物件的時間超過需要的時間,
Binder.flushPendingCommands();
}
//用于等待下一條可用訊息,使用了linux epoll機制阻塞,不會占用cpu時間
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 嘗試尋找下一條message
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
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);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//處理完所有掛起的訊息后,立即處理退出訊息
if (mQuitting) {
dispose();
return null;
}
// 下面都是IdleHandler邏輯
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;
}
}
next方法里主要做了三件事:
(1)使用nativePollOnce阻塞指定時間,等待下一條訊息的執行,
(2)獲取下一條訊息,并回傳此訊息,
(3)如果訊息佇列為空,則執行IdleHandler,
這里有個新名詞IdleHandler,IdleHandler是可以在 Looper 事件回圈的程序中,當出現空閑的時候,允許我們執行任務的一種機制,MessageQueue中提供了addIdleHandler和removeIdleHandler去添加洗掉IdleHandler,
next方法的第一行有個ptr變數,這個ptr變數是什么含義呢?
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
mPtr是一個long型變數,它是在MessageQueue的構造方法中,通過nativeInit方法初始化的,
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
可以看到,ptr的本質是對 jni層的NativeMessageQueue物件的指標的參考,
我們重點來看下nativePollOnce方法,探尋一下Handler中的阻塞機制,nativePollOnce方法最終呼叫的是Looper.cpp中的pollOnce函式,
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) { //一個死回圈
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning signalled identifier %d: "
"fd=%d, events=0x%x, data=%p",
this, ident, fd, events, data);
#endif
if (outFd != nullptr) *outFd = fd;
if (outEvents != nullptr) *outEvents = events;
if (outData != nullptr) *outData = data;
return ident;
}
}
if (result != 0) {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
if (outFd != nullptr) *outFd = 0;
if (outEvents != nullptr) *outEvents = 0;
if (outData != nullptr) *outData = nullptr;
return result;
}
result = pollInner(timeoutMillis);
}
}
函式里有個關于mResponses的while回圈,我們從java層呼叫的暫時不用管它,它是ndk的handler處理邏輯,我們重點來看pollInner函式,
int Looper::pollInner(int timeoutMillis) {
// 根據下一條訊息的到期時間調整超時,
if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
if (messageTimeoutMillis >= 0
&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
timeoutMillis = messageTimeoutMillis;
}
}
// 默認觸發喚醒事件,POLL_WAKE == -1
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//等待寫入事件,寫入事件由awoken函式觸發,timeoutMillis為超時時間,0立即回傳,-1一直等待
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
// Acquire lock.
mLock.lock();
...
// Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
ALOGW("Poll failed with an unexpected error: %s", strerror(errno));
//POLL_ERROR == -4
result = POLL_ERROR;
goto Done;
}
// Check for poll timeout.
if (eventCount == 0) {
//POLL_TIMEOUT == -3,epoll超時會走此分支
result = POLL_TIMEOUT;
goto Done;
}
// Handle all events.
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd.get()) {
if (epollEvents & EPOLLIN) {
//將eventfd里的數值取出,無實際含義,只是為了清空epoll事件和eventfd里的資料
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
//不會走到此分支,忽略它
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
"no longer registered.", epollEvents, fd);
}
}
}
Done: ;
// 中間省略的代碼不做探究,和ndk的handler實作有關
...
return result;
}
可以看到,pollInner函式主要的邏輯是使用epoll_wait去讀取喚醒事件,它有一個最大的等待時長,其最大等待時長和下一條訊息的觸發時間有關,
需要注意一下pollInner的回傳值result,它有三種狀態,進入方法默認為POLL_WAKE,表示觸發喚醒事件,
接下來通過對epoll_wait回傳值的判斷,它可能會變更為另兩種狀態,epoll_wait回傳值為0,表示epoll_wait因超時而結束等待,result值設為POLL_TIMEOUT;epoll_wait回傳值為-1,表示epoll_wait因系統中斷等原因而結束等待,result值設為POLL_ERROR,但不管result值設為哪一個,都會導致pollOnce退出死回圈,然代碼流程回到java層的next方法中,去取得下一個Message物件,
因此,nativePollOnce簡單意義上的理解,它就是一個阻斷器,可以將當前執行緒阻塞,直到超時或者因需立即執行的新訊息入隊才結束阻塞,
各位讀者,看到這里,大家再回過頭去想想文章的第一個問題該怎么回答吧,
Looper
Handler 機制中,我們還剩最后一個一個模塊沒有分析———— Looper,我們先從官方定義來看起:
* 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.
概括一下:
Looper是一個用于在執行緒中回圈遍歷訊息的類,默認情況下,執行緒沒有與之關聯的訊息回圈;如果要創建一個,請在運行Looper的執行緒中呼叫Looper.prepare(),然后使用Looper.loop()讓它處理訊息直到回圈停止,
上面的定義提到了兩個比較關鍵的方法,我們一個一個來看,
Looper.prepare()
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));
}
prepare的方法內容非常簡單,創建一個Looper物件,并把它放到sThreadLocal里,其中sThreadLocal是一個ThreadLocal類,
ThreadLocal類又是什么呢?
多執行緒訪問同一個共享變數的時候容易出現并發問題,特別是多個執行緒對一個變數進行寫入的時候,為了保證執行緒安全,一般使用者在訪問共享變數的時候需要進行額外的同步措施才能保證執行緒安全性,ThreadLocal是除了加鎖這種同步方式之外的一種保證一種規避多執行緒訪問出現執行緒不安全的方法,當我們在創建一個變數后,如果每個執行緒對其進行訪問的時候訪問的都是執行緒自己的變數,這樣就不會存在執行緒不安全問題,
因此,使用ThreadLocal能夠保證不同執行緒的Looper物件都有一個獨立的副本,它們彼此獨立,互不干擾,
Looper.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.");
}
//獲取與Looper關聯的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();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
//進入死回圈,不斷去從MessageQueue中去拉取Message
Message msg = queue.next(); // next方法我們已經在MessageQueue中做了分析
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
...
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
//注意這里,msg.target是一個handler物件,這個方法最終呼叫了handler的dispatchMessage
//去做訊息分發
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//回收Message,上文中有做過分析
msg.recycleUnchecked();
}
}
loop方法主要的作業是:建立一個死回圈,不斷的通過呼叫MessageQueue中的next方法獲取下一個訊息,并最終通過取得的訊息關聯的handler去完成訊息的分發,
總結
最后,我們再來理一理 Handler、Message、MessageQueue、Looper四者的關系和職責,
- Handler : 訊息分發的管理者,負責獲取訊息、封裝訊息、派發訊息以及處理訊息,
- Message :訊息的載體類,
- MessageQueue :訊息的容器,負責按訊息的觸發時間對訊息入隊出隊,以及在合適的時間喚醒或休眠訊息佇列,
- Looper : 訊息分發的執行者,負責從訊息佇列中拉去訊息并交給handler去執行,
為了更好的理解它們的關系,拿現實生活中的場景來舉個例子:
Handler是快遞員,負責收快遞,取快遞,查快遞以及退回快遞,
Message是快遞包裹,message的target屬性就是收件地址,而延時訊息就是收件人預約了派送時間,
希望在指定的時間上門派送,MessageQueue是菜鳥驛站,要對快遞進行整理并擺放在合適的位置,
Looper是一個24小時不休息的資本家,他總是不停的在看菜鳥驛站有沒有需要派送的快遞,一有快遞就立馬取
出然后壓榨快遞員去派送,
最后,我們用一張四者之間的流程圖來結束整篇文章:

為方便學習了解到更多的Handler知識點,特此我將一些 Android 開發相關的學習檔案、面試題、Android 核心筆記等檔案進行了整理,并上傳之我GitHub專案中,如有需要參考的可以直接去我 CodeChina 地址:https://codechina.csdn.net/u012165769/Android-T3 ,希望能幫助到大家學習提升,



轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/297170.html
標籤:其他
