Android訊息機制
概述
Android應用程式是基於訊息驅動的,也就是說,在Android應用程式主執行緒中,所有函式都是在一個訊息迴圈中執行的。Android應用程式主執行緒是一個特殊的執行緒,因為它同時也是UI執行緒以及觸控式螢幕,鍵盤等輸入事件的執行緒。
在Android應用程式程序的啟動過程中,會去載入ActivityThread類,並且執行這個類的main方法,在main方法裡面實現了應用程式的訊息迴圈過程:
public static void main(String[] args) {
... ...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
... ...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在這個方法中,主要做了兩件事:建立了ActivityThread例項;通過Looper使主執行緒進入訊息迴圈中。我們主要關注後者。
可以看到,它首先呼叫了Looper的一個靜態方法:prepareMainLooper()
// 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
final MessageQueue mQueue;
final Thread mThread;
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在上面方法中,首先呼叫了prepare(false);
,在prepare()
方法中,將建立了Looper例項,並將該物件儲存在sThreadLocal中,這個sThreadLocal的型別是ThreadLocal,表示這個是一個執行緒區域性變數,即保證了每一個呼叫了prepare(boolean )
方法的的執行緒中都有一個獨立的Looper物件。
然後我們看看Looper的建立:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
引數quitAllowed為ture的話,表示建立的Looper是可以退出的
在Looper初始化的時候,建立了MessageQueue物件,後續的訊息就是放在這個訊息佇列中的,訊息佇列視Android應用程式處理訊息機制最重要的元件:
// True if the message queue can be quit.
private final boolean mQuitAllowed;
@SuppressWarnings("unused")
private long mPtr;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private native static long nativeInit();
MessageQueue的建立中,呼叫了一個natvie方法: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);
}
在上面函式中,建立了一個訊息佇列NativeMessageQueue,它的建立:
class NativeMessageQueue : public MessageQueue, public LooperCallback
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
它主要就是在內部建立了一個Looper物件,注意,這個Looper物件是實現在JNI層的,它與Java層中的Looper是不一樣的,不過它們是對應的,後面看訊息迴圈的時候,再分析它們之間的關係。
在建立完NativeMessageQueue後,呼叫reinterpret_cast<jlong>(nativeMessageQueue)
函式來把這個訊息佇列物件儲存在Java層中MessageQueue中的mPtr成員變數中,為了後續我們呼叫Java層的訊息佇列物件的其他成員方法進入JNI層時,能夠方便地找到它在JNI層所對應的訊息佇列物件。
再回到NativeMessageQueue的建構函式中,看看JNI層的Looper的構建:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
//建立事件物件,用來實現程序(執行緒)間的等待/通知機制
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
strerror(errno));
//解鎖,在函式退出時,會自動解鎖
AutoMutex _l(mLock);
//建立epoll
rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
... ...
// Allocate the new epoll instance and register the wake pipe.
//分配新的epoll例項並且註冊喚醒管道
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
... ...
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
... ...
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
request.fd, strerror(errno));
}
}
}
eventfd其實是核心為應用程式提供的非同步訊號量,在核心中以檔案存在。
在Looper的建立中,先通過系統呼叫eventfd( int flags)
建立一個事件物件(eventfd object),可以作為使用者空間應用程式的事件等待/通知機制,核心以通知使用者空間應用程式的事件。,核心會為這個物件維護一個64位的計數器,該函式返回一個新的檔案描述符,既然是檔案,那麼我們就可以執行read和write操作。EFD_NONBLOCK
:設定物件為非阻塞狀態;EFD_CLOEXEC
:在執行 exec() 呼叫時關閉檔案描述符,防止檔案描述符洩漏給子程序。
通過系統呼叫epoll_create(int size)
函式建立一個epoll例項,通知核心需要監聽size個fd.
其實在這裡,使用的是linux系統中一種程序間通訊機制,pipe(管道),簡單來說,管道就是一個檔案,在管道的兩端,分別是兩個開啟檔案描述符,這兩個開啟檔案描述符都是對應同一個檔案,其實一個是用來讀,另一個是用來寫,一般的使用方法是:一個執行緒通過讀檔案描述符來讀管道的內容,當管道沒有內容的時候,這個執行緒就會進入等待狀態,而另一個執行緒通過寫檔案描述符來向管道寫入內容,寫入內容的時候,如果另一端正有執行緒正在等待管道中的內容,那麼這個執行緒就會被喚醒。這個等待和喚醒的操作是如果進行的呢,這就要藉助Linux系統中的epoll機制了。在這裡需要監聽的IO介面只有mWakeEventFd
一個,但是Looper中還提供可一個addFd介面供外介面呼叫,外界可以通過這個介面把自己想要監聽的IO事件一併加入到這個Looper物件中去,當所有這些被監聽的IO介面上面有事件發生時,就會喚醒相應的執行緒來處理,
接下來呼叫epoll_ctl
函式來告訴epoll要監控的檔案描述符的什麼事件:
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
在這裡就是告訴mEpollFd,它要監控mWakeEventFd的EPOLLIN事件,也就是當管道中有內容可讀的時候,就喚醒當前正在等待管道中的內容的執行緒。
C++層的Looper建立好了後,就返回到JNI層的NativeMessageQueue的建構函式,最會就返回Java層的訊息佇列MessageQueue的建立過程,這樣Java層的Looper物件就準備好了。我們總結一下這小步做了什麼:
- 在Java層,建立了一個Looper物件,在建立Looper物件的時候,建立了MessageQueue。
- 在建立MessageQueue的時候,在JNI層,建立了NativeMessageQueue物件,這個NativeMessageQueue物件儲存在MessageQueue中的mPtr中。
- 在建立NativeMessageQueue的時候,在C++層,建立了Looper物件,儲存在JNI層的NativeMessageQueue的mLooper中,這個物件的作用是,當Java層的訊息佇列中沒有訊息的時候,就使Android應用程式主執行緒進入等待狀態,而Java層的訊息佇列中來了訊息的時候,就喚醒Android應用程式的主執行緒,來處理這個訊息。這個是基於Linux中的管道來實現的,具體用的是eventfd和epoll。
- 最後將新建的Looper儲存到sThreadLocal中,然後給sMainLooper賦值。
然後回到ActivityThread中的main方法中,在Looper.prepareMainLooper();
方法呼叫完後,會呼叫Looper.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;
}
... ...
final long end;
try {
msg.target.dispatchMessage(msg);
... ...
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
... ...
msg.recycleUnchecked();
}
}
這裡就是進入到訊息迴圈中去了,它不斷的從mQueue中去獲取下一個要處理的訊息msg,如果msg為null,那麼就表示訊息佇列正在退出,否則的話,就呼叫msg.target.dispatchMessage(msg);
來處理訊息,這個target就是Handler物件
我們首先看看是怎麼取訊息的:
//MessageQueue.java
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;
}
}
這段程式碼有點長,我們一點點看:
在呼叫這個方法時候,有可能讓執行緒進入等待狀態,什麼時候進入等待狀態呢?一種是訊息佇列中沒有訊息的時候,第二種是訊息佇列中有訊息,但是訊息指定了執行的事件,而現在還沒有達到那個時間,執行緒也會進入等待狀態。訊息佇列中的訊息是按時間先後排序的。
首先執行下面的native方法,檢視當前訊息佇列中有沒有訊息:
nativePollOnce(ptr, nextPollTimeoutMillis);
這是一個JNI方法,我們等於再看,這裡傳入的ptr就是我們在JNI層建立的NativeMessageQueue
物件,nextPollTimeoutMillis
表示如果當前訊息佇列中沒有訊息的話,如果等待的時間,for迴圈開始之前,它的值為0,就表示不等待。
當nativePollOnce()
方法返回後,就去看看訊息佇列中有沒有訊息:
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//mMessages是連結串列的開頭
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//進入這個if,說明存在一個barrier 訊息,會尋找佇列中下一個非同步訊息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//如果msg不為空
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,表示沒有阻塞
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;
}
在上面程式碼中,如果訊息佇列中訊息,如果不是非同步訊息,且當前時間大於等於訊息中的執行時間,那麼就直接返回這個訊息,否則就要等待到這個訊息的執行時間。如果某個訊息的target為null,就表示沒有handler可以處理它,就表示它是一個屏障訊息,如果找到下一個非同步訊息,然後進行同樣的時間返回。
如果msg為null話,說明訊息佇列中沒有訊息,那麼就將nextPollTimeoutMillis = -1;
,-1表示下次呼叫nativePollOnce時,如果訊息中沒有訊息,就進入無限等待狀態中去。
這裡說的等待是空閒等待,而不是忙等待,因此,在進入空閒等待狀態之前,如果應用程式註冊了IdelHandler介面來處理一些事情,就先執行這裡的IdelHandler,再進入等待狀態。IdlerHandler是定義在MessageQueue的一個內部類:
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
* //當一個執行緒要被阻塞等待的時候,呼叫這個介面去發現更多的messages
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
可是看到它只有一個方法queueIdle()
,執行這個方法的時候,如果返回值為false,那麼就會從應用程式中移除這個IdleHandler,否則的話就會在應用程式中繼續維護這個IdleHandler,下次空閒的時候仍會再次執行它。MessageQueue提供了addIdleHandler和removeIdleHandler兩註冊和刪除IdleHandler。
回到MessageQueue的next
方法中,它接下來就是在進入等待狀態之前,看看有沒有IdleHandler是需要執行的:
Message next() {
... ...
for (;;) {
..