Handler所有問題靈魂拷問

  • Handler 機(jī)制中,存在哪些角色?各自承擔(dān)了什么功能?

Handler:消息輔助類 & 對(duì)外的接口 & 向 MQ 投遞消息 & 消息的目標(biāo)處理者;
Message:消息的載體 & 被 Handler 投遞 & 自帶 Handler 處理 & 自帶消息池;
Looper:循環(huán)器 & 持有 MQ & 循環(huán)從 MQ 中獲取消息 & TLS 線程唯一;
MessageQueue:基于時(shí)間的優(yōu)先級(jí)隊(duì)列 & 鏈表結(jié)構(gòu) & Java 與 C++ 層的紐帶;

  • Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用ANR

答: ANR的真正原因是單個(gè)消息處理時(shí)間過長(zhǎng),阻塞了隊(duì)列中其他關(guān)鍵消息(如界面渲染、手勢(shì)有操作)的執(zhí)行。因?yàn)楫?dāng)Looper它在空閑時(shí)會(huì)通過nativePollOnce()進(jìn)入休眠,不消耗CPU資源,當(dāng)有新的Message進(jìn)來的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行,消息能得到繼續(xù)處理,不會(huì)有消息處理超時(shí)的情況。

Looper的死循環(huán),是循環(huán)執(zhí)行各種事務(wù),包括UI繪制事務(wù)。Looper死循環(huán)說明線程沒有死亡,如果Looper停止循環(huán),線程則結(jié)束退出了。Looper的死循環(huán)本身就是保證UI繪制任務(wù)可以被執(zhí)行的原因之一。

  • Handler的阻塞喚醒機(jī)制是怎么回事?

Message next() {
    for (;;) {
        // 這個(gè)native調(diào)用是關(guān)鍵:讓線程進(jìn)入TIMED_WAITING狀態(tài)
        nativePollOnce(ptr, nextPollTimeoutMillis);
        
        synchronized (this) {
          ...
        }
    }

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        // ... 消息入隊(duì)邏輯
        
        // 判斷是否需要喚醒:隊(duì)列為空、是屏障消息、或者新消息在隊(duì)首
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

當(dāng)消息不可用或者沒有消息的時(shí)候就會(huì)阻塞在next方法,會(huì)進(jìn)入nativePollOnce()方法,而阻塞的辦法是通過pipe/epoll機(jī)制,Android利用Linux的epoll機(jī)制實(shí)現(xiàn)高效的多路復(fù)用I/O,對(duì)I/O進(jìn)行了阻塞。

在enqueueMessage方法中,如果有新的消息進(jìn)入,會(huì)根據(jù)needWeak字段,調(diào)用nativeWake()方法進(jìn)行喚醒。

https://blog.csdn.net/chuyouyinghe/article/details/128198098

  • 可以在子線程直接new一個(gè)Handler嗎

可以在子線程直接new一個(gè)Handler,不過需要在一個(gè)線程里需要先調(diào)用Looper.prepare()和Looper.loop()方法。

Thread {
            Looper.prepare()
            object : Handler() {
                override fun handleMessage(msg: Message) {
                    super.handleMessage(msg)

                }
            }
            Looper.loop()
        }.start()

在主線程中為什么沒看到Looper.prepare()?因?yàn)橄到y(tǒng)已經(jīng)在應(yīng)用啟動(dòng)的main方法里面調(diào)用Looper.prepareMainLooper()。Looper啟動(dòng)了handler的機(jī)制才能夠正常運(yùn)行,啟動(dòng)之前有需要prepare去創(chuàng)建Looper。如果不去調(diào)用loop()方法開啟循環(huán)讀取消息,你就算用handler傳遞了消息,沒有去取消息的呀。

    public static void main(String[] args) {
      Looper.prepareMainLooper();
      Looper.loop();
    }
handler.post(Runnable {})會(huì)做什么

將一個(gè) Runnable 對(duì)象(即一段代碼)發(fā)送到與該 Handler 關(guān)聯(lián)的線程的消息隊(duì)列中,并在該線程中異步執(zhí)行。如果目標(biāo)線程空閑:消息會(huì)被立即處理。
如果目標(biāo)線程正在處理其他消息:你的 Runnable 必須等待前面所有的消息都處理完畢后,才會(huì)輪到它執(zhí)行。

  • MessageQueue內(nèi)部是什么樣的數(shù)據(jù)結(jié)構(gòu)?

MessageQueue內(nèi)部存有Message對(duì)象,Message內(nèi)存存有一個(gè)next的Message對(duì)象,就形成了message->next->message->next ...。
那么就形成了一個(gè)單鏈表。

public final class MessageQueue {
    Message mMessages;

    Message next() {
              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());
                }
    }
}

public final class Message implements Parcelable {
        @UnsupportedAppUsage
    /*package*/ Message next;
}

再看將消息入列的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) {
                // New head, wake up the event queue if blocked.
                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; // invariant: p == prev.next
                prev.next = msg;
            }
        }
    }

enqueueMessage()方法里面,通過延遲時(shí)間when,去遍歷消息隊(duì)列的延遲時(shí)間,依次作比較,如果值小于某個(gè)消息的值,就會(huì)將該消息插入到這個(gè)消息前一個(gè)位置,實(shí)現(xiàn)一個(gè)節(jié)點(diǎn)的插入。為什么這里是一個(gè)隊(duì)列呢?因?yàn)槿∠⒌臅r(shí)候永遠(yuǎn)是從一個(gè)方向去取,永遠(yuǎn)是從頭部開始取,那這不就是一個(gè)隊(duì)列嗎?

又因?yàn)榘l(fā)送消息最終是sendMessageAtTime(),可以控制傳送的時(shí)間先后,將時(shí)間短的插入到先發(fā)送但是延遲時(shí)間長(zhǎng)的節(jié)點(diǎn)前面去,所以MessageQueue是一個(gè)單鏈表的優(yōu)先級(jí)隊(duì)列。所以在loop方法中queue.next()能依次取出下一個(gè)消息,就是因?yàn)殒湵淼慕Y(jié)構(gòu)。

  • Hander機(jī)制采用的是什么設(shè)計(jì)模式?

采用的是生產(chǎn)者/消費(fèi)者的設(shè)計(jì)模式。子線程生產(chǎn)消息,主線程消費(fèi)消息,MessageQueue為生產(chǎn)倉(cāng)庫(kù)。

  • Handler是怎么實(shí)現(xiàn)切換線程的?

    var handler = object: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("minfo", "從子線程收到消息 ${msg.what}")
        }
    }

        object : Thread() {
            override fun run() {
                handler.sendEmptyMessage(200)
            }
        }

在一個(gè)線程中創(chuàng)建handler,然后在另一個(gè)創(chuàng)建的線程中調(diào)用該handler的發(fā)送消息,該handler中能接收到消息,即實(shí)現(xiàn)了線程間通信,那么handler是如何實(shí)現(xiàn)線程切換的呢?

當(dāng)在A線程中創(chuàng)建handler的時(shí)候,同時(shí)創(chuàng)建了Looper與MessageQueue,Looper在A線程中調(diào)用loop進(jìn)入一個(gè)無限的for循環(huán)從MessageQueue中取消息。當(dāng)B線程調(diào)用handler發(fā)送一個(gè)message的時(shí)候,會(huì)通過msg.target.dispatchMessage(msg);將message插入到handler對(duì)應(yīng)的MessageQueue中,Looper發(fā)現(xiàn)有message插入到MessageQueue中,便取出message執(zhí)行相應(yīng)的邏輯,因?yàn)長(zhǎng)ooper.loop()是在A線程中啟動(dòng)的,所以則回到了A線程,達(dá)到了從B線程切換到A線程的目的。

  • handler.sendMessage()與handler.post()的區(qū)別?

1、如果msg.callback不為空,也就是通過post方法發(fā)送消息的時(shí)候,會(huì)把消息交給這個(gè)msg.callback進(jìn)行處理,然后就沒有后續(xù)了。
2、如果msg.callback為空,也就是通過sendMessage發(fā)送消息的時(shí)候,會(huì)判斷Handler當(dāng)前的mCallback是否為空,如果不為空就交給Handler.Callback.handleMessage處理。

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

 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();
    }
  • MessageQueue是怎么增刪消息的?

                msg.next = p; // invariant: p == prev.next
                prev.next = msg;

添加消息:在enqueMessage方法中,將該條msg按照時(shí)間大小,遍歷鏈表到正確的位置,將該msg插入到該節(jié)點(diǎn)中。

                        msg.next = null;
                        msg.markInUse();
                        return msg;

void markInUse() {
        flags |= FLAG_IN_USE;
    }

刪除消息:在next方法中,將該msg對(duì)象的next指向null,并將flags標(biāo)識(shí)為FLAG_IN_USE。

  • 一個(gè)線程可以有幾個(gè)Handler?幾個(gè)Looper?幾個(gè)MessageQueue?

一個(gè)線程可以有幾個(gè)Handler?

可以創(chuàng)建無數(shù)個(gè)Handler,但是他們使用的消息隊(duì)列都是同一個(gè),也就是同一個(gè)Looper。Handler在哪個(gè)線程創(chuàng)建的,就跟哪個(gè)線程的Looper關(guān)聯(lián),也可以在Handler的構(gòu)造方法中傳入指定的Looper,Looper.loop()循環(huán)讀取消息。

那同一個(gè)Looper是怎么區(qū)分不同的Handler的?因?yàn)樵趍sg入隊(duì)列時(shí),會(huì)將msg.target設(shè)置一個(gè)handler,處理消息的時(shí)候,也會(huì)調(diào)用msg對(duì)象的target去處理消息。

一個(gè)線程有幾個(gè)looper?如何保證的?

Looper類中部分代碼:

   final MessageQueue mQueue;
    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();
}

從Looper的源碼中可以看到,在一個(gè)線程中初始化Looper,是用ThreadLocal存儲(chǔ)Looper對(duì)象。同時(shí)保證一個(gè)線程只擁有一個(gè)Looper,存入looper之前,從sThreadLocal.get()中取看是否存過looper, sThreadLocal中如果存在則會(huì)拋出異常,保證一個(gè)線程中的looper實(shí)例唯一,且一旦存入,不可修改不可新增。

幾個(gè)MessageQueue

一個(gè)線程的looper只會(huì)創(chuàng)建一次,只有一個(gè)looper對(duì)象,一個(gè)looper下只有一個(gè)MessageQueue屬性,所以一個(gè)線程只有一個(gè)MessageQueue。

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • A Handler發(fā)送的消息為什么不會(huì)跑到B Handler的handleMessage()方法中?

一個(gè)線程中包含多個(gè)Handler對(duì)象時(shí),在消息的添加分發(fā)時(shí),通過Message的target(Handler)標(biāo)注,處理消息會(huì)去除對(duì)應(yīng)的target去調(diào)用handleMessage,所以不會(huì)錯(cuò)亂。

  • ThreadLoacal的原理?

這里見我的另一篇ThreadLoacal的原理

  • 在UI中創(chuàng)建的Handler,通過post方式發(fā)送的消息在run方法中可以進(jìn)行UI更新嗎?

在消息處理結(jié)束后,其回調(diào)至Runnable 的run方法中,需要注意的是這里的仍然是在UI線程中,因?yàn)槲覀儎?chuàng)建的Handler是在UI線程中,且Handler將Runnable內(nèi)部封住成Message的形式,所以最終調(diào)用的是Runable中的run()方法,因此還是回到UI線程中,可以更新UI界面。

  • 主線程為什么不用初始化Looper?

Android程序的入口在ActivityThread的main方法中,在main方法中已經(jīng)調(diào)用了prepareMainLooper()去創(chuàng)建looper對(duì)象了,并且調(diào)用了 Looper.loop()。如果是自己創(chuàng)建的子線程中,需要自己初始化looper并調(diào)用loop方法。ActivityThread 不繼承自 Thread,它只是一個(gè)運(yùn)行在主線程上的對(duì)象。

public static void main(String[] args) {
    ...
 // 初始化主線程Looper
    Looper.prepareMainLooper();
    ...
    // 新建一個(gè)ActivityThread對(duì)象
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
 
    // 獲取ActivityThread的Handler,也是他的內(nèi)部類H
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
 
    ...
    Looper.loop();
}
  • Handler如何保證MessageQueue并發(fā)訪問安全?

各個(gè)線程都往一個(gè)messageQueue中存取msg,在存取數(shù)據(jù)的時(shí)候,是通過synchronized來保證了線程的安全性,使用messageQueue作為對(duì)象鎖。各個(gè)子線程和主線程都是往用一個(gè)messageQueue存取消息,對(duì)調(diào)用同一個(gè)MessageQueue對(duì)象的線程來說,它們都是互斥的,所以保證了并發(fā)訪問安全。

boolean enqueueMessage(Message msg, long when) {
     synchronized (this) {
          ......
 }
}

Message next() {
     synchronized (this) {
          ......
 }
}
  • 能不能讓一個(gè)Message加急被處理?

可以 / 一種使得異步消息可以被更快處理的機(jī)制。
而看了前面 MessageQueue::next 的代碼我們知道,當(dāng) MessageQueue 中遇到了一個(gè)同步屏障,則它會(huì)不斷地忽略后面的同步消息直到遇到一個(gè)異步的消息,這樣設(shè)計(jì)的目的其實(shí)是為了使得當(dāng)隊(duì)列中遇到同步屏障時(shí),則會(huì)使得異步的消息優(yōu)先執(zhí)行,這樣就可以使得一些消息優(yōu)先執(zhí)行。

    Message next() {
        for (;;) {
            if (msg != null && msg.target == null) {
                    // 同步屏障,找到下一個(gè)異步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
        }
    }
  • Message 的同步屏障有什么用?有什么意義?如何發(fā)送一個(gè)同步屏障?

在 Handler 中還存在了一種特殊的消息,它的 target 為 null,并不會(huì)被消費(fèi),僅僅是作為一個(gè)標(biāo)識(shí)處于 MessageQueue 中。它就是 SyncBarrier (同步屏障)這種特殊的消息。

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

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

移除同步屏障

    public void removeSyncBarrier(int token) {
      ...
    }

比如 View 的繪制過程中的 TraversalRunnable 消息就是異步消息,在放入隊(duì)列之前先放入了一個(gè)消息屏障,從而使得界面繪制的消息會(huì)比其他消息優(yōu)先執(zhí)行,避免了因?yàn)?MessageQueue 中消息太多導(dǎo)致繪制消息被阻塞導(dǎo)致畫面卡頓,當(dāng)繪制完成后,就會(huì)將消息屏障移除。

  • 同步屏障的使用場(chǎng)景

貌似在開發(fā)的過程中,我們很少用到同步屏障,那么源碼中在哪里用到了?

Android中的UI消息就是異步消息,需要優(yōu)先處理 比如View更新,調(diào)用onDraw、requestLayout、invalidate等view最終都會(huì)調(diào)用到ViewRootImpl類中的scheduleTraversals方法。

void scheduleTraversals(){
   if(!mTraversalScheduled){

   mTraversalScheduled=true;
    //開啟同步屏障
   mTraversalBarrier=mHandler.getLooper().getQueue().postSyncBarrier();
   //發(fā)送異步消息
   mChoreographer.postCallback();
   ...
   }
}

這里開啟了同步屏障,并發(fā)送了異步消息,由于UI更顯相關(guān)的消息是優(yōu)先級(jí)最高的,這樣系統(tǒng)就會(huì)優(yōu)先處理這些異步消息了。

當(dāng)然處理完消息后要移除同步屏障,這個(gè)時(shí)候就調(diào)用到了ViewRootImpl#unscheduleTraversals()。

  • 什么是異步消息?如何發(fā)送

意義:需配合同步屏障使用,否者與同步消息無區(qū)別;
異步消息:setAsynchronous(true) → 向 flags 添加 FLAG_ASYNCHRONOUS 標(biāo)記
發(fā)送方式 通過異步 Handler 發(fā)送 → 構(gòu)造 Handler 時(shí),async 傳遞 true 發(fā)送消息前,主動(dòng)調(diào)用 setAsynchronous(true)
安全起見,Android 9.0 普通開發(fā)者無法使用異步消息,所有發(fā)送方式被標(biāo)記為 @hide

將handler標(biāo)識(shí)標(biāo)位異步handler,該handler就發(fā)送異步消息。

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }
  • Handler 里藏著的 Callback 能干什么?

Handler.Callback 有優(yōu)先處理消息的權(quán)利 ,當(dāng)一條消息被 Callback 處理并攔截
(返回 true),那么 Handler 的 handleMessage(msg) 方法就不會(huì)被調(diào)用了;
如果 Callback 處理了消息,但是并沒有攔截,那么就意味著一個(gè)消息可以同時(shí)
被 Callback 以及 Handler 處理。

  • 什么是IdleHandler?

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

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

    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;
        }
    }
  • IntentService是啥?有什么使用場(chǎng)景?

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è)可以在子線程進(jìn)行耗時(shí)任務(wù),并且在任務(wù)執(zhí)行后調(diào)用stopSelf()自動(dòng)停止的Service。

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

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

HandlerThread就是一個(gè)封裝了Looper的Thread類。就是為了讓我們?cè)谧泳€程里面更方便的使用Handler。

  • Handler 分發(fā)事件優(yōu)先級(jí),是否可攔截?攔截的優(yōu)先級(jí)如何?

可以統(tǒng)一攔截消息,但無法攔截通過Runnable通過getPostMessage(Runnable r)生成的Message。
因?yàn)樗黰sg.callback不為空會(huì)優(yōu)先處理msg.callback,不會(huì)經(jīng)過統(tǒng)一的Hanlder的mCallback。

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // mCallback 處理完如果返回 false,還是會(huì)繼續(xù)往下走,再交給 Handler.handleMessage 處理的
            // 所以這邊可以通過反射去 hook 一個(gè) Handler ,可以監(jiān)聽 Handler 處理的每個(gè)消息,也可以改 msg 里面的值
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
  • 主線程 Looper 何時(shí)運(yùn)行?

App 啟動(dòng)時(shí),會(huì)調(diào)用到 ActivityThread 中,Looper 就在其 main() 方法中被啟動(dòng);main() 中會(huì)主動(dòng)調(diào)用 Looper.prepareMainLooper() 和 Looper.loop()

  • Handler 的 Message 可以分為那 3 類?分別有什么標(biāo)識(shí)?

1.發(fā)送普通消息
2.發(fā)送異步消息
給msg對(duì)象加入設(shè)置異步的標(biāo)識(shí)。

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

3.同步屏障
使用postSyncBarrier方法發(fā)送同步屏障,使msg.target == null。

private int postSyncBarrier(long when) {
      ...
}
  • 點(diǎn)擊頁(yè)面上的按鈕后更新TextView的內(nèi)容,談?wù)勀愕睦斫猓?/h4>

點(diǎn)擊按鈕的時(shí)候會(huì)發(fā)送消息到Handler,但是為了保證優(yōu)先執(zhí)行,會(huì)加一個(gè)標(biāo)記異步,同時(shí)會(huì)發(fā)送一個(gè)target為null的消息,這樣在使用消息隊(duì)列的next獲取消息的時(shí)候,如果發(fā)現(xiàn)消息的target為null,那么會(huì)遍歷消息隊(duì)列將有異步標(biāo)記的消息獲取出來優(yōu)先執(zhí)行,執(zhí)行完之后會(huì)將target為null的消息移除。(同步屏障)

  • 同一個(gè) Message 對(duì)象能否重復(fù) send?

關(guān)鍵在于如何定義同一個(gè) Message。

角度一:Java 對(duì)象層面,可被復(fù)用;
原因:Message 由消息池維護(hù),即同一個(gè)對(duì)象被回收后會(huì)被再次復(fù)用;| new Message & Message.obtain()
角度二:業(yè)務(wù)層面,不能復(fù)用;
原因:Message 通過 enqueueMessage() 入隊(duì)時(shí),會(huì)通過 markInUse() 標(biāo)記,再次入隊(duì)無法通過 isInUse() 檢查,則拋出異常;
  • Looper.loop 中,如果沒有待處理的消息,為什么不會(huì)阻塞 UI?

主線程在 MessageQueue 沒有消息時(shí),會(huì)阻塞在 loop 的 queue.next() 方法中的 nativePollOnce()方法里。

此時(shí)主線程會(huì)釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下一個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過往 pipe 管道寫端寫入數(shù)據(jù)的方式,來喚醒主線程。這里采用的是 epoll 機(jī)制。

epoll 機(jī)制是一種 IO 多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,在有事件發(fā)生的時(shí)候,立即通知相應(yīng)程序進(jìn)行讀或?qū)懖僮?,類似一種 callback 的回調(diào)機(jī)制。主線程在大多數(shù)時(shí)候是處于休眠狀態(tài),并不會(huì)消耗大量的 CPU 資源。當(dāng)有新的消息或事務(wù)到達(dá)時(shí),會(huì)立即喚醒主線程進(jìn)行處理,所以對(duì)用戶來說是無感知的。

  • Looper 的 Printer 輸出的日志,有什么其他用途?依靠的原理是什么?有什么缺點(diǎn)?

用途:性能監(jiān)控;
原理:通過篩選日志內(nèi)存,區(qū)分 Message 的開始執(zhí)行和結(jié)束執(zhí)行的時(shí)間點(diǎn),即可判斷處理 Message 的耗時(shí),即主線程卡頓耗時(shí);
缺點(diǎn):Printer 存在大量字符串拼接,在消息量大時(shí),會(huì)導(dǎo)致性能受損;| 實(shí)測(cè)數(shù)據(jù):存在 Printer 時(shí),在列表快速滑動(dòng)時(shí),平均幀率降低 5 幀;

  • Handler 可以 IPC 通信嗎?

不能;Handler是一種共享內(nèi)存的通信方式,Handler 只能用于共享內(nèi)存地址的 2 個(gè)線程通信,即同進(jìn)程的 2 個(gè)線程通信;

  • 為什么系統(tǒng)不建議在子線程訪問UI?(為什么不能在子線程更新UI?)

如果采用多線程訪問UI會(huì)出現(xiàn)線程安全,那為什么不加鎖呢?

加鎖會(huì)降低UI訪問的效率。本身UI控件就是離用戶比較近的一個(gè)組件,加鎖之后自然會(huì)發(fā)生阻塞,那么UI訪問的效率會(huì)降低,最終反應(yīng)到用戶端就是這個(gè)手機(jī)有點(diǎn)卡。
所以,Android設(shè)計(jì)出了單線程模型來處理UI操作,再搭配上Handler,是一個(gè)比較合適的解決方案。

  • 子線程訪問UI的 崩潰原因 和 解決辦法?

1.在ViewRootImpl創(chuàng)建之前進(jìn)行子線程的UI更新,比如onCreate方法中進(jìn)行子線程更新UI。
2.子線程切換到主線程進(jìn)行UI更新,比如Handler、view.post方法。
3.給操作的view設(shè)置大小為matchparent或者

  • Message可以如何創(chuàng)建?哪種效果更好,為什么?

Message.obtain來創(chuàng)建Message,這樣會(huì)復(fù)用之前的Message的內(nèi)存,不會(huì)頻繁的創(chuàng)建對(duì)象,導(dǎo)致內(nèi)存抖動(dòng)。

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

在一個(gè)message對(duì)象調(diào)用了dispatchMessage之后,會(huì)進(jìn)行回收操作。
在recycleUnchecked方法中,釋放了所有資源,然后將當(dāng)前的空消息插入到sPool表頭。
這里的sPool就是一個(gè)消息對(duì)象池,它也是一個(gè)鏈表結(jié)構(gòu)的消息,最大長(zhǎng)度為50。
使用obtain來獲取復(fù)用消息,直接復(fù)用消息池sPool中的第一條消息,然后sPool指向下一個(gè)節(jié)點(diǎn),消息池?cái)?shù)量減一。

    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        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++;
            }
        }
    }
  • 主線程中能否調(diào)用quit()方法?

是不能的。它會(huì)拋出一個(gè)異常,讓程序掛掉。

  • 修改了手機(jī)系統(tǒng)時(shí)間,handler的延時(shí)消息收到也會(huì)發(fā)生改變嗎

發(fā)送延時(shí)消息,會(huì)調(diào)用sendMessageDelayed,然后調(diào)用sendMessageAtTime,將一個(gè)時(shí)間點(diǎn)傳入,該時(shí)間為SystemClock.uptimeMillis()

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    /**
     * Returns milliseconds since boot, not counting time spent in deep sleep.
     *
     * @return milliseconds of non-sleep uptime since boot.
     */
    @CriticalNative
    native public static long uptimeMillis();

SystemClock.uptimeMillis() 是一個(gè)表示當(dāng)前時(shí)間的一個(gè)相對(duì)時(shí)間,它代表的是 自系統(tǒng)啟動(dòng)開始從0開始的到調(diào)用該方法時(shí)相差的毫秒數(shù)

System.currentTimeMillis() 代表的是從 1970-01-01 00:00:00 到當(dāng)前時(shí)間的毫秒數(shù),我們可以通過修改系統(tǒng)時(shí)間達(dá)到修改該值的目的,所以該值是不可靠的值

  • handler怎么移除一個(gè)消息

通過調(diào)用:

Handler().removeMessages(what: Int)

調(diào)用了handler中:

    public final void removeMessages(int what) {
        mQueue.removeMessages(this, what, null);
    }

進(jìn)入removeMessages方法:

    void removeMessages(Handler h, Runnable r, Object object) {
        if (h == null || r == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h && p.callback == r
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.callback == r
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

removeMessages會(huì)將handler對(duì)應(yīng)message queue里的消息清空,如果帶了int參數(shù)則是對(duì)應(yīng)的消息清空。1、這個(gè)方法使用的前提是之前調(diào)用過sendEmptyMessageDelayed(0, time),意思是延遲time執(zhí)行handler中msg.what=0的方法;
2、在延遲時(shí)間未到的前提下,執(zhí)行removeMessages(0),則上面的handler中msg.what=0的方法取消執(zhí)行;
3、在延遲時(shí)間已到,handler中msg.what=0的方法已執(zhí)行,再執(zhí)行removeMessages(0),不起作用。

  • handler.removeCallbacksAndMessages(null)是什么意思

如果需要?jiǎng)h除handler所有的消息和回調(diào)函數(shù),那就需要使用handler.removeCallbacksAndMessages(null)。
這樣做的好處是在Acticity退出的時(shí)候,可以避免內(nèi)存泄露,因?yàn)橛醒舆t消息,msg持有handler,handler持有activity,activity退出需要移除所有消息。
會(huì)while循環(huán)遍歷msg消息鏈表,調(diào)用msg的recycleUnchecked()進(jìn)行回收,方法會(huì)將msg所有屬性置空和置0。

所以,防止handler內(nèi)存泄露,一種解決方案是static+弱引用,另一種方案就是在activity的onDestroy中調(diào)用handler.removeCallbacksAndMessages方法。

參考:

https://blog.csdn.net/javine/article/details/45953575
https://blog.csdn.net/bzlj2912009596/article/details/79736912
http://m.itdecent.cn/p/7f1c46fb55c8
https://blog.csdn.net/qq_39477770/article/details/109331658?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-1&spm=1001.2101.3001.4242
https://blog.csdn.net/u012165769/article/details/114681388?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
https://blog.csdn.net/u012165769/article/details/113531570
https://blog.csdn.net/weixin_39952074/article/details/111249137
說一下Handler的同步屏障機(jī)制?

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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