View的postDelayed方法深度思考

前言

在這裏插入圖片描述

突然某天好友老瑞問我 “View的postdelayed方法,延遲時間如果設置10分鐘或者更長的時間有什麼問題嗎?“ 。當時聽到這個問題時候我只能聯想到 Handle.postDelay ,與此同時讓我回想起了之前的一些疑問?

  • View的postDelayed方法,延遲時間如果設置10分鐘或者更長的時間有什麼問題嗎?
  • View的postDelayed方法,延遲時間如果設置爲負數有沒有問題?
  • View的postDelayed方法,是先delay還是先post?
  • View的postDelayed方法,有沒有可能造成內存泄露?
  • Handle有沒有可能造成內存泄露?
  • Looper的無線循環會使線程卡死麼?

如果上面的問題大家知道答案,那麼文章大家可以快速閱讀。如果不是,那麼我們腦海中可以帶着問題跟着我的思路去一步步學習和理解相關的知識點,最後回過頭來自己再回答這些問題。

網上搜索資料找到一篇 [Handler.postDelayed()精確延遲指定時間的原理] 文章,自己感覺從中學到很很多知識。本篇文章是我結合 Android源碼 在此基礎之上進行了思考和分析,文章內容也包含這篇資料所講的內容。

關於 HandlerLooper 的大部分知識在以前的 又一年對Android消息機制(Handler&Looper)的思考 文章講的比較詳細,這裏講的比較省略。

文章代碼基於 Android5.1 進行分析。

postDelayed源碼分析

View.postDelayed

導致將Runnable添加到消息隊列中,並在經過指定的時間後運行。runnable將在用戶界面線程上運行。

//View.java
/**
 * <p>Causes the Runnable to be added to the message queue, to be run
 * after the specified amount of time elapses.
 * The runnable will be run on the user interface thread.</p>
 * *********
 */
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    //如果mAttachInfo不爲空,調用mAttachInfo的mHandler的post方法
    //即ViewRootHandler的postDelayed方法,ViewRootHandler繼承Handler
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
    //假設會稍後成功,爲什麼假設?可以看方法前面的註釋,後面也會講。
  	//Assume that post will succeed later
    ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
    return true;
}

其中 mAttachInfo 是在 dispatchAttachedToWindow 方法中進行賦值的;

//View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    //System.out.println("Attached! " + this);
    mAttachInfo = info;
    /***部分代碼省略***/
    onAttachedToWindow();
    /***部分代碼省略***/
}

ViewdispatchAttachedToWindow 方法是在 ViewRootImpl.performTraversals 第一次執行的時候調用的。相關知識可以看 ViewRootImpl的獨白,我不是一個View(佈局篇) 這篇文章。

上面通過判斷 mAttachInfo 是否爲空分爲兩種情況:

  • 間接調用 ViewRootHandlerpostDelayed 方法;
  • 調用 ViewRootImpl.RunQueuepostDelayed 方法;

ViewRootImpl.getRunQueue

當沒有附加處理程序時,運行隊列用於將未完成的工作從View中排隊。 該工作在線程的下一次對performTraversals的調用期間執行。

//ViewRootImpl.java
/**
 * The run queue is used to enqueue pending work from Views when no Handler is
 * attached.  The work is executed during the next call to performTraversals on
 * the thread.
 * @hide
 */
static final class RunQueue {
    private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
    void post(Runnable action) {
        postDelayed(action, 0);
    }
    //往隊列中添加HandlerAction對象
    void postDelayed(Runnable action, long delayMillis) {
        HandlerAction handlerAction = new HandlerAction();
        handlerAction.action = action;
        handlerAction.delay = delayMillis;
        synchronized (mActions) {
            mActions.add(handlerAction);
        }
    }

    void removeCallbacks(Runnable action) {
        final HandlerAction handlerAction = new HandlerAction();
        handlerAction.action = action;
        //這裏不考慮delay,所以HandlerAction的equals不考慮delay是否相等
        synchronized (mActions) {
            final ArrayList<HandlerAction> actions = mActions;
            //循環刪除隊列中的HandlerAction,如果該對象的Runnable與action相等
            while (actions.remove(handlerAction)) {
                // Keep going
            }
        }
    }
    //執行RunQueue隊列中的HandlerAction,並清空mActions
    //執行調用的是handler.postDelayed,這裏的handler也是mAttachInfo.mHandler
    void executeActions(Handler handler) {
        synchronized (mActions) {
            final ArrayList<HandlerAction> actions = mActions;
            final int count = actions.size();
            for (int i = 0; i < count; i++) {
                final HandlerAction handlerAction = actions.get(i);
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }
            actions.clear();
        }
    }
    //對Runnable和delay進行包裝
    private static class HandlerAction {
        Runnable action;
        long delay;
        //判斷兩個對象是否相等,action相等則兩個對象相等,不用考慮delay
        //爲什麼不用考慮,上面有講
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            HandlerAction that = (HandlerAction) o;
            return !(action != null ? !action.equals(that.action) : that.action != null);

        }
        //這裏不太清楚爲什麼這樣重寫hashCode方法
        @Override
        public int hashCode() {
            int result = action != null ? action.hashCode() : 0;
            result = 31 * result + (int) (delay ^ (delay >>> 32));
            return result;
        }
    }
}
  • RunQueue.executeActions 是在 ViewRootImpl.performTraversal 當中進行調用;

  • RunQueue.executeActions 是在執行完 host.dispatchAttachedToWindow(mAttachInfo, 0); 之後調用;

  • RunQueue.executeActions 是每次執行 ViewRootImpl.performTraversal 都會進行調用;

  • RunQueue.executeActions 的參數是 mAttachInfo 中的 Handler 也就是 ViewRootHandler ;

這裏我們得到的結論是 ViewRootImpl.getRunQueuepostDelayed 方法最終也是間接調用 ViewRootHandlerpostDelayed 方法。

RunQueue的小問題

//ViewRootImpl.java 
static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
static RunQueue getRunQueue() {
    //獲取當前線程的RunQueue
    //其他線程:你不管我了麼?
    RunQueue rq = sRunQueues.get();
    if (rq != null) {
        return rq;
    }
    rq = new RunQueue();
    sRunQueues.set(rq);
    return rq;
}

這個是 ViewRootImpl 中的 geRunQueue 實現,並且 sRunQueues 使用的是 ThreadLocal

ThreadLocal 相關知識可以閱讀 Android與Java中的ThreadLocal

也就是說這裏只能執行主線程postDelayed 中的 Runnable

我用 Android4.1.2 設備在 new Thread 使用 View.postDelayedRunnable 是不執行的, 但相同代碼在 Android8.0 上是沒有任何問題的。

//android-28中的View.java
public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }
		// 我們看到這裏的註解也不一樣了~!
    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}
private HandlerActionQueue mRunQueue;
private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

其中 android-28 中不僅修改了之前的小問題, HandlerActionQueue 同樣也做了些許優化。

View.postDelayed小結

postDelayed 方法調用的時候如果當前的 View 沒有依附在 Window 上的時候先將 Runnable 緩存在 RunQueue 隊列中等到 View.dispatchAttachedToWindow 調用之後再被 ViewRootHandler 一次 postDelayed 這個過程中相同的 Runnable 只會被 postDelay 一次。

在當前的 View 依附在 Window 上的時候直接調用 ViewRootHandlerpostDelayed 方法。

View.postDelayed 最終都調用 ViewRootHandler.postDelayed

ViewRootHandler.postDelayed

//ViewRootImpl.java
final class ViewRootHandler extends Handler {
    @Override
    public String getMessageName(Message message) {
        /***部分代碼省略***/
        return super.getMessageName(message);
    }
    @Override
    public void handleMessage(Message msg) {
        /***部分代碼省略***/
    }
}

ViewRootImpl 繼承 Handler ,所以下邊我們只需要分析 Handler.postDelayed 即可。

又一年對Android消息機制(Handler&Looper)的思考 這篇文章講了一些 HandlerLooper 的基礎知識。

handler.jpg

//Handler.java
//構造Message消息
public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//獲取緩存中的Message,綁定callback
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
//對Delay時間做判斷,將delaytime改變爲updatetime(延遲時間變爲執行時間)
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//對Queue隊列做判空校驗
//這裏有一個思考,如果uptimeMillis小於當前時間或者小於0呢?
public boolean sendMessageAtTime(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);
}
//給當前的Message綁定當前的Handler,如果handler構造爲async那麼將message設置爲異步消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

上面是 postDelay 將一個 Runnable 添加到消息隊列的方法調用路徑。

Runnable 綁定到了一個 Message 上,這個 Message 也綁定了當前的 Handler

MessageQueue.enqueueMessage

Message 添加到 Looper 的消息隊列:

//MessageQueue.java
//標識是否next方法被poolOnce阻塞進行等待
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
private boolean mBlocked;
/**
 * msg:消息體
 * when:消息的執行時間=SystemClock.uptimeMillis() + delayMillis
 */
boolean enqueueMessage(Message msg, long when) {
    //校驗message對應的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) {
        //當前消息隊列是否執行了quit方法進行退出;
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w("MessageQueue", e.getMessage(), e);
            msg.recycle();
            return false;
        }
        //標記當前的message已經被使用過;
        msg.markInUse();
        msg.when = when;
        //將當前連表的頭消息賦值給p;
        Message p = mMessages;
        //是否需要喚醒looper;
        boolean needWake;
        //如果頭消息爲空,或者新消息執行時間爲0,或者頭消息的執行時間比新消息的還晚;
        //那麼新消息作爲連表的消息頭,如果被阻塞了也需要喚醒事件隊列;
        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;
                }
                //如果可能需要喚醒,但是在隊列中找到其他異步消息。
                //那麼不需要進行喚醒,因爲當前的異步消息不會即可被執行
                //要執行也是它前面的異步消息p被執行
                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;
}

上面這個過程講述了將一個 Message 根據他的執行時間從小到大插入到鏈表中。除過 同步屏障 之外應該都不難理解,又一年對Android消息機制(Handler&Looper)的思考 這一篇文章中我們講了同步屏障的產生、使用和移除 MessageQueue.next 方法。

Handler.postDelayed小結

看到這裏的話,文章開始提的問題幾乎都能夠找到答案了吧~!

  • View的postDelay方法,延遲時間如果設置10分鐘或者更長的時間有什麼問題嗎?

Delay時間是沒有任何限制的,這個時間過程長只是使 post的Runnable的執行時間變長 。當前在這個過程中對應的Handler和Runnable是沒有辦法進行回收的,因爲他們一直存儲在消息隊列中。

  • View的postDelay方法,延遲時間如果設置爲負數有沒有問題?

Delay的時間爲負數也是沒有問題,因爲sendMessageDelayed方法會對Delay的時間做校驗小於零的時候賦值爲零。

  • View的postDelay方法,是先delay還是先post?

需要執行的Runnable是先被post到消息隊列中的,然後延遲delay時間之後執行。

  • View的postDelay方法,有沒有可能造成內存泄露?

只要post之後進入消息隊列中的Message一直在鏈表中,那麼相關對象的引用都不會被釋放,所以這裏會造成內存泄露

  • Handle有沒有可能造成內存泄露?

View的postDelay最終調用的就是Handler的postDelay,根據上面問題的答案就知道Handler也會造成內存泄露。

最後一個問題還沒有解決,所以我們還需要繼續往下閱讀哦_

Looper的無線循環會使線程卡死麼?

又一年對Android消息機制(Handler&Looper)的思考 這一篇文章中我們講了 Looper.loop 方法和 MessageQueue.next 方法。在調用 Looper.loop 方法的過程中 MessageQueue.next 方法可能會產生阻塞,這個在源碼的註釋上都有。

在這裏插入圖片描述

MessageQueue.nativePollOnce和nativeWake

//MessageQueue.java
private native void nativePollOnce(long ptr, int timeoutMillis); 
private native static void nativeWake(long ptr);

nativePollOnce 是一個 C 實現的方法,從JNI_OnLoad看so的加載 這篇文章講述了 native方法的動態註冊

有睡眠就有喚醒,所以這裏我們 pollOncewake 一起做分析。

pollOnce和wake的native方法註冊

//AndroidRuntime.cpp
//啓動AndroidRuntime運行時環境
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote);
//向VM註冊Android的native方法
int AndroidRuntime::startReg(JNIEnv* env);
/**
 * gRegJNI,它是一個常量數組,裏面是註冊native方法的。
 * s這個方法循環遍歷這個gRegJNI數組,依次調用數組中的方法
 */
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env){
    for (size_t i = 0; i < count; i++) {
        //mProc是註冊的方法的函數指針
        if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}
//android_os_MessageQueue.cpp
//需要註冊的native方法以及Java端對應的方法名稱以及函數的參數和返回值
static JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsIdling", "(J)Z", (void*)android_os_MessageQueue_nativeIsIdling }
};
//靜態註冊到gRegJNI數組中的方法,被在register_jni_procs中執行
int register_android_os_MessageQueue(JNIEnv* env) {
    //註冊gMessageQueueMethods數組中的方法
    int res = jniRegisterNativeMethods(env, "android/os/MessageQueue",
            gMessageQueueMethods, NELEM(gMessageQueueMethods));
    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
	  //對應的java端的類爲android/os/MessageQueue
    jclass clazz;
    FIND_CLASS(clazz, "android/os/MessageQueue");
		//對應的java端的類android/os/MessageQueue中的mPtr對應的變量爲J
    GET_FIELD_ID(gMessageQueueClassInfo.mPtr, clazz,"mPtr", "J");
    
    return 0;
}

pollOnce和wake的native方法調用

//android_os_MessageQueue.cpp
//{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, timeoutMillis);
}
//{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake }
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    return nativeMessageQueue->wake();
}
class NativeMessageQueue : public MessageQueue {
public:
    NativeMessageQueue();
    virtual ~NativeMessageQueue();
    virtual void raiseException(JNIEnv* env, const char* msg, jthrowable exceptionObj);
    void pollOnce(JNIEnv* env, int timeoutMillis);
    void wake();
private:
    bool mInCallback;
    jthrowable mExceptionObj;
}
void NativeMessageQueue::wake() {
    mLooper->wake();
}
void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
    mInCallback = true;
    mLooper->pollOnce(timeoutMillis);
    mInCallback = false;
    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

我們可以看到 nativePollOnce 對中調用的是 nativeMessageQueue->pollOnce 方法最終調用 Looper。pollOncenativeWake 對中調用的是 nativeMessageQueue->wake 方法最終調用 Looper.wake

pollOnce和wake方法的聲明

//Looper.h
/**
 * Waits for events to be available, with optional timeout in milliseconds.
 * Invokes callbacks for all file descriptors on which an event occurred.
 *
 * If the timeout is zero, returns immediately without blocking.
 * If the timeout is negative, waits indefinitely until an event appears.
 *
 * Returns POLL_WAKE if the poll was awoken using wake() before
 * the timeout expired and no callbacks were invoked and no other file
 * descriptors were ready.
 *
 * Returns POLL_CALLBACK if one or more callbacks were invoked.
 *
 * Returns POLL_TIMEOUT if there was no data before the given
 * timeout expired.
 *
 * Returns POLL_ERROR if an error occurred.
 *
 * Returns a value >= 0 containing an identifier if its file descriptor has data
 * and it has no callback function (requiring the caller here to handle it).
 * In this (and only this) case outFd, outEvents and outData will contain the poll
 * events and data associated with the fd, otherwise they will be set to NULL.
 *
 * This method does not return until it has finished invoking the appropriate callbacks
 * for all file descriptors that were signalled.
 */
int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
/**
 * Wakes the poll asynchronously.
 *
 * This method can be called on any thread.
 * This method returns immediately.
 */
void wake();

在這裏的閱讀代碼的文檔就對理解方法有很大的幫助,下面的源碼分析中不懂的可以參考上面的文檔。

pollOnce和wake方法的實現

下邊內容會涉及到一些Linux內核所提供的一種文件系統變化通知機制 Epoll 相關的知識點 ,深入理解Android劵III-INofity與Epoll 這篇文章講的非常詳細,建議大家閱讀。

pollOncewake都是 Looper 的成員方法,那麼在將具體方法之前我們先看看 Looper 的構造方法。

Looper的構造

//Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    //兩個文件描述符引用,一個表示讀端,一個表示寫端
    int wakeFds[2];
    //調用pipe系統函數創建一個管道,
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
    //掌握着管道的寫端
    mWakeReadPipeFd = wakeFds[0];
    //掌握着管道的讀端
    mWakeWritePipeFd = wakeFds[1];
    //設置給mWakeReadPipeFd描述符狀態非阻塞I/O標誌
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",errno);
    //設置給mWakeWritePipeFd描述符狀態非阻塞I/O標誌
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",errno);
    mIdling = false;
    //創建一個epoll對象的描述符,之後對epoll的操作均使用這個描述符完成。EPOLL_SIZE_HINT表示了此epoll對象可以監聽的描述符的最大數量。
    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);
    //struct epoll_event {
    //  __uint32_tevents; /* 事件掩碼,指明瞭需要監聽的事件種類*/
    //  epoll_data_t data; /* 使用者自定義的數據,當此事件發生時該數據將原封不動地返回給使用者 */
    //};
    struct epoll_event eventItem;
    //將epoll_event類型結構的eventItem字段初始化爲0
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    //EPOLLIN(可讀),EPOLLOUT(可寫),EPOLLERR(描述符發生錯誤),EPOLLHUP(描述符被掛起)
    eventItem.events = EPOLLIN;
    //eventItem需要監聽的文件描述符mWakeReadPipeFd
    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_create(int max_fds):創建一個epoll對象的描述符,之後對epoll的操作均使用這個描述符完成。max_fds參數表示了此epoll對象可以監聽的描述符的最大數量。

epoll_ctl (int epfd, int op,int fd, struct epoll_event *event):用於管理註冊事件的函數。這個函數可以增加/刪除/修改事件的註冊。

這裏需要注意的是:我們往創建的 mEpollFd 添加的事件的 eventsEPOLLINdata.fdmWakeReadPipeFd ,這些東西我們後面會用到。

pollOnce

//Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    //處理eventItems中除過mWakeReadPipeFd之外的事件
    //(調用Looper::addFd方法添加的事件)隊列mResponses中需要處理的事件
    //比如NativeDisplayEventReceiver的initialize方法,添加的文件描述符的ident=0
    for (;;) {
        while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            //inent>=0標識需要回調的事件,比如輸入事件
            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 != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }
        //表示已經喚醒,或超時,或出錯,或執行了消息回調
        if (result != 0) {
#if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - returning result %d", this, result);
#endif
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }
        //poll epool內部實現,方法會進行等待
        result = pollInner(timeoutMillis);
    }
}
  • 先處理 mResponses 中需要返回結果的事件;
  • 判斷當前是否已經喚醒,或超時,或出錯,或執行了消息回調,否則進行等待;

pollnner

這個方法比較長,請耐心閱讀_。這個方法是正真調用 epoll_wait 方法進行等待事件的地方。

int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout):用於等待事件的到來。當此函數返回時,events數組參數中將會包含產生事件的文件描述符。

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);
#endif
    //根據下一條消息的到期時間調整超時,
    // Adjust the timeout based on when the next message is due.
    //如果喚醒時間不等於0,而且下一條mMessageEnvelopes隊列頂部消息的執行時間不爲LLONG_MAX
    //mMessageEnvelopes相當於Java層的MessageQueue鏈表隊列,隊列中MessageEnvelope執行時間由小到大
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        //獲取當前時間戳
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //判斷當前時間與下一條消息的執行時間的時間間隔,這裏稱爲delay時間。
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        //如果下一條消息的delay時間在現在之後
        //而且本地休眠不需要喚醒(timeoutMillis < 0)或者下一條消息的delay時間比這次需要喚醒的時間靠前,那麼修改本次喚醒時間
        //比如說這次休眠不需要喚醒,或者下一條消息是1s後喚醒,這次消息需要2s後喚醒,那麼將喚醒時間修改爲1s
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }
    //開始喚醒或等待喚醒
    // Poll.
    int result = POLL_WAKE;
    //回調容器清空,索引重置
    mResponses.clear();
    mResponseIndex = 0;
    // We are about to idle.
    mIdling = true;
    //eventItems爲mEpollFd註冊的時間
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    //陷入等待狀態,直到其註冊的事件之一發生之後纔會返回,並且攜帶了剛剛發生的事件的詳細信息。
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // No longer idling.
    mIdling = false;
    // Acquire lock.
    mLock.lock();
    //epoll_wait返回值小於0表示錯誤
    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = POLL_ERROR;
        goto Done;
    }
    //epoll_wait返回值等於0表示沒有任何事件需要處理
    // Check for poll timeout.
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        ALOGD("%p ~ pollOnce - timeout", this);
#endif
        result = POLL_TIMEOUT;
        goto Done;
    }
    //epoll_wait返回值大於0,處理eventCount個事件
    // Handle all events.
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);
#endif
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        //判斷是否是在構造函數中添加到epoll對象中的表示
        if (fd == mWakeReadPipeFd) {
            //管道中已經有了可讀數據
            if (epollEvents & EPOLLIN) {
                //進行讀數據喚醒線程,清理管道,以便於下一次管道寫入信息進行喚醒looper
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            //其他事件(調用Looper::addFd方法添加的事件)添加到
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                // EPOLLIN :可讀(包括對端SOCKET正常關閉);
                // EPOLLOUT:可寫;
                // EPOLLERR:錯誤;
                // EPOLLHUP:中斷;
                // EPOLLPRI:高優先級的可讀(這裏應該表示有帶外數據到來);
                // EPOLLET: 將EPOLL設爲邊緣觸發模式,這是相對於水平觸發來說的。
                // EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後就不再監聽該事件
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                //將events添加到mResponses隊列中
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
Done: ;
    //調用掛起的消息回調
    // Invoke pending message callbacks.
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        //獲取當先時間
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //獲取鏈表頂部的MessageEnvelope對象
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        //如果該MessageEnvelope的執行時間比現在要早或是現在,那麼處理這個MessageEnvelope,並移除這個MessageEnvelope
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                //移除這個消息
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
                        this, handler.get(), message.what);
#endif
                //處理這個消息
                handler->handleMessage(message);
            } // release handler
            mLock.lock();
            mSendingMessage = false;
            result = POLL_CALLBACK;
        } else {
            //直到頂部的MessageEnvelope的執行時間比現在晚
            //設置mNextMessageUptime爲mMessageEnvelopes頂部消息的執行時間
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }
    // Release lock.
    mLock.unlock();
    //調用所有響應回調。 
    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        //如果當前的Response不需要callback那麼現在執行
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            //處理所有回調的相應時間
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            //如果事件屬於單次執行那麼從mResponses刪除這個文件描述符
            if (callbackResult == 0) {
                removeFd(fd);
            }
            ////立即清除響應結構中的回調引用,因爲在下一次輪詢之前不會清除響應向量本身。
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            //需要回調響應
            result = POLL_CALLBACK;
        }
    }
    return result;
}
//進行讀數據喚醒線程,清理管道,以便於下一次管道寫入信息進行喚醒looper
void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken", this);
#endif
    char buffer[16];
    //成功返回讀取的字節數,出錯返回-1並設置errno
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}

好吧,方法真長。讓我們繼續 fuck the source code ,用我們自己的語言敘述一下這個方法。

  • 調整喚醒的超時時間,判斷這個喚醒時間與 MessageQueue 鏈表頭部消息的喚醒時間;
  • 清除mResponses 內容重置索引,開始陷入等待事件中;
  • epoll_wait返回值小於0,result = POLL_ERROR;
  • epoll_wait返回值等於0,result = POLL_TIMEOUT;
  • epoll_wait返回值大於0,處理已經發生的事件;
    • 如果文件描述符是 mWakeReadPipeFd 而且事件爲 EPOLLIN ,這個標識管道有數據寫入,喚醒線程。需要的操作是清楚管道數據,等待下一次被喚醒;
    • 否則將這個已經發送的事件添加到 mResponses 隊列當中;
  • 處理C層消息隊列 mMessageEnvelopes 中執行時間已經到期的消息;
  • 處理 mResponses 數組中不需要信息返回的事件;

wake

//Looper.cpp
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif
    //寫入文檔的字節數(成功);-1(出錯)
    ssize_t nWrite;
    //Linux中系統調用的錯誤都存儲於errno中
    //#define EPERM        1  /* Operation not permitted */
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);
    if (nWrite != 1) {
        //#define EAGAIN      11  /* Try again */
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

當管道中寫入數據的時候,管道的另一端就可以進行讀操作,所以添加到 Epoll 中的事件就會進行處理從而喚起當前的線程。

結論

Looper 的循環中我們由消息就處理消息,沒有消息使用 epoll_wait 掛起當前的線程,這個時候是不會消耗 CPU 資源(或者說消耗的非常少)。

所以**Looper的無線循環會使線程卡死麼?**這個問題的答案我們已經得到了不是麼~!

文章到這裏就全部講述完啦,若有其他需要交流的可以留言哦

想閱讀作者的更多文章,可以查看我 個人博客 和公共號:

振興書城

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章