我所理解的Handler

前言

Handler不管是作為一種消息機制,還是作為切換線程的手段关摇,在Android中都有充足的應用場景。在不了解Handler原理的情況下瑞你,僅知道上層API如何發(fā)送消息烛愧,如何處理消息油宜,加之了解一些Handler容易造成的問題以及應對策略,實際上也不會造成什么大問題怜姿。

Handler的戲份比上面所描述的要重慎冤,因為一個APP的運行過程,是不斷接受消息以及處理消息的過程沧卢。比如Activity蚁堤,從啟動、創(chuàng)建但狭、生命周期回調(diào)披诗、銷毀撬即,都是借由Handler發(fā)送消息來驅(qū)動完成。從一個APK的安裝呈队,到一個View的更新剥槐,都離不開Handler的幫助。

如果對于以下問題有疑問宪摧,那這篇文章可能有借鑒價值:

  • Handler如何保證運行在目標線程
  • Handler容易造成內(nèi)存泄漏的原因
  • loop()為什么不會阻塞粒竖,CPU為什么不會忙等
  • MessageQueue如何存儲
  • Message如何緩存
  • 什么是線程空閑消息
  • 線程如何使用Handler機制

note: 本文源碼版本為8.0

Handler 如何運行

Handler角色分配:

Handler中存在四種角色
Handler
Handler用來向Looper發(fā)送消息,在Looper處理到對應的消息時绍刮,Handler在對消息進行具體的處理温圆。上層關鍵API為handleMessage(),由子類自行實現(xiàn)處理邏輯孩革。

Looper
Looper運行在目標線程里岁歉,不斷從消息隊列MessageQueue讀取消息,分配給Handler處理膝蜈。Looper起到連接的作用锅移,將來自不同渠道的消息,聚集在目標線程里處理饱搏。也因此Looper需要確保線程唯一非剃。

MessageQueue
存儲消息對象Message,當Looper向MessageQueue獲取消息推沸,或Handler向其插入數(shù)據(jù)時备绽,決定消息如何提取、如何存儲鬓催。不僅如此肺素,MessageQueue還維護與Native端的連接,也是解決Looper.loop() 阻塞問題的 Java 端的控制器宇驾。

Message
Message包含具體的消息數(shù)據(jù)倍靡,在成員變量target中保存了用來發(fā)送此消息的Handler引用。因此在消息獲得這行時機時课舍,能知道具體由哪一個Handler處理塌西。此外靜態(tài)成員變量sPool,則維護了消息緩存池以復用筝尾。

運行過程

首先捡需,需要構建消息對象。獲取消息對象從Handler.obtainMessage()系列方法可以獲取Message忿等,這一系列的函數(shù)提供了相應對應于Message對象關鍵成員變量對應的函數(shù)參數(shù)栖忠,而無論使用哪一個方法獲取,最終通過Message.obtain()獲取具體的Message對象。

    // 緩存池
    private static Message sPool;
    // 緩存池當前容量
    private static int sPoolSize = 0;
    // 下一節(jié)點
    Message next;

    public static Message obtain() {
        // 確保同步
        synchronized (sPoolSync) {
            if (sPool != null) {
                // 緩存池不為空
                Message m = sPool;
                // 緩存池指向下一個Message節(jié)點
                sPool = m.next;
                // 從緩存池拿到的Message對象與緩存斷開連接
                m.next = null;
                m.flags = 0; // clear in-use flag
                // 緩存池大小減一
                sPoolSize--;
                return m;
            }
        }
        // 緩存吃沒有可用對象庵寞,返回新的Message()
        return new Message();
    }

Message成員變量中存在類型為Message的next狸相,可以看出Message為鏈表結構,而上面代碼從緩存池里獲取消息對象的過程可以用下圖描述:


從緩存池獲取Message.png

創(chuàng)建出消息之后捐川,通過Handler將消息發(fā)送到消息隊列脓鹃,發(fā)送方法有很多,不一一陳列古沥。發(fā)送發(fā)上有兩種:

  1. 將Message對象發(fā)送到
  2. 發(fā)送Runnable瘸右,通過getPostMessage()將Runnable包裝在Message里,表現(xiàn)為成員變量callback
    private static Message getPostMessage(Runnable r) {
        // 獲取Message
        Message m = Message.obtain();
        // 記住Runnale岩齿,等消息獲得執(zhí)行時回調(diào)
        m.callback = r;
        return m;
    }

不管哪種方式發(fā)送太颤,最終消息隊列MessageQueue只知接受到了消息對象Message。而將消息加入到消息隊列盹沈,最終通過enqueueMessage()加入龄章。

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // Message.target 記住 Handler 以明確是由哪一個Handler來處理這個消息的
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 消息入隊
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在將消息加入消息隊列時,有時需要提供延遲信息delayTime乞封,以期未來多久后執(zhí)行做裙,這個值存于 uptimeMillis。

之后肃晚,等待Looper輪詢從消息隊列中讀取消息進行處理锚贱。見Looper.loop()

    public static void loop() {
         // 拿到Looper
        final Looper me = myLooper();
        if (me == null) {
            // 沒調(diào)用prepare初始化Looper,報錯
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 拿到消息隊列
        final MessageQueue queue = me.mQueue;
        ......
        
        for (;;) {
            // 從消息隊列取出下一個信息
            Message msg = queue.next();
            if (msg == null) {
                // 消息為空关串,返回
                return;
            }
            .......
            try {
                // 分發(fā)消息到Handler
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            }
            // 消息回收拧廊,放入緩存池
            msg.recycleUnchecked();
    }

Looper從MessageQueue里取出Message,Message.target則是具體的Hander晋修,Handler.dispatchMessage()將觸發(fā)具體分配邏輯卦绣。此后,將Message回收飞蚓,放入緩存池。

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            // 這個情況說明了次消息為Runnable廊蜒,觸發(fā)Runnable.run()
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                // 指定了Handler的mCallback
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 普通消息處理
            handleMessage(msg);
        }
    }

Handler分配消息分三種情況:

  1. 可以通過Handler發(fā)送Runnable消息到消息隊列趴拧,因此handleCallback()處理這種情況
  2. 可以給Handler設置Callback,當分配消息給Handler時山叮,Callback可以優(yōu)先處理此消息著榴,如果Callback.handleMessage()返回了true,不再執(zhí)行Handler.handleMessage()
  3. Handler.handleMessage()處理具體邏輯

回收則是通過Message.recycleUnchecked()

    void recycleUnchecked() {
        // 這里是將Message各種屬性重置操作
        ......
        
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                // 緩存池還能裝下屁倔,回收到緩存池
                
                // 下面操作將此Message加入到緩存池頭部
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

通過上面的分析脑又,Handler的運行如下圖


Handler 運行.png
  1. Handler 從緩存池獲取Message,發(fā)送到MessageQueue
  2. Looper不斷從MessageQueue讀取消息,通過Message.target.dispatchMessage()觸發(fā)Handler處理邏輯
  3. 回收Message到緩存池

目前來看问麸,可以算是了解了Handler的運行機制往衷,但是對于解答開篇提出的問題,捉襟見肘严卖,需要深入Handler席舍。

Java端與Native端建立連接

實際上,不僅僅是Java端存在Handler機制哮笆,在Native端同樣存在Handler機制来颤。他們通過MessageQueue建立了連接。

一般來說稠肘,Looper通過prepare()進行初始化

    private static void prepare(boolean quitAllowed) {
        // 保證Looper在線程唯一
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 將Looper放入ThreadLocal
        sThreadLocal.set(new Looper(quitAllowed));
    }

在實例化Looper時福铅,需要確保Looper在線程里是唯一的。Handler知道自己的具體Looper對象项阴,而Looper運行在具體的線程里并在此線程里處理消息滑黔。這也是為什么Looper能達到切換線程的目的。Looper線程唯一需要ThreadLocal來確保鲁冯,ThreadLocal的原理拷沸,簡單來說Thread里有類型為ThreadLocalMap的成員threadLocals,通過ThreadLocal能將相應對象放入threadLocals里通過K/V存儲薯演,如此能保證變量在線程范圍內(nèi)存儲撞芍,其中Key為ThreadLocal< T > 。

   private Looper(boolean quitAllowed) {
        // 初始化MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        // 記住當前線程
        mThread = Thread.currentThread();
    }
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        // 與Native建立連接
        mPtr = nativeInit();
    }

在MessageQueue創(chuàng)建時跨扮,通過native方法nativeInit()與Native端建立了連接序无,mPtr為long型變量,存儲一個地址衡创。
方法實現(xiàn)文件位于frameworks/base/core/jni/android_os_MessageQueue.cpp

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);

    // 返回給Java層的mPtr, NativeMessageQueue地址值
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    // 檢查Looper 是否創(chuàng)建
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        // 確保Looper唯一
        Looper::setForThread(mLooper);
    }
}

在Native端創(chuàng)建了NativeMessageQueue帝嗡,同樣也創(chuàng)建了Native端的Looper。在創(chuàng)建NativeMessageQueue后璃氢,將它的地址值返回給了Java層MessageQueue.mPtr哟玷。實際上,Native端Looper實例化時做了更多事情一也。
Nativ端Looper文件位于system/core/libutils/Looper.cpp

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    // 添加到epoll的文件描述符巢寡,線程喚醒事件的fd
    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);
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    .....

    // Allocate the new epoll instance and register the wake pipe.
    // 創(chuàng)建epolle實例,并注冊wake管道
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    struct epoll_event eventItem;
    // 清空椰苟,把未使用的數(shù)據(jù)區(qū)域進行置0操作
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    // 監(jiān)聽可讀事件
    eventItem.events = EPOLLIN;
    // 設置作為喚醒評判的fd
    eventItem.data.fd = mWakeEventFd;
    // 將喚醒事件(mWakeEventFd)添加到epoll實例抑月,意為放置一個喚醒機制
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));

    // 添加各種事件的fd到epoll實例,如鍵盤舆蝴、傳感器輸入等
    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));
        }
    }
}

初次見面谦絮,上面的代碼讓人迷糊题诵,因為缺少了關鍵的知識點,epoll機制层皱。

如何理解epoll機制性锭?

文件、socket奶甘、pipe(管道)等可以進行I/O操作的對象可以視為流篷店。既然是I/O操作,則有read端讀入數(shù)據(jù)臭家,有write端寫入數(shù)據(jù)疲陕。但是兩端并不知道對方進行操作的時機。而epoll則能觀察到哪個流發(fā)生了了I/O事件钉赁,并進行通知蹄殃。
這個過程,就好比你在等快遞你踩,但你不知道快遞什么時候來诅岩,那這時你可以去睡覺,因為你知道快遞送來時一定會打個電話叫醒你带膜,讓你拿快遞吩谦,接著做你想的事情。
epoll有效地降低了CPU的使用膝藕,在線程空間時令其休眠式廷,等有事件到來時再講它喚醒。
epoll機制可以參考這里

在知道了epoll之后芭挽,再來看上面的代碼滑废,就可以理解了。在Native端創(chuàng)建Looper時袜爪,會創(chuàng)建用來喚醒線程的fd —— mWakeEventFd蠕趁,創(chuàng)建epoll實例并注冊管道,清空管道數(shù)據(jù)辛馆,監(jiān)聽可讀事件俺陋。當有數(shù)據(jù)寫入mWakeEventFd描述的文件時,epoll能監(jiān)聽到此事件昙篙,并通知將目標線程喚醒倔韭。

在Java端MessageQueue.mPrt存儲了Native端NativeMassageQueue的地址,可以利用NativeMassageQueue享用此機制瓢对。

發(fā)送數(shù)據(jù)的具體過程

之前說過,Handler發(fā)送消息時胰苏,最終通過MessageQueue.enqueueMessage向消息隊列中插入消息硕蛹,下面為具體代碼

   boolean enqueueMessage(Message msg, long when) {
        ......

        synchronized (this) {
            ......
            
            // 記錄消息處理的時間
            msg.when = when;
            Message p = mMessages;
            // 喚醒線程的標志位
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 這里三種情況:
                // 1醇疼、目標消息隊列是空隊列
                // 2、插入的消息處理時間等于0
                // 3法焰、插入的消息處理時間小于保存在消息隊列頭的消息處理時間
                // 這三種情況都插入列表頭
                msg.next = p;
                mMessages = msg;
                // mBlocked 表示當前線程是否睡眠
                needWake = mBlocked;
            } else {
                // 這里則說明消息處理時間大于消息列表頭的處理時間秧荆,因此需要找到合適的插入位置
               
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 這里的循環(huán)是找到消息的插入位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // 到鏈表尾,或處理時間早于p的時間
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        // 如果插入的消息在目標隊列中間埃仪,是不需要檢查改變線程喚醒狀態(tài)的
                        needWake = false;
                    }
                }
                // 插入到消息隊列
                msg.next = p; 
                prev.next = msg;
            }

            if (needWake) {
                // 喚醒線程
                nativeWake(mPtr);
            }
        }
        return true;
    }

消息隊列里的消息也是以鏈表形式存儲乙濒,存儲順序則按照處理的時間順序。那么在向消息隊列里插入數(shù)據(jù)時卵蛉,存在四種情況:

  1. 目標消息隊列是空隊列
  2. 插入的消息處理時間等于0
  3. 插入的消息處理時間小于保存在消息隊列頭的消息處理時間
  4. 插入的消息處理時間大于消息隊列頭的消息處理時間

前三種情況颁股,將消息插入消息隊列頭的位置,在這種情況下傻丝,因為不能保證當前消息是否達到了可以處理的狀態(tài)甘有,且如果此時線程是睡眠的,則需要調(diào)用nativeWake()將其線程喚醒葡缰。后一種情況亏掀,則需要找到消息的插入位置,因不影響線程狀態(tài)而需要改變線程狀態(tài)泛释。

插入消息如圖


插入消息到隊列.png

mPtr保存了NativeMessageQueue的地址滤愕,所以Native可以知道具體操作的NativeMessageQueue,當前用它來喚醒線程怜校,實際調(diào)用鏈為
MessageQueue.cpp.nativeWake()
-> MessageQueue.cpp.wake()
-> Looper.cpp.wake()

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    // 向管道寫入一個新數(shù)據(jù)间影,這樣管道因為發(fā)生了IO事件被喚醒
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
                    mWakeEventFd, strerror(errno));
        }
    }
}

實現(xiàn)也簡單,向mWakeEventFd文件里寫入一個數(shù)據(jù)韭畸,根據(jù)epoll機制監(jiān)聽到此次I/O事件宇智,將線程喚醒。

消息讀取

Looper不斷從MessageQueue讀取消息進行處理胰丁,通過MessageQueue.next()進行讀取随橘。

    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            // 獲取NativeMessageQueue地址失敗,無法正常使用epoll機制
            return null;
        }

        // 用來保存注冊到消息隊列中的空閑消息處理器(IdleHandler)的個數(shù)
        int pendingIdleHandlerCount = -1; 
        // 如果這個變量等于0锦庸,表示即便消息隊列中沒有新的消息需要處理机蔗,當前
        // 線程也不要進入睡眠等待狀態(tài)。如果值等于-1甘萧,那么就表示當消息隊列中沒有新的消息
        // 需要處理時萝嘁,當前線程需要無限地處于休眠等待狀態(tài),直到它被其它線程喚醒為止
        int nextPollTimeoutMillis = 0;
        for (;;) {
            ......

            // 檢查當前線程的消息隊列中是否有新的消息需要處理扬卷,嘗試進入休眠
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // 當前時間
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                // mMessages 表示當前線程需要處理的消息
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 找到有效的Message
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    /**
                     * 檢查當前時間和消息要被處理的時間牙言,如果小于當前時間,說明馬上要進行處理
                     */
                     
                    if (now < msg.when) {
                        // 還沒達到下一個消息需要被處理的時間怪得,計算需要休眠的時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 有消息需要處理
                        // 不要進入休眠
                        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 {
                    // 沒有更多消息咱枉,休眠時間無限
                    nextPollTimeoutMillis = -1;
                }

                ......
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    // 獲取IdleHandler數(shù)
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }

                if (pendingIdleHandlerCount <= 0) {
                    // 沒有IdleHandler需要處理卑硫,可直接進入休眠
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 如果沒有更多要進行處理的消息,在休眠之前蚕断,發(fā)送線程空閑消息給已注冊到消息隊列中的IdleHandler對象來處理
            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 {
                    // 處理對應邏輯欢伏,并由自己決定是否保持激活狀態(tài)
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    // 不需要存活,移除
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 重置IdleHandler數(shù)量
            pendingIdleHandlerCount = 0;

            /**
             * 這里置0亿乳,表示下一次循環(huán)不能馬上進入休眠狀態(tài)硝拧,因為IdleHandler在處理事件的時間里,
             * 有可能有新的消息發(fā)送來過來葛假,需要重新檢查障陶。
             */
            nextPollTimeoutMillis = 0;
        }
    }

分為兩種情況處理:
取到消息Message時
需要查看當前時間是否達到了Message處理的時間,如果達到了則返回桐款,改變mMessages指向下一消息咸这。如果沒達到,則計算要達到處理的時間魔眨,還需要休眠多久媳维,并進行休眠
沒有更多Message時
當消息隊列里沒有消息時,則會檢查是否有IdleHandler需要處理遏暴。在Handler機制里侄刽,允許添加一些事件,在線程空閑時進行處理朋凉,表現(xiàn)為IdleHandler州丹,可以通過MessageQueue.addIdleHandler添加。當有IdleHandler需要處理杂彭,則在IdleHandler處理完后墓毒,線程不能馬上進入休眠狀態(tài),在此期間可能已有新消息加入消息隊列亲怠,需要重新做檢查所计。如果沒有IdleHandler,則可以進入休眠团秽。

線程休眠調(diào)用鏈為
NativeMessageQueue.nativePollOnce()
-> NativeMessageQueue.pollOnce()
-> Looper.pollOnce()
-> Looper.pollInner()

int Looper::pollInner(int timeoutMillis) {
......
    // 這個是用來監(jiān)聽實例化時創(chuàng)建的epoll實例的文件描述符的IO讀寫事件
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 如果沒有事件主胧,進入休眠,timeoutMillis為休眠事件
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ......
    
    /**
    * 檢測是哪一個文件描述符發(fā)生了IO讀寫事件
    */
    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) {
            // 如果文件描述符為mWakeEventFd习勤,并且讀寫事件類型為EPOLLIN踪栋,說明
            // 當前線程所關聯(lián)的一個管道被寫入了一個新的數(shù)據(jù)
            // 喚醒
                awoken();
            }                
        }
        ......
    }
}

Java層提供了線程休眠時間timeoutMillis,通過epoll_wait()讓線程進行休眠图毕。當線程被喚醒后夷都,查看文件描述符,如果為mWakeEventFd并且為I/O事件予颤,則說明當當前線程所關聯(lián)的一個管道被寫入了一個新的數(shù)據(jù)囤官,通過awoken()處理厢破。當前線程已是喚醒狀態(tài),awoken()則是將管道中的數(shù)據(jù)讀出達到清理目的治拿,但并不關心數(shù)據(jù)什么。核心目的是喚醒線程笆焰。

總結

Handler機制更具體的原理如圖:


Handler運行機制(包含Native).png
  1. Looper通過prepare()創(chuàng)建劫谅,借助ThreadLocal保證線程唯一,如果沒有進行prepare()嚷掠,調(diào)用Loop()會拋出異常
  2. Looper在實例化時創(chuàng)建MessageQueue捏检,MessageQueue與NativeMessageQueue建立連接,NativeMessageQueue存儲地址存于MessageQueue.mPtr不皆。Native端也建立了Handler機制贯城,使用epoll機制。Java端借由NativeMessageQueue能達到使用epoll機制的目的
  3. 從Message緩存里獲取Message霹娄,緩存為鏈表存儲能犯,從頭出取出,并且Message在回收時也是插入頭部犬耻。如果存緩存里取不到踩晶,則新建
  4. Handler向MessageQueue插入消息,如果消息插入消息隊列頭部枕磁,需要喚醒線程渡蜻;如果插入消息隊列中,無需改變線程狀態(tài)
  5. Looper.loop() 不斷從消息隊列獲取消息计济,消息隊列獲取消息時會出現(xiàn)兩種情況茸苇。如果取到消息,但沒達到處理時間沦寂,則讓線程休眠学密;如果沒有更多消息,則在處理IdleHandler事后凑队,在考慮讓線程進入休眠
  6. Message達到了可處理狀態(tài)则果,則有Handler處理,處理時考慮三種情況漩氨,消息內(nèi)容為Runnable時西壮、設置了Handle.Callback時、普通消息時叫惊,對應調(diào)用為Message.callback.run() 款青、 Callback.handleMessage()、Handler.handleMessage()
  7. 從Handler機制里霍狰,epoll可以簡單理解為抡草,當Handler機制沒有消息要處理時饰及,讓線程進入休眠,當Handler機制有消息要處理時康震,將線程喚起燎含。通過Native端監(jiān)聽mWakeEventFd的I/O事件實現(xiàn)

答疑

這里對文章一開始的問題進行回答

Handler如何保證運行在目標線程

Looper在實例化時通過ThreadLocal保證線程唯一。Looper運行在目標線程中腿短,接收Handler發(fā)送的消息并進行處理屏箍。Message創(chuàng)建時與具體的Handler進行了關聯(lián),因此能知道由哪一個Handler進行相應橘忱。

Handler容易造成內(nèi)存泄漏的原因

Message.target存有Handler的引用赴魁,以知道自身由哪一個Handler來處理。因此钝诚,當Handler為非靜態(tài)內(nèi)部類颖御、或持有關鍵對象的其它表現(xiàn)形式時(如Activity常表現(xiàn)為Context),就引用了其它外部對象凝颇。當Message得不到處理時潘拱,被Handler持有的外部對象會一直處于內(nèi)存泄漏狀態(tài)。

loop()為什么不會阻塞祈噪,CPU為什么不會忙等

通過epoll機制監(jiān)聽文件I/O時間泽铛,在有Message需要處理時,寫入數(shù)據(jù)以喚醒線程辑鲤;在沒有Message要處理時盔腔,讓線程進入休眠狀態(tài)。

MessageQueue如何存儲

以鏈表存儲月褥,MessageQueue.mMessages指向頭節(jié)點弛随。

Message如何緩存

以鏈表緩存,取出時從頭部取出宁赤,回收時插入頭部舀透。

什么是線程空閑消息

Handler提供的一種機制,允許添加IdleHandler事件决左。并在沒有更多Message要處理愕够,要進入休眠前,讓IdleHandler做具體事情佛猛,也就是線程空間時處理的事件惑芭。

子線程如何使用Handler機制

只要保證在子線程先執(zhí)行Looper.prepare()再使用Looper.Loop()即可,但實際應用場景不多继找。 順便提一句遂跟,主線程初始化Looper操作在ActivityTread.main()里觸發(fā),簡單了解即可。

參考

《Android 系統(tǒng)源代碼情景分析》 —— 第十三章
Android Handler機制10之Native的實現(xiàn)
我讀過的最好的epoll講解--轉(zhuǎn)自”知乎“

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幻锁,一起剝皮案震驚了整個濱河市凯亮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哄尔,老刑警劉巖假消,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岭接,居然都是意外死亡置谦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門亿傅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瘟栖,你說我怎么就攤上這事葵擎。” “怎么了半哟?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵酬滤,是天一觀的道長。 經(jīng)常有香客問我寓涨,道長盯串,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任戒良,我火速辦了婚禮体捏,結果婚禮上,老公的妹妹穿的比我還像新娘糯崎。我一直安慰自己几缭,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布沃呢。 她就那樣靜靜地躺著年栓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪薄霜。 梳的紋絲不亂的頭發(fā)上某抓,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音惰瓜,去河邊找鬼否副。 笑死,一個胖子當著我的面吹牛鸵熟,可吹牛的內(nèi)容都是我干的副编。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痹届!你這毒婦竟也來了呻待?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤队腐,失蹤者是張志新(化名)和其女友劉穎蚕捉,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柴淘,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡迫淹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了为严。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片敛熬。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖第股,靈堂內(nèi)的尸體忽然破棺而出应民,到底是詐尸還是另有隱情,我是刑警寧澤夕吻,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布诲锹,位于F島的核電站,受9級特大地震影響涉馅,放射性物質(zhì)發(fā)生泄漏归园。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一稚矿、第九天 我趴在偏房一處隱蔽的房頂上張望庸诱。 院中可真熱鬧,春花似錦晤揣、人聲如沸偶翅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聚谁。三九已至,卻和暖如春滞诺,著一層夾襖步出監(jiān)牢的瞬間形导,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工习霹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留朵耕,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓淋叶,卻偏偏與公主長得像阎曹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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