handler機(jī)制--讓線程變?yōu)椤坝绖訖C(jī)”

handler要想能工作起來流昏,第一步要做的事情是讓線程變?yōu)椤坝绖訖C(jī)”,也就是讓線程一直循環(huán)起來,不死掉亏掀,這樣線程就可以不斷的處理各種任務(wù)了。那這節(jié)就來介紹下如何讓線程變?yōu)椤坝绖訖C(jī)”泛释。

如何讓線程變?yōu)椤坝绖訖C(jī)”

下面代碼可以做到

public class Thread{

    public void run(){
        Looper.prepare();

        Looper.loop();
    }
}

如上代碼滤愕,需要依次調(diào)用Looper.prepare()和Looper.loop()方法就可以讓線程變?yōu)椤坝绖訖C(jī)”,是不是非常的簡單怜校,那我們就從源碼角度一趟究竟间影。

線程變?yōu)椤坝绖訖C(jī)”-源碼分析

前置知識

fd:Linux系統(tǒng)中把一切都看做是文件,當(dāng)進(jìn)程打開現(xiàn)有文件或創(chuàng)建新文件時茄茁,內(nèi)核向進(jìn)程返回一個文件描述符魂贬,文件描述符就是內(nèi)核為了高效管理已被打開的文件所創(chuàng)建的索引,用來指向被打開的文件裙顽,所有執(zhí)行 I/O 操作的系統(tǒng)調(diào)用都會通過文件描述符付燥,文件描述符不一定是文件,也可以是一塊匿名內(nèi)存愈犹。
epoll:io多路復(fù)用技術(shù)键科,就是在一個線程或進(jìn)程中監(jiān)聽多個文件描述符是否可以執(zhí)行io操作的能力,handler使用了這種技術(shù)漩怎,在下面會詳細(xì)介紹
eventfd:類似于管道的概念勋颖,可以實(shí)現(xiàn)線程間的事件通知 eventfd原文
ThreadLocal:這個類的主要作用是保存當(dāng)前線程獨(dú)有的數(shù)據(jù)。

相關(guān)類介紹

下面的文件屬于android s

frameworks/base/core/java/android/os/Looper.java
frameworks/base/core/java/android/os/MessageQueue.java
libcore/ojluni/annotations/hiddenapi/java/lang/ThreadLocal.java

frameworks/base/core/jni/android_os_MessageQueue.cpp
system/core/libutils/Looper.cpp

那我們就分別從:Looper.prepare()和Looper.loop()這兩個方法作為分析起點(diǎn)來進(jìn)行分析

1. Looper.prepare()

    public static void prepare() {
        //調(diào)用prepare方法勋锤,quitAllowed值為true饭玲,代表允許結(jié)束loop
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //當(dāng)前線程已經(jīng)存在Looper,則不能再次創(chuàng)建叁执,拋異常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //sThreadLocal是一個ThreadLocal類型的靜態(tài)變量茄厘,它存儲Looper實(shí)例
        [1.1]
        sThreadLocal.set(new Looper(quitAllowed));
    }

sThreadLocal.set(new Looper(quitAllowed)) 這行代碼的作用是把Looper實(shí)例存儲在ThreadLocal中矮冬,存儲下來的一個最大的目的就是:能更方便的獲取當(dāng)前Thread對應(yīng)的Looper。比如可以在代碼的任何位置蚕断,只要調(diào)用Looper.myLooper()方法就能非常方便的獲取到Looper對象欢伏。

1.1 Looper#Looper()

    //Looper的構(gòu)造方法是私有的,只能調(diào)用Looper的靜態(tài)方法來創(chuàng)建Looper
    private Looper(boolean quitAllowed) {
        //new MessageQueue
        [1.2]
        mQueue = new MessageQueue(quitAllowed);
        //保存當(dāng)前線程引用
        mThread = Thread.currentThread();
    }

1.2 MessageQueue#MessageQueue()

    MessageQueue(boolean quitAllowed) {
        //quitAllowed的值當(dāng)前為true
        mQuitAllowed = quitAllowed;
        //這一看就是進(jìn)入nativeInit jni方法亿乳,該方法返回一個指針硝拧,mPtr保存下來
        [1.3]
        mPtr = nativeInit();
    }

1.3 android_os_MessageQueue.cpp#android_os_MessageQueue_nativeInit

    static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
        //new一個NativeMessageQueue
        [1.4]
        NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
        if (!nativeMessageQueue) {
            jniThrowRuntimeException(env, "Unable to allocate native queue");
            return 0;
        }

        //增加它的引用計數(shù)器
        nativeMessageQueue->incStrong(env);
        //把nativeMessageQueue指針轉(zhuǎn)化為jlong類型躯护,返回給java層齐疙,
        return reinterpret_cast<jlong>(nativeMessageQueue);
    }

1.4 android_os_MessageQueue.cpp#NativeMessageQueue

    NativeMessageQueue::NativeMessageQueue() :
            mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
        //先獲取當(dāng)前線程的Looper,不存在則創(chuàng)建匀哄,native層也存在一個Looper
        mLooper = Looper::getForThread();
        if (mLooper == NULL) {
            //new 一個Looper出來
            [1.5]
            mLooper = new Looper(false);
            Looper::setForThread(mLooper);
        }
    }

1.5 Looper.cpp#Looper

    Looper::Looper(bool allowNonCallbacks)
        : mAllowNonCallbacks(allowNonCallbacks),
          mSendingMessage(false),
          mPolling(false),
          mEpollRebuildRequired(false),
          mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),
          mResponseIndex(0),
          mNextMessageUptime(LLONG_MAX) {
        //eventfd方法返回fd聊训,EFD_NONBLOCK的作用在調(diào)用read/write函數(shù)的時候 不阻塞抱究,EFD_CLOEXEC作用是在fork子進(jìn)程時候調(diào)用exec()方法的時,把fd close調(diào)带斑,這樣在子進(jìn)程就不存在相應(yīng)的fd了
        mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
        LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

        AutoMutex _l(mLock);
        rebuildEpollLocked();
    }

該方法中有幾個關(guān)鍵點(diǎn)需要介紹下:
eventfd:可以實(shí)現(xiàn)線程之間或者進(jìn)程之間通信鼓寺,eventfd類似于pipe,但是比 pipe 更高效勋磕,一方面它比 pipe 少用一個fd妈候,節(jié)省了資源;另一方面挂滓,eventfd 的緩沖區(qū)管理也簡單得多苦银,全部“buffer”一共只有8字節(jié)。eventfd在通信時候不能傳大數(shù)據(jù)赶站。
eventfd是如何實(shí)現(xiàn)線程之間的通信幔虏,通過下面的偽代碼來說明

    //用來保持生成的fd
    savedEventfd

    //調(diào)用eventfd方法,返回的fd是阻塞類型的
    savedEventfd = eventfd(0, EFD_CLOEXEC);

    //下面方法發(fā)生于線程A中贝椿,調(diào)用read方法從savedEventfd中讀取int數(shù)據(jù)想括,因?yàn)閟avedEventfd是阻塞類型的,因此線程A會阻塞于read方法烙博,直到有數(shù)據(jù)為止
    uint64_t counter;
    read(savedEventfd,&counter,sizeof(uint64_t));

    //下面方法調(diào)用發(fā)生于線程B中瑟蜈,調(diào)用write方法往savedEventfd中寫一個int值,因?yàn)閟avedEventfd寫入了數(shù)據(jù)习勤,上面線程A就會被喚醒,讀出剛剛寫入的int數(shù)據(jù)
    uint64_t inc = 1;
    write(savedEventfd,&inc,sizeof(uint64_t));

mWakeEventFd:它保存了eventfd方法返回的fd焙格,并且需要注意图毕,在調(diào)用eventfd方法的時候,傳遞了EFD_NONBLOCK這個參數(shù)眷唉,表示返回的fd是非阻塞類型予颤,即調(diào)用read囤官,write方法不會發(fā)生阻塞,上面的偽代碼是阻塞的蛤虐,現(xiàn)在卻是非阻塞類型的党饮,那又怎么實(shí)現(xiàn)線程之間或者進(jìn)程之間通信呢?答案是結(jié)合epoll機(jī)制驳庭。
mWakeEventFd的主要作用是java層的MessageQueue的喚醒/等待操作刑顺,喚醒/等待操作都是通過給mWakeEventFd寫數(shù)據(jù)和讀數(shù)據(jù)實(shí)現(xiàn)的。

1.6 Looper.cpp#rebuildEpollLocked

    void Looper::rebuildEpollLocked() {
        // Close old epoll instance if we have one.
        // mEpollFd存在饲常,則重新設(shè)置
        if (mEpollFd >= 0) {
    #if DEBUG_CALLBACKS
            ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
    #endif
            mEpollFd.reset();
        }

        // Allocate the new epoll instance and register the wake pipe.
        //調(diào)用epoll_create1方法重新創(chuàng)建蹲堂,這個方法會返回一個fd并賦值給mEpollFd
        mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
        LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(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 = mWakeEventFd.get();
        //通過epoll_ctl方法來添加一個event
        int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
        LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                            strerror(errno));

        //若mRequests中存在,則依次調(diào)用epoll_ctl方法添加event
        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.get(), 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));
            }
        }
    }

在介紹上面方法之前贝淤,先來介紹下fd管道柒竞,管道,eventfd播聪,再來介紹epoll機(jī)制朽基。

管道,F(xiàn)IFO离陶,fd管道稼虎,eventfd
這節(jié)不是介紹handler機(jī)制嗎?怎么涉及到了管道這些內(nèi)容枕磁,主要原因是咱們的handler機(jī)制已經(jīng)不是單單解決線程之間通信的問題渡蜻,還解決進(jìn)程之間的通信。

管道:是半雙工的(就是數(shù)據(jù)只能在一個方向上流動)计济,只能有公共祖先的兩個進(jìn)程之間使用
FIFO:也是一種管道茸苇,它沒有管道的只能在公共祖先的兩個進(jìn)程之間使用的限制
fd管道:又稱UNIX 域套接字,它是全雙工的(一端既可以是寫端也可以是讀端)沦寂,并且對進(jìn)程沒有限制学密。vsync機(jī)制就使用的是fd管道
eventfd:上面已經(jīng)介紹了,它的優(yōu)點(diǎn)高效/少占用資源传藏,缺點(diǎn):傳播的數(shù)據(jù)只能是int腻暮。

這四者都可以實(shí)現(xiàn)線程之間/進(jìn)程之間的通信,并且它們的read毯侦,write的目標(biāo)都是fd(文件描述符)哭靖。它們實(shí)現(xiàn)通信的方式如上面eventfd的偽代碼一樣,在創(chuàng)建的時候需要設(shè)置為阻塞模式侈离,在阻塞模式下read试幽,write函數(shù)都是阻塞的,因此尤其read函數(shù)的調(diào)用就需要放在單獨(dú)的線程中卦碾。那如果創(chuàng)建了很多個管道來實(shí)現(xiàn)線程之間通信铺坞,那豈不是要創(chuàng)建很多的讀線程起宽,隨著管道的數(shù)量多起來,讀線程也增加济榨,這種方式肯定不是一個好的解決方案坯沪,解決這個問題的一個技術(shù)是IO多路復(fù)用。

epoll
IO多路復(fù)用:我的理解是比如原先創(chuàng)建一個阻塞型的管道擒滑,那就需要創(chuàng)建一個讀線程專門的來監(jiān)聽管道的讀端是否有數(shù)據(jù)腐晾,那創(chuàng)建n多個阻塞型管道,那就需要創(chuàng)建n多個讀線程橘忱;那IO多路復(fù)用就是只創(chuàng)建一個讀線程赴魁,來監(jiān)聽n多個管道讀端的數(shù)據(jù)

IO多路復(fù)用的優(yōu)勢是不是很明顯钝诚,它有多種實(shí)現(xiàn):select機(jī)制颖御,poll機(jī)制,epoll機(jī)制凝颇。epoll機(jī)制是最高效潘拱,優(yōu)點(diǎn)最多的一個機(jī)制,用一段偽代碼來介紹下它的使用:

    //1. 調(diào)用epoll_create1方法先創(chuàng)建拧略,該方法返回一個fd
    epollFd = epoll_create1(EPOLL_CLOEXEC);

    //2. 調(diào)用epoll_ctl方法添加一個event芦岂,epoll_ctl方法的第一個參數(shù)就是epollFd。這樣event就和epollFd綁定在了一起垫蛆,epollFd就可以監(jiān)聽event上的數(shù)據(jù)了
    struct epoll_event eventItem;
    //event類型
    eventItem.events = EPOLLIN;
    //event對應(yīng)的fd
    eventItem.data.fd = pipeFd;
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);

    //3. 調(diào)用epoll_wait方法開始監(jiān)聽所有event的數(shù)據(jù)禽最。在沒有監(jiān)聽到數(shù)據(jù)的情況下,會進(jìn)入阻塞狀態(tài)袱饭,會釋放cpu等資源川无。參數(shù)timeoutMillis代表等待時間,== 0代表不等待立馬返回虑乖,== -1 則代表等待懦趋,直到等待數(shù)據(jù)為止,> 0則代表需要等待的時間疹味。
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

總結(jié)下epoll的用法:

  1. 初始化仅叫,調(diào)用epoll_create1方法創(chuàng)建并返回一個fd
  2. 添加event事件,調(diào)用epoll_ctl方法
  3. 等待數(shù)據(jù)到來糙捺,調(diào)用epoll_wait诫咱,它的參數(shù)timeoutMillis代表等待時間,== 0代表不等待立馬返回洪灯,== -1 則代表等待坎缭,直到等待數(shù)據(jù)為止,> 0則代表需要等待的時間。在沒有數(shù)據(jù)到來的時候幻锁,會進(jìn)入阻塞狀態(tài),會釋放cpu等資源

rebuildEpollLocked
那就來看下rebuildEpollLocked方法所做的事情:

  1. 調(diào)用epoll_create1方法創(chuàng)建并返回fd賦值給mEpollFd
  2. 把mWakeEventFd關(guān)聯(lián)的epoll_event边臼,調(diào)用epoll_ctl方法添加該事件哄尔,這樣mEpollFd就和mWakeEventFd產(chǎn)生了關(guān)聯(lián),就可以監(jiān)聽它的數(shù)據(jù)了
  3. 把mRequests中的請求也通過epoll_ctl方法添加這些事件柠并。mRequests包含了各種需要監(jiān)聽的fd岭接,比如:vsync機(jī)制就是通過fd管道實(shí)現(xiàn)的,其中一個fd在surfaceflinger進(jìn)程臼予,另外一個對應(yīng)的fd位于app進(jìn)程鸣戴,位于app進(jìn)程的fd會通過Looper.cpp#addFd方法把自己加入mRequests,這樣就可以通過epoll來監(jiān)聽是否有數(shù)據(jù)到來

1.7 小結(jié)

到此Looper.prepare()方法的流程就分析完了粘拾,用一張時序圖來看下整個調(diào)用流程


handler-looper-prepare

2.Looper.loop()

再來分析下Looper.loop()這個方法的流程

    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.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }

        me.mInLoop = true;

        省略代碼......

        //啟動一個死循環(huán)
        for (;;) {
            [2.1]
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }

    public static @Nullable Looper myLooper() {
        //從ThreadLocal中獲取Looper
        return sThreadLocal.get();
    }

2.1 Looper.loopOnce

    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        //從MessageQueue中取Message窄锅,若沒有可執(zhí)行的Message 則block
        [2.2]
        Message msg = me.mQueue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return false;
        }

        省略代碼......

        try {
            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);
            }
        }
        
        省略代碼......

        msg.recycleUnchecked();

        return true;
    }

me.mQueue.next()會進(jìn)入MessageQueue.next()方法獲取Message,進(jìn)入該方法

2.2 MessageQueue.next

    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;

        //同樣起一個死循環(huán)
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            //進(jìn)入jni方法, 這時候nextPollTimeoutMillis的值是-1
            [2.3]
            nativePollOnce(ptr, nextPollTimeoutMillis);

            省略獲取Message的代碼...... (下一節(jié)會重點(diǎn)介紹)
        }
    }

nativePollOnce方法最終會調(diào)jni的android_os_MessageQueue_nativePollOnce方法

2.3 android_os_MessageQueue.cpp#android_os_MessageQueue_nativePollOnce

    //obj java層的MessageQueue缰雇, ptr NativeMessageQueue指針入偷,timeoutMillis值為-1
    static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
            jlong ptr, jint timeoutMillis) {
        //把ptr轉(zhuǎn)換為NativeMessageQueue指針
        NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
        [2.4]
        nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
    }

2.4 android_os_MessageQueue.cpp#nativeMessageQueue#pollOnce

    void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
        mPollEnv = env;
        mPollObj = pollObj;
        //調(diào)用pollonce方法,timeoutMillis值為-1
        [2.5]
        mLooper->pollOnce(timeoutMillis);
        mPollObj = NULL;
        mPollEnv = NULL;

        if (mExceptionObj) {
            env->Throw(mExceptionObj);
            env->DeleteLocalRef(mExceptionObj);
            mExceptionObj = NULL;
        }
    }

2.5 Looper.cpp#pollOnce

    //下面方法在system/core/libutils/include/utils/Looper.h
    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);
    inline int pollOnce(int timeoutMillis) {
        //調(diào)用了pollOnce的重載方法械哟,outFd,outEvents,outData都為nullptr
        return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);
    }


    //下面方法在system/core/libutils/Looper.cpp
    //timeoutMillis為-1
    int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
        int result = 0;
        //同樣死循環(huán)
        for (;;) {
            //若有沒處理的response疏之,則處理,并返回
            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;
                }
            }

            //若result不為0,則返回暇咆,剛開始進(jìn)入這方法锋爪,result是0
            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;
            }
            //進(jìn)入pollInner方法
            [2.6]
            result = pollInner(timeoutMillis);
        }
    }

2.6 Looper.cpp#pollInner

    //timeoutMillis值為-1
    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.
        //因?yàn)閠imeoutMillis當(dāng)前值為-1 并且 mNextMessageUptime在Looper構(gòu)造方法初始化的時候,它的值為LLONG_MAX爸业,因此不會進(jìn)入下面調(diào)整timeout的邏輯
        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;
            }
    #if DEBUG_POLL_AND_WAKE
            ALOGD("%p ~ pollOnce - next message in %" PRId64 "ns, adjusted timeout: timeoutMillis=%d",
                    this, mNextMessageUptime - now, timeoutMillis);
    #endif
        }

        // Poll.
        int result = POLL_WAKE;
        mResponses.clear();
        mResponseIndex = 0;

        // We are about to idle.
        mPolling = true;

        //定義eventItems數(shù)組它主要接受傳遞過來的event其骄, 調(diào)用epoll_wait方法開始等待event,因?yàn)閠imeoutMillis當(dāng)前的值為-1,因此會阻塞等待events沃呢,并且釋放cpu等資源
        struct epoll_event eventItems[EPOLL_MAX_EVENTS];
        int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

        省略掉處理wakefd和mRequest的代碼......(后面章節(jié)會詳細(xì)講解)

        return result;
    }

該方法會調(diào)用epoll_wait方法等待events年栓,因?yàn)閠imeoutMillis當(dāng)前的值為-1,因此會使當(dāng)前線程進(jìn)入阻塞狀態(tài),并釋放cpu等資源

2.7 小結(jié)

到此 Looper.loop()方法的流程就分析完了薄霜,用一張時序圖總結(jié)下:


handler-looper-loop

總結(jié)

讓線程變?yōu)椤坝绖訖C(jī)”可以分為兩個步驟:

  1. 準(zhǔn)備階段某抓,Looper.prepare()方法其實(shí)做的都是準(zhǔn)備工作:

    • 初始化Looper對象,并且把他放入ThreadLocal中惰瓜,放入ThreadLocal的主要作用就是:為了能在當(dāng)前線程的任何代碼處非常方便的獲取到當(dāng)前線程”綁定“的Looper否副。
    • 初始化MessageQueue對象,它與上面初始化的Looper對象是一對一關(guān)系
    • 初始化native層的Looper對象崎坊,調(diào)用eventfd方法創(chuàng)建fd备禀;調(diào)用epoll_create方法創(chuàng)建epollfd,并且依次調(diào)用epoll_ctl把mWakeEventfd以及mRequest封裝成event并添加,這樣就可以通過epoll機(jī)制來監(jiān)聽它所添加的event上面的事件是否發(fā)生了
  2. 開始工作階段曲尸,在線程中調(diào)用Looper.loop()方法后就開始工作了:

    • Looper的loop方法會啟動一個死循環(huán)赋续,這樣一個線程就真正的變成“永動機(jī)”了
    • 啟動死循環(huán)后,會調(diào)用MessageQueue的next方法從中獲取Message另患,next方法調(diào)用了nativePollOnce方法纽乱,最終會調(diào)用到Looper.cpp的pollInner方法,由于第一步準(zhǔn)備階段昆箕,epoll鸦列,eventfd相關(guān)的準(zhǔn)備工作都已經(jīng)準(zhǔn)備好了,pollInner方法中會調(diào)用epoll_wait方法等待事件到來鹏倘,因?yàn)檫@時候的等待時間(timeoutMillis)為-1,會一直等待薯嗤,直到有事件發(fā)生為止,進(jìn)而導(dǎo)致當(dāng)前的線程進(jìn)入阻塞狀態(tài)纤泵,并釋放cpu等資源

因?yàn)閔andler使用了epoll機(jī)制骆姐,handler既可以實(shí)現(xiàn)線程之間通信,也可以實(shí)現(xiàn)進(jìn)程之間通信捏题。

好了诲锹,到此線程已經(jīng)做好了一切準(zhǔn)備,就等待著“各種事件“的到來了涉馅。

思考

MessageQueue的next方法獲取消息時候 等待/喚醒 實(shí)現(xiàn)方案為啥沒用 wait/notify 來實(shí)現(xiàn)归园?
上面源碼分析提到MessageQueue的next方法最終是因?yàn)閑poll_wait方法,導(dǎo)致線程進(jìn)入 等待阻塞狀態(tài)的稚矿,那為啥沒有使用wait/notify來實(shí)現(xiàn)呢庸诱?大家其實(shí)可以找很早以前的android代碼,那時候確實(shí)是用wait/notify來實(shí)現(xiàn) 等待/喚醒機(jī)制晤揣。

我認(rèn)為的主要原因是:用epoll機(jī)制實(shí)現(xiàn)的 等待/喚醒機(jī)制桥爽,主要是它有如下優(yōu)點(diǎn):

  • 功能強(qiáng)大:不僅實(shí)現(xiàn)線程之間通信,還實(shí)現(xiàn)進(jìn)程之間通信的功能昧识,vsync機(jī)制就是利用fd管道實(shí)現(xiàn)進(jìn)程通信钠四,epoll只需要監(jiān)聽fd管道的一端fd上的數(shù)據(jù)狀態(tài)即可,surfaceflinger進(jìn)程往對端fd上寫數(shù)據(jù)跪楞,epoll在當(dāng)前的線程中就可以監(jiān)聽到surfaceflinger發(fā)過來的數(shù)據(jù)缀去。
  • 高性能: epoll機(jī)制可以監(jiān)聽n多個fd,并且不會隨著fd的增加而性能下降
  • 擴(kuò)展性好: 只需要調(diào)用Looper.cpp的addFd方法就可以在當(dāng)前線程監(jiān)聽fd上的數(shù)據(jù)
  • 既可以為java層提供服務(wù)甸祭,也可以為native層提供服務(wù):native層的Looper.cpp類其實(shí)也提供了和上層MessageQueue相關(guān)的功能

假如使用wait/notify實(shí)現(xiàn)進(jìn)程之間通信就困難了缕碎,并且即使實(shí)現(xiàn)了還可能會涉及到線程之間的切換,性能方面肯定大打折扣池户。

為啥要用eventfd機(jī)制咏雌?
在把Message放入MessageQueue的時候凡怎,這時候只是需要給阻塞的MessageQueue發(fā)一個有多簡單就能多簡單的通知或者信號就行,告訴它有消息到達(dá)因赊抖。為沒有用 wait/notify 來實(shí)現(xiàn) 等待/喚醒统倒,所以就需要用管道這類技術(shù)來實(shí)現(xiàn),但是用管道做這種事情又大才小用了氛雪,evenfd是最合適檐薯,它占用的內(nèi)存非常小并且只使用一個fd,并且它就是發(fā)送一個int類型的值就可以注暗,因此使用了eventfd來實(shí)現(xiàn):把Message放入MessageQueue的時候,通知阻塞的MessageQueue有消息到來了這樣的功能墓猎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捆昏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子毙沾,更是在濱河造成了極大的恐慌骗卜,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件左胞,死亡現(xiàn)場離奇詭異寇仓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)烤宙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門遍烦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躺枕,你說我怎么就攤上這事服猪。” “怎么了拐云?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵罢猪,是天一觀的道長。 經(jīng)常有香客問我叉瘩,道長膳帕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任薇缅,我火速辦了婚禮危彩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘泳桦。我一直安慰自己恬砂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布蓬痒。 她就那樣靜靜地躺著泻骤,像睡著了一般漆羔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狱掂,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天演痒,我揣著相機(jī)與錄音,去河邊找鬼趋惨。 笑死鸟顺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的器虾。 我是一名探鬼主播讯嫂,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼兆沙!你這毒婦竟也來了欧芽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤葛圃,失蹤者是張志新(化名)和其女友劉穎千扔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體库正,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡曲楚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了褥符。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龙誊。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖喷楣,靈堂內(nèi)的尸體忽然破棺而出载迄,到底是詐尸還是另有隱情,我是刑警寧澤抡蛙,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布护昧,位于F島的核電站,受9級特大地震影響粗截,放射性物質(zhì)發(fā)生泄漏惋耙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一熊昌、第九天 我趴在偏房一處隱蔽的房頂上張望绽榛。 院中可真熱鬧,春花似錦婿屹、人聲如沸灭美。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽届腐。三九已至铁坎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間犁苏,已是汗流浹背硬萍。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留围详,地道東北人朴乖。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像助赞,于是被迫代替她去往敵國和親买羞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容