Handler

  • Handler:Android SDK 提供給開(kāi)發(fā)者方便進(jìn)行異步消息處理的類(面試都會(huì)問(wèn)原理)
    image.png
  • messageQueue 消息隊(duì)列: 先進(jìn)先出。管理Message對(duì)象,需要注意的是 messageQueue消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)并不是隊(duì)列,它是由單鏈表來(lái)構(gòu)成的

整個(gè)流程: 首先為主線程創(chuàng)建一個(gè)looper對(duì)象,每一個(gè)線程只能有一個(gè)looper。而在創(chuàng)建looper對(duì)象的時(shí)候它內(nèi)部構(gòu)造方法中又會(huì)創(chuàng)建一個(gè)messageQueue對(duì)象。而創(chuàng)建handler的時(shí)候我們會(huì)取出當(dāng)前線程的looper,然后通過(guò)looper去輪詢messageQueue中的message。handler每發(fā)送一條消息,相當(dāng)于在messageQueue中添加一條消息。最后通過(guò)looper當(dāng)中的消息循環(huán)取得消息隊(duì)列中的message交給handler去進(jìn)行處理。

image.png

通過(guò)源碼可以發(fā)現(xiàn),創(chuàng)建handler的時(shí)候。會(huì)調(diào)用一個(gè)Looper.myLooper()方法,如果looper沒(méi)走looper.prepare,則會(huì)報(bào)異常。

image.png

image.png

我們可以看到 looper通過(guò)sThreadLocal這個(gè)容器來(lái)存放。如果創(chuàng)建了就會(huì)提示一個(gè)looper只能創(chuàng)建一次。否則則將這個(gè)looper對(duì)象放到sThreadLoacl中,sThreadLoacl我們也叫作線程本地變量。它能為每個(gè)變量在每個(gè)線程都創(chuàng)建一個(gè)副本,這樣就能保證每個(gè)線程都能訪問(wèn)自己的變量。所以說(shuō)handler必須得有一個(gè)指定的looper對(duì)象。而創(chuàng)建完looper之后,looper會(huì)創(chuàng)建消息隊(duì)列,之后賦值給handlermQueue。三者做一個(gè)捆綁。而在looper構(gòu)造方法中,我們也看到了他new 了一個(gè) messageQueue對(duì)象。

現(xiàn)在我們來(lái)看看Looper.myLooper()方法

image.png

通過(guò)上面所說(shuō)的,sThreadLoacl是線程所持有的容器。所以通過(guò)他的get方法就能獲得looper對(duì)象

再看sendEmptyMessage還是sendEmptyMessageDelayed 最終都會(huì)走到一個(gè)enqueueMessage方法。這個(gè)方法的就是將我們的message添加到消息隊(duì)列當(dāng)中。

image.png

接下來(lái)我們?cè)倏?em>looper這個(gè)類

  /**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        final long end;
        try {
            msg.target.dispatchMessage(msg);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (slowDispatchThresholdMs > 0) {
            final long time = end - start;
            if (time > slowDispatchThresholdMs) {
                Slog.w(TAG, "Dispatch took " + time + "ms on "
                        + Thread.currentThread().getName() + ", h=" +
                        msg.target + " cb=" + msg.callback + " msg=" + msg.what);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

for (;;)是個(gè)死循環(huán),意思是looper輪詢器會(huì)不斷的去輪詢消息。如果消息不為空,他就會(huì)執(zhí)行 msg.target.dispatchMessage(msg);來(lái)處理消息 其中msg.target可以看上面一張圖 enqueueMessage的方法。我們可以知道,這個(gè)就是我們的handler對(duì)象

我們回到handler類看這個(gè)方法:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

他會(huì)先判斷msgcallback是不是為空,如果不為空他就會(huì)執(zhí)行 handleCallback(msg);那么這個(gè) callback 是什么呢?其實(shí)他就是我們的runnable。所以說(shuō)不管handler最終post了什么。最終他傳遞的都是一個(gè)runnable參數(shù),我們可以點(diǎn)進(jìn)去看下他的方法如下:

private static void handleCallback(Message message) {
    message.callback.run();
}

就是調(diào)用了runnablerun方法

接著我們繼續(xù)看如果msgcallback為空的情況下,他會(huì)用mCallback.handleMessage(msg)來(lái)處理消息。那么這個(gè)mCallback又是什么呢?

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}

我們?cè)谠创a中發(fā)現(xiàn)他是一個(gè)接口,而他的接口中呢只有一個(gè)我們特別熟悉的方法handleMessage。用來(lái)處理消息。

主線程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢? 其實(shí)不然,這里就涉及到Linux pipe/e****poll機(jī)制,簡(jiǎn)單說(shuō)就是在主線程的MessageQueue沒(méi)有消息時(shí),便阻塞在loop的queue.next()中的nativePollOnce()方法里,詳情見(jiàn)Android消息機(jī)制1-Handler(Java層),此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往pipe管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作。這里采用的epoll機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮?,本質(zhì)同步I/O,即讀寫(xiě)是阻塞的。 所以說(shuō),主線程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源。

下面總結(jié)幾點(diǎn)

  • 1.Lopper 類主要是為每個(gè)線程開(kāi)啟的單獨(dú)的消息循環(huán)。默認(rèn)情況下Android新誕生的線程是沒(méi)有開(kāi)啟消息循環(huán)的,所以說(shuō)要在線程中使用Handler,你必須先調(diào)用Looper.prepare()方法。但是主線程除外,因?yàn)橹骶€程中系統(tǒng)已為我們創(chuàng)建了Looper對(duì)象**
  • 2.handler 我們可以看作是Looper的一個(gè)接口,用來(lái)像Looper指定的messageQueue 發(fā)送消息
  • 3.在非主線程中直接new Handler()是不可以的。原理:Handler他需要發(fā)送消息到MessageQueue,而你所在的線程中沒(méi)有MessageQueue,而MessageQueueLooper管理,你想創(chuàng)建Handler,你必須先創(chuàng)建好Looper。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 【Android Handler 消息機(jī)制】 前言 在Android開(kāi)發(fā)中,我們都知道不能在主線程中執(zhí)行耗時(shí)的任務(wù)...
    Rtia閱讀 5,076評(píng)論 1 28
  • 前言 在Android開(kāi)發(fā)的多線程應(yīng)用場(chǎng)景中,Handler機(jī)制十分常用 今天,我將手把手帶你深入分析Handle...
    BrotherChen閱讀 528評(píng)論 0 0
  • 談到Android開(kāi)發(fā),就離不開(kāi)線程操作,而我們需要在子線程中更新UI,一般有以下幾種方式: 1、view.pos...
    StChris閱讀 1,417評(píng)論 0 1
  • Android消息處理機(jī)制估計(jì)都被寫(xiě)爛了,但是依然還是要寫(xiě)一下,因?yàn)锳ndroid應(yīng)用程序是通過(guò)消息來(lái)驅(qū)動(dòng)的,An...
    一碼立程閱讀 4,591評(píng)論 4 36
  • Android中的消息機(jī)制,消息的發(fā)送和接收過(guò)程以及與線程之間的關(guān)系。雖然我們經(jīng)常使用這些基礎(chǔ)的東西,但對(duì)于其內(nèi)部...
    Sunny君907閱讀 703評(píng)論 0 1

友情鏈接更多精彩內(nèi)容