Handler 的工作原理

參考資料
gityuan

一、Handler原理

Handler 是 Android 中線(xiàn)程間通信的組件岛都。在異步線(xiàn)程中使用前需要先調(diào)用 Looper.prepare 為當(dāng)前線(xiàn)程準(zhǔn)備 Looper 和 Looper 持有的消息隊(duì)列律姨,然后通過(guò)創(chuàng)建的 Handler 對(duì)象像這個(gè)消息隊(duì)列里插入消息,調(diào)用 Looper.loop 方法啟動(dòng) loop 循環(huán)處理消息臼疫。

創(chuàng)建 Looper 對(duì)象

當(dāng)調(diào)用 Looper.perpare 函數(shù)時(shí)择份,在 Java 層會(huì)為當(dāng)前線(xiàn)程創(chuàng)建一個(gè) Looper 以及 MessageQueue 對(duì)象。創(chuàng)建 MessageQueue 時(shí),同時(shí)還會(huì)調(diào)用 nativeInit 函數(shù)去創(chuàng)建 native 層的 MessageQueue 和 Looper 對(duì)象烫堤。Native 層的 Looper 對(duì)象會(huì)去創(chuàng)建一個(gè) EventFd 對(duì)象荣赶。它是消息循環(huán)的核心凤价。

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

創(chuàng)建 Handler 對(duì)象

  1. 首先判斷是否為當(dāng)前線(xiàn)程準(zhǔn)備了 Looper 對(duì)象。如果沒(méi)有會(huì)拋出異常
  2. 通過(guò)調(diào)用 Looper 的 prepare 方法準(zhǔn)備當(dāng)前線(xiàn)程環(huán)境的 Looper 對(duì)象
  3. 獲取 Looper 中維護(hù)的消息隊(duì)列
 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
 }
 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 Handler(Callback callback, boolean async) {
        ....
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

2.發(fā)送消息

  1. 發(fā)送消息本質(zhì)上是像 Looper 所持有的 MessageQueue 隊(duì)列中插入消息
  2. 在將消息入隊(duì)前對(duì)樣會(huì)把自身的引用賦值給這個(gè) Message讯壶,在取出消息時(shí)就可以方便的知道消息是由哪一個(gè) Handler 發(fā)送的
 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ......
        return enqueueMessage(queue, msg, uptimeMillis);
  }
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue 插入消息時(shí)會(huì)對(duì)消息進(jìn)行排序

  1. 如果當(dāng)前隊(duì)列沒(méi)有消息料仗,新消息的延時(shí)為0,或者新消息的延時(shí)時(shí)間要比當(dāng)前消隊(duì)列第一個(gè)的延時(shí)時(shí)間短伏蚊,都會(huì)將這個(gè)新的消息作為第一個(gè)消息優(yōu)先被取出
  2. 否則就將消息消息鏈接在之前的消息后面
  3. 如果之前的 Looper 在等待狀態(tài)會(huì)喚起 Looper

喚醒消息是通過(guò)調(diào)用 nativeWake 函數(shù)立轧,它會(huì)調(diào)用 native looper 對(duì)象的 wake 函數(shù)向 EventFD 寫(xiě)入一個(gè)數(shù)字,喚醒被 epoll_wait 阻塞的代碼

 boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            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;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 向 EventFD 中寫(xiě)入一個(gè)數(shù)字躏吊,喚醒 epoll_wait 阻塞的地方
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

JNI 層

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

核心就是向 mWakeEventFd 寫(xiě)入了一個(gè)數(shù)字 write(mWakeEventFd.get()

void NativeMessageQueue::wake() {
    mLooper->wake();
}

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

    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
                             mWakeEventFd.get(), nWrite, strerror(errno));
        }
    }
}

3. Looper 取出消息

  1. Looper 通過(guò)調(diào)用 MessageQueue 的 next 函數(shù)取出消息
  2. 通過(guò)消息中持有的 Handler 引用觸發(fā)對(duì)應(yīng) Handler 的 dispatchMessage 執(zhí)行對(duì)應(yīng)的任務(wù)
public static void loop() {
      final MessageQueue queue = me.mQueue;
         for (;;) {
            // 如果沒(méi)有消息要發(fā)送會(huì)讓出 CPU
            Message msg = queue.next(); 
            .....
            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
         }
}

MessageQueue 的 next 方法

  1. 第一次進(jìn)入時(shí)氛改,會(huì)判斷隊(duì)列中是否有消息,如果沒(méi)有消息比伏,會(huì)將延時(shí)時(shí)間設(shè)置成 -1胜卤,再次循環(huán)時(shí)會(huì)調(diào)用 nativePollOnce 函數(shù)進(jìn)入無(wú)限的休眠期。nativePollOnce 函數(shù)本質(zhì)上是調(diào)用 native Looper 對(duì)象的 pollOnce 函數(shù)通過(guò)調(diào)用 epoll_wait 阻塞在當(dāng)前的位置赁项。
  2. 如果當(dāng)前有消息葛躏,會(huì)取出消息計(jì)算它的發(fā)送時(shí)間,如果消息的時(shí)間比當(dāng)前時(shí)間小就立即發(fā)送悠菜,如果消息時(shí)間比當(dāng)前的時(shí)間大就計(jì)算延時(shí)的時(shí)間舰攒,進(jìn)入休眠。等待休眠時(shí)間到以后再發(fā)送
  Message next() {
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

  }

native 的 Looper

  .....
  struct epoll_event eventItems[EPOLL_MAX_EVENTS];
  int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
  .....

4.處理消息結(jié)果

Handler 的 handleMessage 方法

  1. 首先判斷 Message 是否指定了 callback
  2. 如果沒(méi)有就判斷是否有全局的 callback
  3. 如果沒(méi)有全局的 callback 就調(diào)用重寫(xiě)后的 handleMessage 方法悔醋。
 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

二摩窃、常見(jiàn)問(wèn)題

1. Handler 延時(shí)消息的原理

Handler 消息的延時(shí)是處理延時(shí),任何發(fā)送的消息都是第一時(shí)間入隊(duì)的芬骄,在發(fā)送消息時(shí)會(huì)和當(dāng)前時(shí)間做比較猾愿,如果需要延時(shí)就會(huì)計(jì)算延時(shí)時(shí)間,調(diào)用 epoll_wait 阻塞住账阻,等待延時(shí)時(shí)間達(dá)到后喚醒當(dāng)前的線(xiàn)程發(fā)送消息

2. IdleHandler原理

IdleHandler 可以在當(dāng)前線(xiàn)程的消息隊(duì)列空閑時(shí)做一些事情蒂秘。它的原理是向 IdleHandler 的隊(duì)列中插入了一個(gè)消息。當(dāng) MessageQueue 去查找隊(duì)里中的消息時(shí)淘太,如果隊(duì)列中沒(méi)有消息或者消息還沒(méi)有達(dá)到發(fā)送的時(shí)間材彪,就會(huì)執(zhí)行 IdleHandler 隊(duì)列中的消息

      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        .......

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
  1. IdleHandler 可以做一些延時(shí)初始化的任務(wù)。
  2. 當(dāng)一個(gè) View 頻繁接受消息并刷新時(shí)琴儿,可以使用 IdleHandler段化,讓任務(wù)隊(duì)里先去處理消息,等到線(xiàn)程空閑時(shí)使用 IdleHandler 去更新最新的數(shù)據(jù)造成。

3. 子線(xiàn)程和主線(xiàn)程 Looper 的區(qū)別

子線(xiàn)程的 Looper 可以退出而主線(xiàn)程的是不可以的显熏。

**4. 應(yīng)用線(xiàn)程進(jìn)入 looper 循環(huán)為什么沒(méi)有 ANR **

ANR 是 Android 中的一種機(jī)制,它是在應(yīng)用沒(méi)有按時(shí)完成 AMS 指定的任務(wù)才觸發(fā)的晒屎。組件在創(chuàng)建時(shí)會(huì)向 AMS 申請(qǐng)開(kāi)始計(jì)時(shí)喘蟆,當(dāng)完成創(chuàng)建后會(huì)通知 AMS 取消計(jì)時(shí)缓升。

進(jìn)入 looper 循環(huán)后,AMS 會(huì)在 looper 線(xiàn)程中通過(guò)主線(xiàn)程的 Handler 發(fā)送消息給主線(xiàn)程去執(zhí)行任務(wù)。所以即使進(jìn)入 looper 循環(huán), AMS 仍然可以和主線(xiàn)程交互蕴轨。這也是為什么 ApplicationThread 接到任務(wù)后還需要發(fā)送 Handler 消息給 ActivityThread 去執(zhí)行任務(wù)港谊。

如果主線(xiàn)程中有其他任務(wù)導(dǎo)致 AMS 的任務(wù)被延時(shí),或者 AMS 的任務(wù)本身很耗時(shí)才會(huì)觸發(fā) ANR

5.消息屏障

消息屏障是 Handler 優(yōu)先執(zhí)行異步消息的一種機(jī)制橙弱。在 android 中 choreographer 類(lèi)在刷新 view 時(shí)使用到了

在子線(xiàn)程中setText可能成功么

在setTextView的時(shí)候會(huì)調(diào)用requestLayout會(huì)做線(xiàn)程檢查,如果不是的主線(xiàn)程會(huì)拋出異常歧寺,但是不是絕對(duì)的,如果invalid 比 requestLayout執(zhí)行快,那么不會(huì)拋出異常棘脐。

創(chuàng)建Handler時(shí)傳入Callback有什么影響

Handler在取消息時(shí)會(huì)優(yōu)先查找是否設(shè)置了callback(通過(guò)msg設(shè)置callback,或者通過(guò)Handler構(gòu)造方法直接傳入),性能會(huì)更好

image.png

為什么post一個(gè)runnable原理(如何讓handleMessage快速執(zhí)行)

使用handler直接post一個(gè)runnable對(duì)象,這個(gè)runnbale對(duì)象會(huì)被包裝成一個(gè)message對(duì)象,這個(gè)runable會(huì)作為message的callback方法斜筐,結(jié)合上面來(lái)看如果message有callback那么會(huì)不經(jīng)過(guò)判斷執(zhí)行執(zhí)行handleMessage方法。

一直死循環(huán)不會(huì)造成cpu浪費(fèi)么

在沒(méi)有消息的時(shí)候,會(huì)阻塞在nativePollOnce方法上蛀缝,讓出cpu資源顷链,進(jìn)入休眠狀態(tài),當(dāng)有新的任務(wù)進(jìn)入時(shí)會(huì)重新喚醒cpu

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末屈梁,一起剝皮案震驚了整個(gè)濱河市嗤练,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌在讶,老刑警劉巖煞抬,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異真朗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)僧诚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)遮婶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人湖笨,你說(shuō)我怎么就攤上這事旗扑。” “怎么了慈省?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵臀防,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我边败,道長(zhǎng)袱衷,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任笑窜,我火速辦了婚禮致燥,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘排截。我一直安慰自己嫌蚤,他們只是感情好辐益,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著脱吱,像睡著了一般智政。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箱蝠,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天续捂,我揣著相機(jī)與錄音,去河邊找鬼抡锈。 笑死疾忍,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的床三。 我是一名探鬼主播一罩,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼撇簿!你這毒婦竟也來(lái)了聂渊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤四瘫,失蹤者是張志新(化名)和其女友劉穎汉嗽,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體找蜜,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饼暑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洗做。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弓叛。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖诚纸,靈堂內(nèi)的尸體忽然破棺而出撰筷,到底是詐尸還是另有隱情,我是刑警寧澤畦徘,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布毕籽,位于F島的核電站,受9級(jí)特大地震影響井辆,放射性物質(zhì)發(fā)生泄漏关筒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一杯缺、第九天 我趴在偏房一處隱蔽的房頂上張望平委。 院中可真熱鬧,春花似錦夺谁、人聲如沸廉赔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜡塌。三九已至碉纳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馏艾,已是汗流浹背劳曹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琅摩,地道東北人铁孵。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像房资,于是被迫代替她去往敵國(guó)和親蜕劝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 在前一篇文章搞懂Handler的使用與工作流程中講了下Handler的使用方式轰异,并簡(jiǎn)單介紹了Handler實(shí)現(xiàn)線(xiàn)程...
    HelloTu閱讀 319評(píng)論 0 0
  • Handler的使用 在日常開(kāi)發(fā)過(guò)程中岖沛,Handler 多用來(lái)進(jìn)行切換線(xiàn)程的操作。一般的場(chǎng)景是搭独,在子線(xiàn)程中做完了耗...
    AndroidHint閱讀 291評(píng)論 0 1
  • 面試場(chǎng)景 平時(shí)開(kāi)發(fā)用到其他線(xiàn)程嗎牙肝?都是如何處理的唉俗? 基本都用 RxJava 的線(xiàn)程調(diào)度切換,嗯對(duì)配椭,就是那個(gè) obs...
    nanchen2251閱讀 4,898評(píng)論 2 39
  • 文章獨(dú)家授權(quán)公眾號(hào):碼個(gè)蛋更多分享:http://www.cherylgood.cn 談到Android開(kāi)發(fā)虫溜,就離...
    Angels_安杰閱讀 1,628評(píng)論 0 3
  • 前言 在Android開(kāi)發(fā)的多線(xiàn)程應(yīng)用場(chǎng)景中,Handler機(jī)制十分常用 今天颂郎,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 474評(píng)論 0 0