由淺入深全面分析Handler機(jī)制原理之源碼<難點(diǎn)>

前言

下面的內(nèi)容基于由淺入深全面分析Handler機(jī)制原理之源碼的理解,擴(kuò)展的Handler機(jī)制的難點(diǎn)分析僚害。

目錄

  • 內(nèi)存共享(如何切換線程的)
  • prepare()函數(shù)中萨蚕,使用ThreadLocal存放Looper對象岳遥,ThreadLocal的作用浩蓉。
  • Message使用對象復(fù)用池(享元設(shè)計(jì)模式)
  • Handler的阻塞/喚醒機(jī)制
  • Handler的同步屏障

來妻往,上一張大綱圖试和,醒一下神

image

Handler如何切換線程

當(dāng)我們都了解了Handler的原理,再結(jié)合上述的使用例子节视,我們也對Handler是如何切換的寻行,其實(shí)情況也很明朗了匾荆。

  1. 創(chuàng)建Message對象,我們都是知道創(chuàng)建一個對象兔魂,對象一般都是存放在堆內(nèi)存析校,在JVM中堆是在線程共享區(qū)域智玻,并不是存放在線程私有數(shù)據(jù)區(qū)吊奢,所以說所有線程都可以訪問得到這個Message對象事甜。
  2. 在子線程中逻谦,Handler發(fā)送一條Message邦马,Message會插入到MessageQueue中滋将。有人會問MessageQueue是屬于那個線程症昏,其實(shí)MessageQueue并不是屬于那個線程肝谭,只僅僅是存放Message的容器而已攘烛,就相當(dāng)于List一樣坟漱,只不過MessageQueue比較特殊,是一個隊(duì)列的形式而已成翩。
  3. 當(dāng)Looper.loop()函數(shù)捕传,不斷的從MessageQueue取出Message庸论,當(dāng)獲取到Message后,通過Handler.dispatchMessage(msg)函數(shù)去分發(fā)消費(fèi)掉這條Message而已否副。

Handler的線程如何切換是不是很簡單艘儒,當(dāng)然這都是你對Handler的原理熟悉掌握僧凤,并不難理解掐场。下面通過一張圖加深到Handler線程切換熊户。

image

用ThreadLocal保存Looper對象嚷堡,目的是什么麦到?

ThreadLocal是線程本地變量,它是一種特殊的變量刺桃,ThreadLocal為每個線程提供一個變量的副本瑟慈,使得每一個線程在同一時(shí)間內(nèi)訪問到的不同對象葛碧,這樣就隔離了線程之間對數(shù)據(jù)的一個共享訪問进泼,那么在內(nèi)部實(shí)現(xiàn)上乳绕,ThreadLocal內(nèi)部都有一個ThreadLocalMap济蝉,用來保存每個線程所擁有的變量的副本王滤。

子線程中創(chuàng)建Handler體現(xiàn)ThreadLocal的使用

錯誤例子:直接在子線程創(chuàng)建一個Handler雁乡。

new Thread(new Runnable() {
    @Override
    public void run() {
        new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
    }
}).start();

直接拋出異常:java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

直接看源碼:

public Handler(Callback callback, boolean async) {
   //...省略部分代碼
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //...省略部分代碼
}
//Looper類
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();
}

使用ThreadLocal 保存當(dāng)前Looper對象蔗怠,ThreadLocal 類可以對數(shù)據(jù)進(jìn)行線程隔離寞射,保證了在當(dāng)前線程只能獲取當(dāng)前線程的Looper對象桥温,同時(shí)prepare()函數(shù)保證當(dāng)前線程有且只有一個Looper對象。所以在子線程中創(chuàng)建Handler對象掏觉,需要添加Looper.prepare()澳腹,如果只單單添加Looper.prepare()函數(shù)還是不行酱塔,缺少動力沥邻,也需要添加Looper.loop()函數(shù)。

正確代碼如下:

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();//注意添加
        new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();//注意添加
    }
}).start();

為什么可以直接new Message羊娃,還提供obtain()函數(shù)唐全,為什么這樣設(shè)計(jì)?

如果不斷的new出Meaage對象并插入到MessageQueue中蕊玷,在jvm的堆中是不是會不斷有新Message對象創(chuàng)建以及銷毀邮利,導(dǎo)致內(nèi)存抖動集畅,而GC線程雖然作為優(yōu)先級最低的線程近弟,此時(shí)因?yàn)楸仨欸C,導(dǎo)致GC線程搶占CPU時(shí)間片挺智,主線程拿不到CPU而卡頓調(diào)幀祷愉。Google在設(shè)計(jì)消息機(jī)制的時(shí)候就想到了消息復(fù)用機(jī)制,幾乎所有Framework中發(fā)送消息都是通過Message.obtain()函數(shù)來進(jìn)行消息復(fù)用赦颇。這種消息復(fù)用機(jī)制其實(shí)就是一種享元設(shè)計(jì)模式二鳄。享元設(shè)計(jì)模式就不展開了,感興趣的可以自行查閱資料

下面我們看看Message是如何復(fù)用的媒怯,首先看看Mesage里的幾個成員變量:

Message next;//形成一個鏈表订讼,指向下一個Message
private static final Object sPoolSync = new Object();//對象鎖
private static Message sPool;//頭節(jié)點(diǎn)的消息
private static int sPoolSize = 0;//當(dāng)前鏈表的個數(shù)
private static final int MAX_POOL_SIZE = 50;//鏈表最多存放的個數(shù)

Looper的loop()函數(shù)中不斷的從消息隊(duì)列中取消息diapatch,分發(fā)之后會調(diào)用Message的回收操作扇苞。

public static void loop() {
    //獲取Looper對應(yīng)的消息隊(duì)列MessageQueue
    final MessageQueue queue = me.mQueue;
    //...省略部分代碼
    for (;;) {//不斷循環(huán)從消息隊(duì)列中取出消息
        Message msg = queue.next(); //有可能阻塞
        if (msg == null) {//沒有消息欺殿,則退出消息隊(duì)列
            return;
        }
        //...省略部分代碼
        //msg.target就是Handler,把獲取到的消息分發(fā)出去
        msg.target.dispatchMessage(msg);  
        //...省略部分代碼
        msg.recycleUnchecked();//回收Message
    }
}

當(dāng)Message分發(fā)完之后鳖敷,就調(diào)用recycleUnchecked()函數(shù)對Message進(jìn)行回收脖苏。

void recycleUnchecked() {
    flags = FLAG_IN_USE; // 標(biāo)記改Message將加入消息池
    // 重置所有消息屬性
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) { // 線程安全鎖
        if (sPoolSize < MAX_POOL_SIZE) { // MAX_POOL_SIZE = 50 ,表明消息池最多50個
            //頭節(jié)點(diǎn)設(shè)置給Next 將當(dāng)前對象最為最新的頭節(jié)點(diǎn)sPool 
            next = sPool;  
            sPool = this;
            sPoolSize++;
        }
    }
}

所以獲取一個對象池中的Message可以直接調(diào)用Message.obtain()函數(shù)即可定踱。

public static Message obtain() {
    synchronized (sPoolSync) {
        //sPool就是Looper.loop()棍潘,調(diào)用dispatchMessage函數(shù)后,調(diào)用recycleUnchecked()函數(shù)回收的Message
        if (sPool != null) {
           Message m = sPool;  //取出頭節(jié)點(diǎn)
           sPool = m.next; // 將頭節(jié)點(diǎn)的下一個作為最新的頭節(jié)點(diǎn)
           m.next = null; // 設(shè)置需要返回的消息的next為空
           m.flags = 0; // 清除是否還在鏈表中
           sPoolSize--;  
           return m;
        }
    }
    //如果對象池中沒有消息崖媚,就創(chuàng)建一個消息
    return new Message();
}

Handler的阻塞/喚醒機(jī)制

首先我們要了解Handler的阻塞或喚醒是在那里發(fā)起的亦歉,經(jīng)過上面的源碼分析及Handler的整個調(diào)用過程,我們都知道先是創(chuàng)建一個Looper對象畅哑,然后創(chuàng)建一個Handler對象肴楷,最后調(diào)用Looper.loop()函數(shù),在loop()函數(shù)中不斷從MessageQueue.next()函數(shù)中的輪詢獲取Message荠呐。

public static void loop() { 
    //...省略部分代碼
    for (;;) {//不斷循環(huán)從消息隊(duì)列中取出消息
        Message msg = queue.next(); //有可能阻塞
        //...省略部分代碼
        try {
            //msg.target就是Handler阶祭,把獲取到的消息分發(fā)出去
            msg.target.dispatchMessage(msg);
        } finally {
        }
        //...省略部分代碼
    }
}

Message next() {
    //...省略部分代碼
    int nextPollTimeoutMillis = 0;
    for (;;) {
        nativePollOnce(ptr, nextPollTimeoutMillis);//根據(jù)nextPollTimeoutMillis是阻塞還喚醒
        synchronized (this) {
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
               //...省略部分代碼
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 當(dāng)頭消息延遲時(shí)間大于當(dāng)前時(shí)間绷杜,阻塞消息要到延遲時(shí)間和當(dāng)前時(shí)間的差值
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {}
               //...省略部分代碼
            } else {
                nextPollTimeoutMillis = -1;//隊(duì)列已無消息,一直阻塞
            }
        }
        //...省略部分代碼
    }
}

Looper輪詢器調(diào)用loop()函數(shù)開始輪詢濒募,真正干活的是MessageQueue.next()函數(shù),而next()函數(shù)中獲取消息前首先調(diào)用了nativePollOnce(ptr, nextPollTimeoutMillis)函數(shù)圾结,其意思是根據(jù)nextPollTimeoutMillis判斷是否進(jìn)行阻塞瑰剃,nextPollTimeoutMillis初始化時(shí)默認(rèn)為0表表示不阻塞。

nextPollTimeoutMillis有三種對應(yīng)的狀態(tài):

  • nextPollTimeoutMillis=0 筝野,不阻塞
  • nextPollTimeoutMillis<0 晌姚,一直阻塞
  • nextPollTimeoutMillis>0 ,阻塞對應(yīng)時(shí)長歇竟,可被新消息喚醒

MessageQueue輪詢獲取Message時(shí)有兩種阻塞情況:

  • 當(dāng)輪詢MessageQueue時(shí)獲取獲取不到Message挥唠,nextPollTimeoutMillis賦值為-1進(jìn)行阻塞。
  • 當(dāng)輪詢MessageQueue時(shí)獲取獲取到Message焕议,msg.when大于當(dāng)前時(shí)間宝磨,時(shí)間差值就是阻塞的時(shí)長。

Handler的阻塞

在MessageQueue類中盅安,有幾個Native層的函數(shù)

    private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

在MessageQueue的構(gòu)造函數(shù)中唤锉,就調(diào)用nativeInit()函數(shù)進(jìn)行初始化。

MessageQueue(boolean quitAllowed) {
      mQuitAllowed = quitAllowed;
      mPtr = nativeInit();
 }

也就是說在創(chuàng)建MessageQueue對象時(shí)别瞭,通過JNI調(diào)用native層的android_os_MessageQueue_nativeInit()函數(shù)進(jìn)行初始化窿祥。進(jìn)入源碼看看里面做了那些事情。

//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // 創(chuàng)建一個與java對應(yīng)的MessageQueue
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);//返回到Java層
}

NativeMessageQueue::NativeMessageQueue() :
    mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();//獲取該線程關(guān)聯(lián)的Looper
    if (mLooper == NULL) {
        mLooper = new Looper(false);// 創(chuàng)建一個Looper對象
        Looper::setForThread(mLooper);// 將Looper保存到線程里蝙寨,也相當(dāng)于Java中用ThreadLocal保存Looper一樣
    }
}
//Looper.cpp
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);// 創(chuàng)建一個喚醒事件fd
    AutoMutex _l(mLock);
    rebuildEpollLocked();// 重構(gòu)epoll事件
}

#include<sys/eventfd.h>  
int eventfd(unsigned int initval,int flags);

void Looper::rebuildEpollLocked() {
    if (mEpollFd >= 0) {
        close(mEpollFd);//關(guān)閉epoll
    }
    // 創(chuàng)建一個epoll實(shí)例
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    struct epoll_event eventItem;
    //重新設(shè)置
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;// 監(jiān)聽可讀事件
    eventItem.data.fd = mWakeEventFd;//喚醒事件fd
    // 注冊喚醒事件mWakeEventFd到epoll實(shí)例中
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    // 將請求中的事件注冊到epoll實(shí)例中
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        // 初始化請求事件
        request.initEventItem(&eventItem);
        // 注冊請求中的事件到epoll實(shí)例中
        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, errno=%d",
                    request.fd, errno);
        }
    }
}

在調(diào)用android_os_MessageQueue_nativeInit()函數(shù)做了些什么事情:

  • 創(chuàng)建一個NativeMessageQueue對象與Jave層相對應(yīng)的MessageQueue對象晒衩。
  • 獲取一個MessageQueue相對應(yīng)Looper對象,沒有獲取則創(chuàng)建一個Looper 對象墙歪。
  • 如果創(chuàng)建Looper對象听系,則保存Looper對應(yīng)的線程。

了解了nativeInit()層初始化時(shí)后箱亿,其實(shí)也不難明白跛锌,說穿了就是構(gòu)建一個Java層一樣的處理方式而已。

值得注意的是 eventfd(0, EFD_NONBLOCK)這句代碼届惋,它是什么意思呢髓帽。eventfd函數(shù)會創(chuàng)建一個事件描述符對象,通過IO多路復(fù)用機(jī)制epoll可以監(jiān)聽事件描述符脑豹,實(shí)現(xiàn)進(jìn)程(線程)間的等待(wait)/通知(notify)郑藏,對就這么簡單。

eventfd()函數(shù)的標(biāo)志參數(shù)有兩種:

  • EFD_NONBLOCK:設(shè)置對象為非阻塞狀態(tài)瘩欺。
  • EFD_CLOEXEC:調(diào)用exec后會自動關(guān)閉文件描述符必盖,防止泄漏拌牲。

值得注意的是 rebuildEpollLocked()函數(shù),在函數(shù)里歌粥,創(chuàng)建了epoll實(shí)例塌忽,向epoll中注冊喚醒監(jiān)聽事件和請求監(jiān)聽事件。其實(shí)通過Linux系統(tǒng)的epoll機(jī)制失驶,來實(shí)現(xiàn)線程間的等待與喚醒操作土居。好可以簡單理解為epoll就是監(jiān)聽內(nèi)容的讀寫變化,來實(shí)現(xiàn)阻塞或喚醒操作嬉探。

nativePollOnce()函數(shù)

MassgeQueue#nativePollOnce()函數(shù)是Native層調(diào)用的擦耀,那么我們進(jìn)行Native層,通過源碼看看他的調(diào)用流程涩堤。

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
    jlong ptr, jint timeoutMillis) {
    //獲取NativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);// 調(diào)用pollOnce()函數(shù)
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);// 調(diào)用Looper#pollOnce()函數(shù)
    mPollObj = NULL;
    mPollEnv = NULL;

    if (mExceptionObj) {//異步處理
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

//Looper.cpp
inline int pollOnce(int timeoutMillis) {
    return pollOnce(timeoutMillis, NULL, NULL, NULL); 
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        // 一個循環(huán)不斷處理響應(yīng)列表中的事件眷蜓。
        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 (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
           }
        }

        if (result != 0) {
            if (outFd != NULL) *outFd = 0;
            if (outEvents != NULL) *outEvents = 0;
            if (outData != NULL) *outData = NULL;
            return result;
        }
        result = pollInner(timeoutMillis);// 內(nèi)部輪詢
     }
}

//循環(huán)處理內(nèi)部事件
int Looper::pollInner(int timeoutMillis) {

    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        //記錄當(dāng)前時(shí)間
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
    }

    // 事件處理類型為POLL_WAKE  喚醒
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;
    mPolling = true;//正在輪詢
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];

    //等待事件
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    //不要輪詢啦
    mPolling = false;
    //獲取鎖
    mLock.lock();

    //如果需要,重新生成epoll集胎围。
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    //POLL_ERROR Poll錯誤
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        ALOGW("Poll failed with an unexpected error, errno=%d", errno);
        result = POLL_ERROR;
        goto Done;
    }

    //POLL_TIMEOUT Poll超時(shí)
    if (eventCount == 0) {
        result = POLL_TIMEOUT;
        goto Done;
    }

    //循環(huán)處理所有事件
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;//獲取文件描述符
        uint32_t epollEvents = eventItems[i].events;//獲取事件
        if (fd == mWakeEventFd) {//如果是喚醒事件
            if (epollEvents & EPOLLIN) {
                awoken();//喚醒
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {//處理請求隊(duì)列中的事件
            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;
                //請求事件添加到Response數(shù)組中
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
    Done: ;

    //處理MessageEnvelopes的事件
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        // 如果消息的處理時(shí)間小于當(dāng)前時(shí)間吁系,則將從列表中移除
        if (messageEnvelope.uptime <= now) {
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;//處理消息的handler
                Message message = messageEnvelope.message;//獲取消息
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();//釋放鎖

                handler->handleMessage(message);//處理消息
            } // release handler

            mLock.lock();//加上鎖
            mSendingMessage = false;
            result = POLL_CALLBACK;//事件處理類型為POLL_CALLBACK
        } else {
            mNextMessageUptime = messageEnvelope.uptime;//更新下一個消息的處理時(shí)間
            break;
        }
    }

    //釋放鎖
    mLock.unlock();

    //處理所有Responses事件
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        // 如果響應(yīng)類型為POLL_CALLBACK
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            //調(diào)用callback類型的handleEvent方法
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }
            response.request.callback.clear();
            result = POLL_CALLBACK;//事件處理類型為POLL_CALLBACK
        }
    }
    return result;
}

void Looper::awoken() {
    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}

從java層調(diào)用Native層的nativePollOnce()函數(shù),通過分析整理得出調(diào)用的主路線:nativePollOnce() -> NativeMessageQueue.pollOnce() -> Looper.pollOnce() -> Looper.pollInner()

注意Java傳入的mPtr變量痊远,mPtr變量保存了一個NativeMessageQueue對象垮抗,從而使得MessageQueue成為Java層和Native層的連接橋梁,使得Java層與native層就可以相互處理消息了碧聪。

從源碼的過程得知冒版,消息的處理都放在Looper.pollInner()函數(shù)中處理了,我們重點(diǎn)分析pollInner()函數(shù)逞姿。

1辞嗡、首先是參數(shù)timeoutMillis(從Java傳入的nextPollTimeoutMillis)做相應(yīng)的處理,而timeoutMillis有三狀態(tài):

  • 等于0滞造,馬上返回续室,也就是沒有阻塞。
  • 小于0谒养,一直阻塞挺狰,直接向消息隊(duì)列中添加消息,才處理事件买窟。
  • 大于0丰泊,時(shí)間差值是多少,那么就等待多久始绍,時(shí)間到瞳购,才處理事件。

2亏推、調(diào)用epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)獲取到等待事件eventCount 学赛。

eventCount 也有三種狀態(tài):

  • 等于0年堆,返回result =POLL_TIMEOUT,表示在超時(shí)之前盏浇,要準(zhǔn)備的數(shù)據(jù)沒有準(zhǔn)備好变丧,即為等待超時(shí)。
  • 小于0绢掰,如果errno == EINTR锄贷,返回result =POLL_WAKE,否則返回result = POLL_ERROR曼月,發(fā)生了錯誤。
  • 大于0柔昼,所有數(shù)據(jù)都準(zhǔn)備好了哑芹。如果返回result =POLL_WAKE,通過wake()方法喚醒的捕透。如果返回result = POLL_CALLBACK聪姿,在一個或多個文件描述符被觸發(fā)了。

從eventCount 這三種情況乙嘀,pollInner()函數(shù)返回的值就是pollOnce()函數(shù)的返回值末购,就是POLL_WAKE、POLL_TIMEOUT虎谢、POLL_ERROR盟榴、POLL_CALLBACK四種狀態(tài)。

3婴噩、獲取到等待事件擎场,首先處理eventCount 的事件,也就是處理傳入epoll_wait()函數(shù)的等待事件几莽。如果fd == mWakeEventFd迅办,并且 epollEvents & EPOLLIN,說明有消息隊(duì)列中有新的消息要處理章蚣,調(diào)用awoken()函數(shù)站欺,它只把管道中內(nèi)容讀取出來,清空管道纤垂,方便下一次調(diào)用epoll_wait()函數(shù)時(shí)矾策,再次阻塞等待。

4洒忧、接著處理MessageEnvelopes事件蝴韭,如果消息的處理時(shí)間小于當(dāng)前時(shí)間,則將從消息列表中移除此消息熙侍,并且消費(fèi)掉這條消息榄鉴,最后更新下一個消息的處理時(shí)間履磨。

5、最后處理Responses事件庆尘,如果響應(yīng)類型為POLL_CALLBACK剃诅,然后處理這個事件,如果callbackResult等于0驶忌,則移除文件描述符矛辕,最后也callback.clear()掉。

Handler的喚醒

有Handler阻塞付魔,那么肯定有Handler喚醒聊品,對吧,什么時(shí)候喚醒呢几苍,由于一開始時(shí)翻屈,MessageQueue是沒有Message的,從而進(jìn)入了阻塞等待妻坝,如果向MessageQueue插入一條消息呢伸眶,是不是會喚醒啊。其實(shí)消息從消息隊(duì)列中全部移除quit()時(shí)刽宪,可能需要調(diào)用nativeWake方法厘贼。那么我們從MessageQueue.enqueueMessage()函數(shù)入手吧。

//MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
     //...省略部分代碼
        if (needWake) {//調(diào)用Native的喚醒機(jī)制
            nativeWake(mPtr);
        }
    //...省略部分代碼
    return true;
}

喚醒是有兩種情況:

  • 當(dāng)消息隊(duì)列中沒有消息時(shí)圣拄,Handler發(fā)送一條新消息到消息隊(duì)列中嘴秸,那么就會調(diào)用Native層的nativeWake(mPtr)函數(shù)。
  • 當(dāng)消息隊(duì)列的消息的時(shí)間售担,與當(dāng)前時(shí)間的比值赁遗,就是需要等待的時(shí)間,這個會傳入Native層族铆,時(shí)間到則自動喚醒岩四。

nativeWake()函數(shù)

我們從調(diào)用Native層的nativeWake(mPtr)函數(shù),通過源碼去分析它們的調(diào)用過程吧哥攘。

//android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    //獲取到當(dāng)前的消息隊(duì)列
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();//調(diào)用NatvieMessageQueue的wake()函數(shù)
}
void NativeMessageQueue::wake() {
    mLooper->wake();//調(diào)用Looper的wake()函數(shù)
}

//Looper.cpp
void Looper::wake() {
    uint64_t inc = 1;
    //向mWakeEventFd(喚醒文件描述符)寫入字符
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
        ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

看到了Nativen層的喚醒調(diào)用過程是不是很簡單剖煌。最后直接調(diào)用了TEMP_FAILURE_RETRY函數(shù),其他就是向管道中寫入數(shù)據(jù)逝淹,管道監(jiān)聽到有數(shù)據(jù)寫入就是喚醒Android應(yīng)用程序主線程處理事件耕姊。關(guān)于Linux系統(tǒng)的epoll機(jī)制,管道栅葡,涉及到了Binder相關(guān)知識茉兰,就不展開討論了。

Handler的同步屏障

什么是同步屏障

同步屏障:阻礙同步消息欣簇,優(yōu)先執(zhí)行異步消息规脸。

為什么要設(shè)計(jì)同步屏障

從上述代碼的Handler構(gòu)造函數(shù)可知坯约,一般情況下,默認(rèn)調(diào)用Handler(callback, false)構(gòu)造函數(shù)莫鸭,也是就是mAsynchronous = false闹丐,同時(shí)在Handler.enqueueMessage函數(shù)中msg.target已經(jīng)賦值了當(dāng)前的Handler對象,在MessageQueue.enqueueMessage中按執(zhí)行時(shí)間順序把Message插入到Message鏈表合適的位置被因。在調(diào)用MessageQueue.next函數(shù)獲取Message時(shí)添加了synchronied鎖卿拴,所以取消息的時(shí)候是互斥取消息,只能從頭部取消息梨与,也因?yàn)榧酉⑹前凑障⒌膱?zhí)行的先后順序進(jìn)行堕花。如果要優(yōu)先立即執(zhí)行某條Message時(shí),按正常情況是要排隊(duì)的粥鞋,是沒法做到立即執(zhí)行航徙,所以就引入了同步屏障。

如何開啟同步屏障

我們通過MessageQueue.postSyncBarrier() 函數(shù)陷虎,是如何引入同步屏障的。源碼如下:

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();//從消息對象池中獲取一條消息
        msg.markInUse();
        //將消息信息初始化賦值杠袱,注意這里并沒有給target賦值尚猿,這是關(guān)鍵
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            //如果開啟同步屏障的時(shí)間(假設(shè)記為T)T不為0,且當(dāng)前的同步消息里有時(shí)間小于T楣富,則prev也不為null 
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        //將 msg 按照時(shí)間順序插入到 消息隊(duì)列(鏈表)的合適位置 
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;//返回一個序號凿掂,通過這個序號可以撤銷屏障
    }
}

從上述開啟同步屏障的源碼中可以看出,Message 對象初始化時(shí)并沒有給 target 賦值纹蝴,也就是說向MessageQueue中插入一條target 為null標(biāo)記的Message庄萎,相對于正常的enqueue操作,在Handler.enqueueMessage函數(shù)中Handler與msg.target綁定了 塘安。同步屏障的Message特殊在于 target 為 null糠涛,并不會被消費(fèi),因?yàn)椴粫拘严㈥?duì)列兼犯。

在那里消費(fèi)掉同步屏障的Message忍捡,上述代碼:Looper.loop()函數(shù)中調(diào)用了MessageQueue.next()函數(shù),那么我們再看一次next函數(shù)源碼:

Message next() {
    //...省略部分代碼
    for (;;) {
        //...省略部分代碼
        nativePollOnce(ptr, nextPollTimeoutMillis);//根據(jù)nextPollTimeoutMillis是阻塞還喚醒
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //target為null 說明這是一條同步屏障消息
            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());//如果是異步切黔,則獲取并消費(fèi)這條消息
            }
        }
        //...省略部分代碼   
    }
}

從next函數(shù)中可以看出砸脊,MessageQueue中的msg.target為null說明開啟了同步屏障,同時(shí)是異步纬霞,那么Message則會優(yōu)先處理凌埂,這就是同步屏障的作用(過濾和優(yōu)先作用)。

我們來一形象圖诗芜,加深印象:

image

發(fā)送異步Message

發(fā)送同步和異步Message都是在Handler的幾個構(gòu)造函數(shù)瞳抓,可以傳入async標(biāo)志為true埃疫,這樣構(gòu)造的Handler發(fā)送的消息就是異步消息。

public Handler(boolean async) {
    this(null, async);
}
 public Handler(Callback callback, boolean async) {
     //...省略代碼
 }
 public Handler(Looper looper, Callback callback, boolean async) {
     //...省略代碼
 }
//最終調(diào)用enqueueMessage函數(shù)挨下,把消息插入到消息隊(duì)列中
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //this就是Handler Message中持有一個Handler
    //為發(fā)送消息出隊(duì)列交給handler處理埋下伏筆熔恢。
    msg.target = this;
    if (mAsynchronous) {//是否是異步信息
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//調(diào)用消息隊(duì)列的入隊(duì)函數(shù)
}

當(dāng)然我們也可以在創(chuàng)建Message時(shí),調(diào)用Message.setAsynchronous(true)將消息設(shè)為異步臭笆。
發(fā)送異步消息和發(fā)送同步消息一樣叙淌,唯一區(qū)別就在于Asynchronous的設(shè)置即可。

移除同步屏障

有啟動同步屏障愁铺,那么就有移除同步屏障鹰霍,我們看MessageQueue.removeSyncBarrier()函數(shù)源碼是怎么移除同步屏障的:

public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        //找到token對應(yīng)的屏障
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        final boolean needWake;
        //從消息鏈表中移除
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        //回收Message到對象池中。
        p.recycleUnchecked();
        if (needWake && !mQuitting) {
            nativeWake(mPtr);//喚醒消息隊(duì)列
        }
    }

在啟動同步屏障時(shí)茵乱,已經(jīng)記錄了token茂洒,然后通過token對應(yīng)的屏障,從消息鏈表中移除瓶竭,回收Message到對象池中督勺。

同步消息屏障的應(yīng)用場景

同步屏障在系統(tǒng)源碼中有哪些使用場景呢?我們?nèi)粘i_發(fā)中也很少用到同步消息屏障斤贰,涉及到同步消息屏障一般都是Android系統(tǒng)中使用得最多了智哀,比如我們的UI更新相關(guān)的消息,就是利用同步屏障發(fā)送異步消息荧恍,則會優(yōu)先處理瓷叫,從而達(dá)到馬上刷新UI。既然知道了Android系統(tǒng)中刷新UI使用了異步消息送巡,那么我們看看View的更新摹菠,draw()、requestLayout()骗爆、invalidate() 等函數(shù)都調(diào)用了次氨。

我們從View的繪制流程起始,View通過ViewRootImpl來繪制摘投,ViewRootImpl調(diào)用到requestLayout()來完成View的繪制操作:知道這個流程糟需,我們看ViewRootImpl.requestLayout()函數(shù)源碼:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();//檢查是否在主線程
        mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
        //重要函數(shù)
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //設(shè)置同步障礙谷朝,確保mTraversalRunnable優(yōu)先被執(zhí)行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //內(nèi)部通過Handler發(fā)送了一個異步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

//移除同步屏障
void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

調(diào)用 Handler.getLooper().getQueue().postSyncBarrier() 并設(shè)置同步屏障消息洲押。

最終調(diào)用Choreographer.postCallbackDelayedInternal()函數(shù),在其函數(shù)中設(shè)置Message.setAsynchronous(true) 時(shí) 圆凰,也就發(fā)送異步消息杈帐。

private void postCallbackDelayedInternal(int callbackType,
                                         Object action, Object token, long delayMillis) {
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);//獲取消息對象
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);//設(shè)置為異步
            mHandler.sendMessageAtTime(msg, dueTime);//發(fā)送異步消息
        }
    }
}

調(diào)用Handler.getLooper().getQueue().removeSyncBarrier()函數(shù)移除同步屏障。最終調(diào)用Choreographer.removeCallbacksInternal()函數(shù)移除消息。

private void removeCallbacksInternal(int callbackType, Object action, Object token) {
    synchronized (mLock) {
        mCallbackQueues[callbackType].removeCallbacksLocked(action, token);
        if (action != null && token == null) {
            //移除消息
            mHandler.removeMessages(MSG_DO_SCHEDULE_CALLBACK, action);
        }
    }
}

總結(jié)

  • 線程切換挑童,在子線程發(fā)送Message累铅,Message對象是存放在堆內(nèi)存,獲取到Message時(shí)站叼,主線程dispatchMessage分發(fā)處理這條消息娃兽。
  • ThreadLocal的作用就是線程隔離,每個線程都提供一個變量的副本尽楔,使得每一個線程在同一時(shí)間內(nèi)訪問到的不同對象投储。
  • 當(dāng)MessageQueue中沒有Message時(shí),觸發(fā)Native層進(jìn)入阻塞狀態(tài)阔馋;或者M(jìn)essageQueue中的Message更新時(shí)間沒有到時(shí)玛荞,也進(jìn)入了阻塞狀態(tài),阻塞時(shí)長就是它的更新時(shí)間呕寝。
  • MessageQueue中沒有Message時(shí)勋眯,向MessageQueue中添加Message時(shí),觸發(fā)Native層的喚醒機(jī)制下梢;MessageQueue中的Message更新時(shí)間到時(shí)客蹋,也觸發(fā)喚醒。
  • Handler的同步屏障孽江,就是優(yōu)化處理系統(tǒng)的Message嚼酝,并且Message是異步的。比如:Android應(yīng)用程序出現(xiàn)ANR竟坛,系統(tǒng)發(fā)起同步屏障,發(fā)送一條異步消息钧舌,界面上優(yōu)先處理這條消息了担汤,然后系統(tǒng)彈出一個ANR消息對話框。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載洼冻,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者崭歧。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撞牢,隨后出現(xiàn)的幾起案子率碾,更是在濱河造成了極大的恐慌,老刑警劉巖屋彪,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件所宰,死亡現(xiàn)場離奇詭異,居然都是意外死亡畜挥,警方通過查閱死者的電腦和手機(jī)仔粥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躯泰,你說我怎么就攤上這事谭羔。” “怎么了麦向?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵瘟裸,是天一觀的道長。 經(jīng)常有香客問我诵竭,道長话告,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任秀撇,我火速辦了婚禮超棺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呵燕。我一直安慰自己棠绘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布再扭。 她就那樣靜靜地躺著氧苍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪泛范。 梳的紋絲不亂的頭發(fā)上让虐,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音罢荡,去河邊找鬼赡突。 笑死,一個胖子當(dāng)著我的面吹牛区赵,可吹牛的內(nèi)容都是我干的惭缰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼笼才,長吁一口氣:“原來是場噩夢啊……” “哼漱受!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起骡送,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤昂羡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后摔踱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虐先,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年派敷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赴穗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖般眉,靈堂內(nèi)的尸體忽然破棺而出了赵,到底是詐尸還是另有隱情,我是刑警寧澤甸赃,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布柿汛,位于F島的核電站,受9級特大地震影響埠对,放射性物質(zhì)發(fā)生泄漏络断。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一项玛、第九天 我趴在偏房一處隱蔽的房頂上張望貌笨。 院中可真熱鬧,春花似錦襟沮、人聲如沸锥惋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膀跌。三九已至,卻和暖如春固灵,著一層夾襖步出監(jiān)牢的瞬間捅伤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工巫玻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丛忆,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓仍秤,卻偏偏與公主長得像熄诡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子徒扶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353