溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Handler的作用有哪些

發(fā)布時(shí)間:2021-10-15 11:59:42 來(lái)源:億速云 閱讀:271 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“Handler的作用有哪些”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Handler的作用有哪些”吧!

Handler被設(shè)計(jì)出來(lái)的原因?有什么用?

一種東西被設(shè)計(jì)出來(lái)肯定就有它存在的意義,而Handler的意義就是切換線程。

作為Android消息機(jī)制的主要成員,它管理著所有與界面有關(guān)的消息事件,常見(jiàn)的使用場(chǎng)景有:

  • 跨進(jìn)程之后的界面消息處理。

比如Activity的啟動(dòng),就是AMS在進(jìn)行進(jìn)程間通信的時(shí)候,通過(guò)Binder線程 將消息發(fā)送給ApplicationThread的消息處理者Handler,然后再將消息分發(fā)給主線程中去執(zhí)行。

  • 網(wǎng)絡(luò)交互后切換到主線程進(jìn)行UI更新

當(dāng)子線程網(wǎng)絡(luò)操作之后,需要切換到主線程進(jìn)行UI更新。

總之一句話,Hanlder的存在就是為了解決在子線程中無(wú)法訪問(wèn)UI的問(wèn)題。 

為什么建議子線程不訪問(wèn)(更新)UI?

因?yàn)?code>Android中的UI控件不是線程安全的,如果多線程訪問(wèn)UI控件那還不亂套了。

那為什么不加鎖呢?

  • 會(huì)降低UI訪問(wèn)的效率。本身UI控件就是離用戶比較近的一個(gè)組件,加鎖之后自然會(huì)發(fā)生阻塞,那么UI訪問(wèn)的效率會(huì)降低,最終反應(yīng)到用戶端就是這個(gè)手機(jī)有點(diǎn)卡。
  • 太復(fù)雜了。本身UI訪問(wèn)時(shí)一個(gè)比較簡(jiǎn)單的操作邏輯,直接創(chuàng)建UI,修改UI即可。如果加鎖之后就讓這個(gè)UI訪問(wèn)的邏輯變得很復(fù)雜,沒(méi)必要。

所以,Android設(shè)計(jì)出了 單線程模型 來(lái)處理UI操作,再搭配上Handler,是一個(gè)比較合適的解決方案。 

子線程訪問(wèn)UI的 崩潰原因 和 解決辦法?

崩潰發(fā)生在ViewRootImpl類的checkThread方法中:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }  

其實(shí)就是判斷了當(dāng)前線程 是否是 ViewRootImpl創(chuàng)建時(shí)候的線程,如果不是,就會(huì)崩潰。

而ViewRootImpl創(chuàng)建的時(shí)機(jī)就是界面被繪制的時(shí)候,也就是onResume之后,所以如果在子線程進(jìn)行UI更新,就會(huì)發(fā)現(xiàn)當(dāng)前線程(子線程)和View創(chuàng)建的線程(主線程)不是同一個(gè)線程,發(fā)生崩潰。

解決辦法有三種:

  • 在新建視圖的線程進(jìn)行這個(gè)視圖的UI更新,主線程創(chuàng)建View,主線程更新View。
  • 在     ViewRootImpl創(chuàng)建之前進(jìn)行子線程的UI更新,比如onCreate方法中進(jìn)行子線程更新UI。
  • 子線程切換到主線程進(jìn)行UI更新,比如     Handler、view.post方法。

MessageQueue是干嘛呢?用的什么數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)?

看名字應(yīng)該是個(gè)隊(duì)列結(jié)構(gòu),隊(duì)列的特點(diǎn)是什么?先進(jìn)先出,一般在隊(duì)尾增加數(shù)據(jù),在隊(duì)首進(jìn)行取數(shù)據(jù)或者刪除數(shù)據(jù)。

Hanlder中的消息似乎也滿足這樣的特點(diǎn),先發(fā)的消息肯定就會(huì)先被處理。但是,Handler中還有比較特殊的情況,比如延時(shí)消息。

延時(shí)消息的存在就讓這個(gè)隊(duì)列有些特殊性了,并不能完全保證先進(jìn)先出,而是需要根據(jù)時(shí)間來(lái)判斷,所以Android中采用了鏈表的形式來(lái)實(shí)現(xiàn)這個(gè)隊(duì)列,也方便了數(shù)據(jù)的插入。

來(lái)一起看看消息的發(fā)送過(guò)程,無(wú)論是哪種方法發(fā)送消息,都會(huì)走到sendMessageDelayed方法

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        return enqueueMessage(queue, msg, uptimeMillis);
    }
 

sendMessageDelayed方法主要計(jì)算了消息需要被處理的時(shí)間,如果delayMillis為0,那么消息的處理時(shí)間就是當(dāng)前時(shí)間。

然后就是關(guān)鍵方法enqueueMessage。

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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; 
                prev.next = msg;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
 

不懂得地方先不看,只看我們想看的:

  • 首先設(shè)置了     Message的when字段,也就是代表了這個(gè)消息的處理時(shí)間
  • 然后判斷當(dāng)前隊(duì)列是不是為空,是不是即時(shí)消息,是不是執(zhí)行時(shí)間when大于表頭的消息時(shí)間,滿足任意一個(gè),就把當(dāng)前消息msg插入到表頭。
  • 否則,就需要遍歷這個(gè)隊(duì)列,也就是     鏈表,找出when小于某個(gè)節(jié)點(diǎn)的when,找到后插入。

好了,其他內(nèi)容暫且不看,總之,插入消息就是通過(guò)消息的執(zhí)行時(shí)間,也就是when字段,來(lái)找到合適的位置插入鏈表。

具體方法就是通過(guò)死循環(huán),使用快慢指針p和prev,每次向后移動(dòng)一格,直到找到某個(gè)節(jié)點(diǎn)p的when大于我們要插入消息的when字段,則插入到p和prev之間?;蛘弑闅v到鏈表結(jié)束,插入到鏈表結(jié)尾。

所以,MessageQueue就是一個(gè)用于存儲(chǔ)消息、用鏈表實(shí)現(xiàn)的特殊隊(duì)列結(jié)構(gòu)。 

延遲消息是怎么實(shí)現(xiàn)的?

總結(jié)上述內(nèi)容,延遲消息的實(shí)現(xiàn)主要跟消息的統(tǒng)一存儲(chǔ)方法有關(guān),也就是上文說(shuō)過(guò)的enqueueMessage方法。

無(wú)論是即時(shí)消息還是延遲消息,都是計(jì)算出具體的時(shí)間,然后作為消息的when字段進(jìn)程賦值。

然后在MessageQueue中找到合適的位置(安排when小到大排列),并將消息插入到MessageQueue中。

這樣,MessageQueue就是一個(gè)按照消息時(shí)間排列的一個(gè)鏈表結(jié)構(gòu)。

MessageQueue的消息怎么被取出來(lái)的?

剛才說(shuō)過(guò)了消息的存儲(chǔ),接下來(lái)看看消息的取出,也就是queue.next方法。

    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) {
                    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 {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            }
        }
    }
 

奇怪,為什么取消息也是用的死循環(huán)呢?

其實(shí)死循環(huán)就是為了保證一定要返回一條消息,如果沒(méi)有可用消息,那么就阻塞在這里,一直到有新消息的到來(lái)。

其中,nativePollOnce方法就是阻塞方法,nextPollTimeoutMillis參數(shù)就是阻塞的時(shí)間。

那什么時(shí)候會(huì)阻塞呢??jī)煞N情況:

  • 1、有消息,但是當(dāng)前時(shí)間小于消息執(zhí)行時(shí)間,也就是代碼中的這一句:
if (now < msg.when) {
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
 

這時(shí)候阻塞時(shí)間就是消息時(shí)間減去當(dāng)前時(shí)間,然后進(jìn)入下一次循環(huán),阻塞。

  • 2、沒(méi)有消息的時(shí)候,也就是上述代碼的最后一句:
if (msg != null) {} 
    else {
    // No more messages.
    nextPollTimeoutMillis = -1;
    }
 

-1就代表一直阻塞。

MessageQueue沒(méi)有消息時(shí)候會(huì)怎樣?阻塞之后怎么喚醒呢?說(shuō)說(shuō)pipe/epoll機(jī)制?

接著上文的邏輯,當(dāng)消息不可用或者沒(méi)有消息的時(shí)候就會(huì)阻塞在next方法,而阻塞的辦法是通過(guò)pipe/epoll機(jī)制

epoll機(jī)制是一種IO多路復(fù)用的機(jī)制,具體邏輯就是一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,當(dāng)某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作,這個(gè)讀寫操作是阻塞的。在Android中,會(huì)創(chuàng)建一個(gè)Linux管道(Pipe)來(lái)處理阻塞和喚醒。

  • 當(dāng)消息隊(duì)列為空,管道的讀端等待管道中有新內(nèi)容可讀,就會(huì)通過(guò)     epoll機(jī)制進(jìn)入阻塞狀態(tài)。
  • 當(dāng)有消息要處理,就會(huì)通過(guò)管道的寫端寫入內(nèi)容,喚醒主線程。

同步屏障和異步消息是怎么實(shí)現(xiàn)的?

其實(shí)在Handler機(jī)制中,有三種消息類型:

  • 同步消息。也就是普通的消息。
  • 異步消息。通過(guò)setAsynchronous(true)設(shè)置的消息。
  • 同步屏障消息。通過(guò)postSyncBarrier方法添加的消息,特點(diǎn)是target為空,也就是沒(méi)有對(duì)應(yīng)的handler。

這三者之間的關(guān)系如何呢?

  • 正常情況下,同步消息和異步消息都是正常被處理,也就是根據(jù)時(shí)間when來(lái)取消息,處理消息。
  • 當(dāng)遇到同步屏障消息的時(shí)候,就開(kāi)始從消息隊(duì)列里面去找異步消息,找到了再根據(jù)時(shí)間決定阻塞還是返回消息。

也就是說(shuō)同步屏障消息不會(huì)被返回,他只是一個(gè)標(biāo)志,一個(gè)工具,遇到它就代表要去先行處理異步消息了。

所以同步屏障和異步消息的存在的意義就在于有些消息需要“加急處理”。 

同步屏障和異步消息有具體的使用場(chǎng)景嗎?

使用場(chǎng)景就很多了,比如繪制方法scheduleTraversals。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 同步屏障,阻塞所有的同步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 通過(guò) Choreographer 發(fā)送繪制任務(wù)
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }


    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
    msg.arg1 = callbackType;
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, dueTime);

在該方法中加入了同步屏障,后續(xù)加入一個(gè)異步消息MSG_DO_SCHEDULE_CALLBACK,最后會(huì)執(zhí)行到FrameDisplayEventReceiver,用于申請(qǐng)VSYNC信號(hào)。

Message消息被分發(fā)之后會(huì)怎么處理?消息怎么復(fù)用的?

再看看loop方法,在消息被分發(fā)之后,也就是執(zhí)行了dispatchMessage方法之后,還偷偷做了一個(gè)操作——recycleUnchecked。

    public static void loop() {
        for (;;) {
            Message msg = queue.next(); // might block

            try {
                msg.target.dispatchMessage(msg);
            } 

            msg.recycleUnchecked();
        }
    }

//Message.java
    private static Message sPool;
    private static final int MAX_POOL_SIZE = 50;

    void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

recycleUnchecked方法中,釋放了所有資源,然后將當(dāng)前的空消息插入到sPool表頭。

這里的sPool就是一個(gè)消息對(duì)象池,它也是一個(gè)鏈表結(jié)構(gòu)的消息,最大長(zhǎng)度為50。

那么Message又是怎么復(fù)用的呢?在Message的實(shí)例化方法obtain中:

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
 

直接復(fù)用消息池sPool中的第一條消息,然后sPool指向下一個(gè)節(jié)點(diǎn),消息池?cái)?shù)量減一。 

Looper是干嘛呢?怎么獲取當(dāng)前線程的Looper?為什么不直接用Map存儲(chǔ)線程和對(duì)象呢?

在Handler發(fā)送消息之后,消息就被存儲(chǔ)到MessageQueue中,而Looper就是一個(gè)管理消息隊(duì)列的角色。Looper會(huì)從MessageQueue中不斷的查找消息,也就是loop方法,并將消息交回給Handler進(jìn)行處理。

而Looper的獲取就是通過(guò)ThreadLocal機(jī)制:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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();
    }
 

通過(guò)prepare方法創(chuàng)建Looper并且加入到sThreadLocal中,通過(guò)myLooper方法從sThreadLocal中獲取Looper。

ThreadLocal運(yùn)行機(jī)制?這種機(jī)制設(shè)計(jì)的好處?

下面就具體說(shuō)說(shuō)ThreadLocal運(yùn)行機(jī)制。

//ThreadLocal.java
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal類中的get和set方法可以大致看出來(lái),有一個(gè)ThreadLocalMap變量,這個(gè)變量存儲(chǔ)著鍵值對(duì)形式的數(shù)據(jù)。

  • key為this,也就是當(dāng)前ThreadLocal變量。
  • value為T,也就是要存儲(chǔ)的值。

然后繼續(xù)看看ThreadLocalMap哪來(lái)的,也就是getMap方法:

    //ThreadLocal.java
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    //Thread.java
    ThreadLocal.ThreadLocalMap threadLocals = null;
 

原來(lái)這個(gè)ThreadLocalMap變量是存儲(chǔ)在線程類Thread中的。

所以ThreadLocal的基本機(jī)制就搞清楚了:

在每個(gè)線程中都有一個(gè)threadLocals變量,這個(gè)變量存儲(chǔ)著ThreadLocal和對(duì)應(yīng)的需要保存的對(duì)象。

這樣帶來(lái)的好處就是,在不同的線程,訪問(wèn)同一個(gè)ThreadLocal對(duì)象,但是能獲取到的值卻不一樣。

挺神奇的是不是,其實(shí)就是其內(nèi)部獲取到的Map不同,Map和Thread綁定,所以雖然訪問(wèn)的是同一個(gè)ThreadLocal對(duì)象,但是訪問(wèn)的Map卻不是同一個(gè),所以取得值也不一樣。

這樣做有什么好處呢?為什么不直接用Map存儲(chǔ)線程和對(duì)象呢?

打個(gè)比方:

  • ThreadLocal就是老師。
  • Thread就是同學(xué)。
  • Looper(需要的值)就是鉛筆。

現(xiàn)在老師買了一批鉛筆,然后想把這些鉛筆發(fā)給同學(xué)們,怎么發(fā)呢??jī)煞N辦法:

  • 1、老師把每個(gè)鉛筆上寫好每個(gè)同學(xué)的名字,放到一個(gè)大盒子里面去(map),用的時(shí)候就讓同學(xué)們自己來(lái)找。

這種做法就是Map里面存儲(chǔ)的是同學(xué)和鉛筆,然后用的時(shí)候通過(guò)同學(xué)來(lái)從這個(gè)Map里找鉛筆。

這種做法就有點(diǎn)像使用一個(gè)Map,存儲(chǔ)所有的線程和對(duì)象,不好的地方就在于會(huì)很混亂,每個(gè)線程之間有了聯(lián)系,也容易造成內(nèi)存泄漏。

  • 2、老師把每個(gè)鉛筆直接發(fā)給每個(gè)同學(xué),放到同學(xué)的口袋里(map),用的時(shí)候每個(gè)同學(xué)從口袋里面拿出鉛筆就可以了。

這種做法就是Map里面存儲(chǔ)的是老師和鉛筆,然后用的時(shí)候老師說(shuō)一聲,同學(xué)只需要從口袋里拿出來(lái)就行了。

很明顯這種做法更科學(xué),這也就是ThreadLocal的做法,因?yàn)殂U筆本身就是同學(xué)自己在用,所以一開(kāi)始就把鉛筆交給同學(xué)自己保管是最好的,每個(gè)同學(xué)之間進(jìn)行隔離。

還有哪些地方運(yùn)用到了ThreadLocal機(jī)制?

比如:Choreographer。

public final class Choreographer {

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

    private static volatile Choreographer mMainInstance;

Choreographer主要是主線程用的,用于配合 VSYNC中斷信號(hào)。

所以這里使用ThreadLocal更多的意義在于完成線程單例的功能。

可以多次創(chuàng)建Looper嗎?

Looper的創(chuàng)建是通過(guò)Looper.prepare方法實(shí)現(xiàn)的,而在prepare方法中就判斷了,當(dāng)前線程是否存在Looper對(duì)象,如果有,就會(huì)直接拋出異常:

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

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
 

所以同一個(gè)線程,只能創(chuàng)建一個(gè)Looper,多次創(chuàng)建會(huì)報(bào)錯(cuò)。 

Looper中的quitAllowed字段是啥?有什么用?

按照字面意思就是是否允許退出,我們看看他都在哪些地方用到了:

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
        }
    }
 

哦,就是這個(gè)quit方法用到了,如果這個(gè)字段為false,代表不允許退出,就會(huì)報(bào)錯(cuò)。

但是這個(gè)quit方法又是干嘛的呢?從來(lái)沒(méi)用過(guò)呢。還有這個(gè)safe又是啥呢?

其實(shí)看名字就差不多能了解了,quit方法就是退出消息隊(duì)列,終止消息循環(huán)。

  • 首先設(shè)置了     mQuitting字段為true。
  • 然后判斷是否安全退出,如果安全退出,就執(zhí)行     removeAllFutureMessagesLocked方法,它內(nèi)部的邏輯是清空所有的延遲消息,之前沒(méi)處理的非延遲消息還是需要取處理,然后設(shè)置非延遲消息的下一個(gè)節(jié)點(diǎn)為空(p.next=null)。
  • 如果不是安全退出,就執(zhí)行     removeAllMessagesLocked方法,直接清空所有的消息,然后設(shè)置消息隊(duì)列指向空(mMessages = null)

然后看看當(dāng)調(diào)用quit方法之后,消息的發(fā)送和處理:

//消息發(fā)送
    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;
            }
        }

當(dāng)調(diào)用了quit方法之后,mQuitting為true,消息就發(fā)不出去了,會(huì)報(bào)錯(cuò)。

再看看消息的處理,loop和next方法:

    Message next() {
        for (;;) {
            synchronized (this) {
                if (mQuitting) {
                    dispose();
                    return null;
                } 
            }  
        }
    }


    public static void loop() {
        for (;;) {
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        }
    }

很明顯,當(dāng)mQuitting為true的時(shí)候,next方法返回null,那么loop方法中就會(huì)退出死循環(huán)。

那么這個(gè)quit方法一般是什么時(shí)候使用呢?

  • 主線程中,一般情況下肯定不能退出,因?yàn)橥顺龊笾骶€程就停止了。所以是當(dāng)APP需要退出的時(shí)候,就會(huì)調(diào)用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。
  • 子線程中,如果消息都處理完了,就需要調(diào)用quit方法停止消息循環(huán)。 

Looper.loop方法是死循環(huán),為什么不會(huì)卡死(ANR)?

關(guān)于這個(gè)問(wèn)題,強(qiáng)烈建議看看Gityuan的回答:https://www.zhihu.com/question/34652589

我大致總結(jié)下:

  • 1、主線程本身就是需要一只運(yùn)行的,因?yàn)橐幚砀鱾€(gè)View,界面變化。所以需要這個(gè)死循環(huán)來(lái)保證主線程一直執(zhí)行下去,不會(huì)被退出。
  • 2、真正會(huì)卡死的操作是在某個(gè)消息處理的時(shí)候操作時(shí)間過(guò)長(zhǎng),導(dǎo)致掉幀、ANR,而不是loop方法本身。
  • 3、在主線程以外,會(huì)有其他的線程來(lái)處理接受其他進(jìn)程的事件,比如     Binder線程(ApplicationThread),會(huì)接受AMS發(fā)送來(lái)的事件
  • 4、在收到跨進(jìn)程消息后,會(huì)交給主線程的     Hanlder再進(jìn)行消息分發(fā)。所以Activity的生命周期都是依靠主線程的     Looper.loop,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施,比如收到     msg=H.LAUNCH_ACTIVITY,則調(diào)用     ActivityThread.handleLaunchActivity()方法,最終執(zhí)行到onCreate方法。
  • 5、當(dāng)沒(méi)有消息的時(shí)候,會(huì)阻塞在loop的     queue.next()中的     nativePollOnce()方法里,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生。所以死循環(huán)也不會(huì)特別消耗CPU資源。 

Message是怎么找到它所屬的Handler然后進(jìn)行分發(fā)的?

在loop方法中,找到要處理的Message,然后調(diào)用了這么一句代碼處理消息:

msg.target.dispatchMessage(msg); 

所以是將消息交給了msg.target來(lái)處理,那么這個(gè)target是啥呢?

找找它的來(lái)頭:

//Handler
    private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;
       
        return queue.enqueueMessage(msg, uptimeMillis);
    }
 

在使用Hanlder發(fā)送消息的時(shí)候,會(huì)設(shè)置msg.target = this,所以target就是當(dāng)初把消息加到消息隊(duì)列的那個(gè)Handler。

Handler 的 post(Runnable) 與 sendMessage 有什么區(qū)別

Hanlder中主要的發(fā)送消息可以分為兩種:

  • post(Runnable)
  • sendMessage
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
 

通過(guò)post的源碼可知,其實(shí)post和sendMessage的區(qū)別就在于:

post方法給Message設(shè)置了一個(gè)callback。

那么這個(gè)callback有什么用呢?我們?cè)俎D(zhuǎn)到消息處理的方法dispatchMessage中看看:

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

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

這段代碼可以分為三部分看:

  • 1、如果     msg.callback不為空,也就是通過(guò)post方法發(fā)送消息的時(shí)候,會(huì)把消息交給這個(gè)msg.callback進(jìn)行處理,然后就沒(méi)有后續(xù)了。
  • 2、如果     msg.callback為空,也就是通過(guò)sendMessage發(fā)送消息的時(shí)候,會(huì)判斷Handler當(dāng)前的mCallback是否為空,如果不為空就交給Handler.Callback.handleMessage處理。
  • 3、如果     mCallback.handleMessage返回true,則無(wú)后續(xù)了。
  • 4、如果     mCallback.handleMessage返回false,則調(diào)用handler類重寫的handleMessage方法。

所以post(Runnable) 與 sendMessage的區(qū)別就在于后續(xù)消息的處理方式,是交給msg.callback還是 Handler.Callback或者Handler.handleMessage。 

Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一樣?為什么這么設(shè)計(jì)?

接著上面的代碼說(shuō),這兩個(gè)處理方法的區(qū)別在于Handler.Callback.handleMessage方法是否返回true:

  • 如果為     true,則不再執(zhí)行Handler.handleMessage
  • 如果為     false,則兩個(gè)方法都要執(zhí)行。

那么什么時(shí)候有Callback,什么時(shí)候沒(méi)有呢?這涉及到兩種Hanlder的 創(chuàng)建方式:

    val handler1= object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }

    val handler2 = Handler(object : Handler.Callback {
        override fun handleMessage(msg: Message): Boolean {
            return true
        }
    })
 

常用的方法就是第1種,派生一個(gè)Handler的子類并重寫handleMessage方法。而第2種就是系統(tǒng)給我們提供了一種不需要派生子類的使用方法,只需要傳入一個(gè)Callback即可。 

Handler、Looper、MessageQueue、線程是一一對(duì)應(yīng)關(guān)系嗎?

  • 一個(gè)線程只會(huì)有一個(gè)     Looper對(duì)象,所以線程和Looper是一一對(duì)應(yīng)的。
  • MessageQueue對(duì)象是在new Looper的時(shí)候創(chuàng)建的,所以Looper和MessageQueue是一一對(duì)應(yīng)的。
  • Handler的作用只是將消息加到MessageQueue中,并后續(xù)取出消息后,根據(jù)消息的target字段分發(fā)給當(dāng)初的那個(gè)handler,所以Handler對(duì)于Looper是可以多對(duì)一的,也就是多個(gè)Hanlder對(duì)象都可以用同一個(gè)線程、同一個(gè)Looper、同一個(gè)MessageQueue。

總結(jié):Looper、MessageQueue、線程是一一對(duì)應(yīng)關(guān)系,而他們與Handler是可以一對(duì)多的。 

ActivityThread中做了哪些關(guān)于Handler的工作?(為什么主線程不需要單獨(dú)創(chuàng)建Looper)

主要做了兩件事:

  • 1、在main方法中,創(chuàng)建了主線程的     Looper和     MessageQueue,并且調(diào)用loop方法開(kāi)啟了主線程的消息循環(huán)。
public static void main(String[] args) {

        Looper.prepareMainLooper();

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
 
  • 2、創(chuàng)建了一個(gè)Handler來(lái)進(jìn)行四大組件的啟動(dòng)停止等事件處理
final H mH = new H();

class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int STOP_SERVICE            = 116;
        public static final int BIND_SERVICE            = 121;
   

IdleHandler是啥?有什么使用場(chǎng)景?

之前說(shuō)過(guò),當(dāng)MessageQueue沒(méi)有消息的時(shí)候,就會(huì)阻塞在next方法中,其實(shí)在阻塞之前,MessageQueue還會(huì)做一件事,就是檢查是否存在IdleHandler,如果有,就會(huì)去執(zhí)行它的queueIdle方法。

    private IdleHandler[] mPendingIdleHandlers;

    Message next() {
        int pendingIdleHandlerCount = -1;
        for (;;) {
            synchronized (this) {
                //當(dāng)消息執(zhí)行完畢,就設(shè)置pendingIdleHandlerCount
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                //初始化mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //mIdleHandlers轉(zhuǎn)為數(shù)組
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 遍歷數(shù)組,處理每個(gè)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 {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                //如果queueIdle方法返回false,則處理完就刪除這個(gè)IdleHandler
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
        }
    }

當(dāng)沒(méi)有消息處理的時(shí)候,就會(huì)去處理這個(gè)mIdleHandlers集合里面的每個(gè)IdleHandler對(duì)象,并調(diào)用其queueIdle方法。最后根據(jù)queueIdle返回值判斷是否用完刪除當(dāng)前的IdleHandler。

然后看看IdleHandler是怎么加進(jìn)去的:

Looper.myQueue().addIdleHandler(new IdleHandler() {  
    @Override  
    public boolean queueIdle() {  
        //做事情
        return false;    
    }  
});

    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
 

ok,綜上所述,IdleHandler就是當(dāng)消息隊(duì)列里面沒(méi)有當(dāng)前要處理的消息了,需要堵塞之前,可以做一些空閑任務(wù)的處理。

常見(jiàn)的使用場(chǎng)景有:啟動(dòng)優(yōu)化。

我們一般會(huì)把一些事件(比如界面view的繪制、賦值)放到onCreate方法或者onResume方法中。但是這兩個(gè)方法其實(shí)都是在界面繪制之前調(diào)用的,也就是說(shuō)一定程度上這兩個(gè)方法的耗時(shí)會(huì)影響到啟動(dòng)時(shí)間。

所以我們可以把一些操作放到IdleHandler中,也就是界面繪制完成之后才去調(diào)用,這樣就能減少啟動(dòng)時(shí)間了。

但是,這里需要注意下可能會(huì)有坑。

如果使用不當(dāng),IdleHandler會(huì)一直不執(zhí)行,比如在View的onDraw方法里面無(wú)限制的直接或者間接調(diào)用View的invalidate方法

其原因就在于onDraw方法中執(zhí)行invalidate,會(huì)添加一個(gè)同步屏障消息,在等到異步消息之前,會(huì)阻塞在next方法,而等到FrameDisplayEventReceiver異步任務(wù)之后又會(huì)執(zhí)行onDraw方法,從而無(wú)限循環(huán)。 

HandlerThread是啥?有什么使用場(chǎng)景?

直接看源碼:

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }

哦,原來(lái)如此。HandlerThread就是一個(gè)封裝了Looper的Thread類。

就是為了讓我們?cè)谧泳€程里面更方便的使用Handler。

這里的加鎖就是為了保證線程安全,獲取當(dāng)前線程的Looper對(duì)象,獲取成功之后再通過(guò)notifyAll方法喚醒其他線程,那哪里調(diào)用了wait方法呢?

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
 

就是getLooper方法,所以wait的意思就是等待Looper創(chuàng)建好,那邊創(chuàng)建好之后再通知這邊正確返回Looper。

IntentService是啥?有什么使用場(chǎng)景?

老規(guī)矩,直接看源碼:

public abstract class IntentService extends Service {


    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
 

理一下這個(gè)源碼:

  • 首先,這是一個(gè)     Service
  • 并且內(nèi)部維護(hù)了一個(gè)     HandlerThread,也就是有完整的Looper在運(yùn)行。
  • 還維護(hù)了一個(gè)子線程的     ServiceHandler。
  • 啟動(dòng)Service后,會(huì)通過(guò)Handler執(zhí)行     onHandleIntent方法。
  • 完成任務(wù)后,會(huì)自動(dòng)執(zhí)行     stopSelf停止當(dāng)前Service。

所以,這就是一個(gè)可以在子線程進(jìn)行耗時(shí)任務(wù),并且在任務(wù)執(zhí)行后自動(dòng)停止的Service。 

BlockCanary使用過(guò)嗎?說(shuō)說(shuō)原理

BlockCanary是一個(gè)用來(lái)檢測(cè)應(yīng)用卡頓耗時(shí)的三方庫(kù)。

上文說(shuō)過(guò),View的繪制也是通過(guò)Handler來(lái)執(zhí)行的,所以如果能知道每次Handler處理消息的時(shí)間,就能知道每次繪制的耗時(shí)了?那Handler消息的處理時(shí)間怎么獲取呢?

再去loop方法中找找細(xì)節(jié):

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

        msg.target.dispatchMessage(msg);

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

可以發(fā)現(xiàn),loop方法內(nèi)有一個(gè)Printer類,在dispatchMessage處理消息的前后分別打印了兩次日志。

那我們把這個(gè)日志類Printer替換成我們自己的Printer,然后統(tǒng)計(jì)兩次打印日志的時(shí)間不就相當(dāng)于處理消息的時(shí)間了?

    Looper.getMainLooper().setMessageLogging(mainLooperPrinter);

    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }
 

這就是BlockCanary的原理。

說(shuō)說(shuō)Hanlder內(nèi)存泄露問(wèn)題。

這也是常常被問(wèn)的一個(gè)問(wèn)題,Handler內(nèi)存泄露的原因是什么?

"內(nèi)部類持有了外部類的引用,也就是Hanlder持有了Activity的引用,從而導(dǎo)致無(wú)法被回收唄。"

其實(shí)這樣回答是錯(cuò)誤的,或者說(shuō)沒(méi)回答到點(diǎn)子上。

我們必須找到那個(gè)最終的引用者,不會(huì)被回收的引用者,其實(shí)就是主線程,這條完整引用鏈應(yīng)該是這樣:

主線程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

具體分析可以看看我之前寫的這篇文章:https://juejin.cn/post/6909362503898595342

利用Handler機(jī)制設(shè)計(jì)一個(gè)不崩潰的App?

主線程崩潰,其實(shí)都是發(fā)生在消息的處理內(nèi),包括生命周期、界面繪制。

所以如果我們能控制這個(gè)過(guò)程,并且在發(fā)生崩潰后重新開(kāi)啟消息循環(huán),那么主線程就能繼續(xù)運(yùn)行。

Handler(Looper.getMainLooper()).post {
        while (true) {
            //主線程異常攔截
            try {
                Looper.loop()
            } catch (e: Throwable) {
            }
        }
    }

感謝各位的閱讀,以上就是“Handler的作用有哪些”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Handler的作用有哪些這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI