1. 程式人生 > >Android訊息機制

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物件就準備好了。我們總結一下這小步做了什麼:

  1. 在Java層,建立了一個Looper物件,在建立Looper物件的時候,建立了MessageQueue。
  2. 在建立MessageQueue的時候,在JNI層,建立了NativeMessageQueue物件,這個NativeMessageQueue物件儲存在MessageQueue中的mPtr中。
  3. 在建立NativeMessageQueue的時候,在C++層,建立了Looper物件,儲存在JNI層的NativeMessageQueue的mLooper中,這個物件的作用是,當Java層的訊息佇列中沒有訊息的時候,就使Android應用程式主執行緒進入等待狀態,而Java層的訊息佇列中來了訊息的時候,就喚醒Android應用程式的主執行緒,來處理這個訊息。這個是基於Linux中的管道來實現的,具體用的是eventfd和epoll。
  4. 最後將新建的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 (;;) {
            ..