一文讀懂 Handler 機制

Handler 在整個 Android 開發(fā)體系中占據(jù)著很重要的地位稚新,是一種標(biāo)準(zhǔn)的事件驅(qū)動模型,對開發(fā)者來說起到的作用很明確梅忌,就是為了實現(xiàn)線程切換或者是執(zhí)行延時任務(wù)嘲玫,稍微更高級一點的用法可能是為了保證多個任務(wù)在執(zhí)行時的有序性。由于 Android 系統(tǒng)中的主線程有特殊地位伤柄,所以像 EventBus 和 Retrofit 這類并非 Android 獨有的三方庫绊困,都是通過 Handler 來實現(xiàn)對 Android 系統(tǒng)的特殊平臺支持。大部分開發(fā)者都已經(jīng)對如何使用 Handler 很熟悉了适刀,這里就再來了解下其內(nèi)部具體是如何實現(xiàn)的秤朗,希望對你有所幫助 ????

本文基于 Android API 30(即 Android 11)的系統(tǒng)源碼進行講解

一、動手實現(xiàn) Handler

本文不會一上來就直接介紹源碼笔喉,而是會先根據(jù)我們想要實現(xiàn)的效果來反推源碼取视,一步步來自己動手實現(xiàn)一個簡單的 Handler

1、Message

首先常挚,我們需要有個載體來表示要執(zhí)行的任務(wù)作谭,就叫它 Message 吧,Message 應(yīng)該有什么參數(shù)呢奄毡?

  • 需要有一個唯一標(biāo)識折欠,因為要執(zhí)行的任務(wù)可能有多個,我們要分得清哪個是哪個吼过,用個 Int 類型變量就足夠表示了
  • 需要能夠承載數(shù)據(jù)锐秦,需要發(fā)送的數(shù)據(jù)類型會有很多種可能,那就直接用一個 Object 類型變量來表示吧盗忱,由開發(fā)者自己在使用時再來強轉(zhuǎn)類型
  • 需要有一個 long 類型變量來表示任務(wù)的執(zhí)行時間戳

所以酱床,Message 類就應(yīng)該至少包含以下幾個字段:

/**
 * @Author: leavesCZY
 * @Desc:
 * @公眾號:字節(jié)數(shù)組
 */
public final class Message {
    //唯一標(biāo)識
    public int what;
    //數(shù)據(jù)
    public Object obj;
    //時間戳
    public long when;
}

2、MessageQueue

因為 Message 并不是發(fā)送了就能夠馬上被消費掉趟佃,所以就肯定要有個可以用來存放的地方扇谣,就叫它 MessageQueue 吧慷垮,即消息隊列。Message 可能需要延遲處理揍堕,那么 MessageQueue 在保存 Message 的時候就應(yīng)該按照時間戳的大小來順序存放料身,時間戳小的 Message 放在隊列的頭部,在消費 Message 的時候就直接從隊列頭取值即可

那么用什么數(shù)據(jù)結(jié)構(gòu)來存放 Message 比較好呢衩茸?

  • 用數(shù)組芹血?不太合適,數(shù)組雖然在遍歷的時候會比較快楞慈,但需要預(yù)先就申請固定的內(nèi)存空間幔烛,導(dǎo)致在插入數(shù)據(jù)和移除數(shù)據(jù)時可能需要移動大量數(shù)據(jù)。而 MessageQueue 可能隨時會收到數(shù)量不定囊蓝、時間戳大小不定的 Message饿悬,消費完 Message 后還需要將該其移出隊列,所以使用數(shù)組并不合適
  • 用鏈表聚霜?好像可以狡恬,鏈表在插入數(shù)據(jù)和移除數(shù)據(jù)時只需要改變指針的引用即可,不需要移動數(shù)據(jù)蝎宇,內(nèi)存空間也只需要按需申請即可弟劲。雖然鏈表在隨機訪問的時候性能不高,但是對于 MessageQueue 而言無所謂姥芥,因為在消費 Message 的時候也只需要取隊列頭的值兔乞,并不需要隨機訪問

好了,既然決定用鏈表結(jié)構(gòu)凉唐,那么 Message 就需要增加一個字段用于指向下一條消息才行

public final class Message {
    //唯一標(biāo)識
    public int what;
    //數(shù)據(jù)
    public Object obj;
    //時間戳
    public long when;
    //下一個節(jié)點
    public Message next;
}

MessageQueue 需要提供一個 enqueueMessage方法用來向鏈表插入 Message庸追,由于存在多個線程同時向隊列發(fā)送消息的可能,所以方法內(nèi)部還需要做下線程同步才行

public class MessageQueue {

    //鏈表中的第一條消息
    private Message mMessages;
    
    void enqueueMessage(Message msg, long when) {
        synchronized (this) {
            Message p = mMessages;
            //如果鏈表是空的台囱,或者處于隊頭的消息的時間戳比 msg 要大淡溯,則將 msg 作為鏈表頭部
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
            } else {
                Message prev;
                //從鏈表頭向鏈表尾遍歷,尋找鏈表中第一條時間戳比 msg 大的消息玄坦,將 msg 插到該消息的前面
                for (; ; ) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }
        }
    }
}

此外血筑,MessageQueue 要有一個可以獲取隊頭消息的方法才行绘沉,就叫做next()吧煎楣。外部有可能會隨時向 MessageQueue 發(fā)送 Message,next()方法內(nèi)部就直接來開啟一個無限循環(huán)來反復(fù)取值吧车伞。如果當(dāng)前隊頭的消息可以直接處理的話(即消息的時間戳小于或等于當(dāng)前時間)择懂,那么就直接返回隊頭消息。而如果隊頭消息的時間戳比當(dāng)前時間還要大(即隊頭消息是一個延時消息)另玖,那么就計算當(dāng)前時間和隊頭消息的時間戳的差值困曙,計算 next() 方法需要阻塞等待的時間表伦,調(diào)用 nativePollOnce()方法來等待一段時間后再繼續(xù)循環(huán)遍歷

//用來標(biāo)記 next() 方法是否正處于阻塞等待的狀態(tài)
private boolean mBlocked = false;

Message next() {
    int nextPollTimeoutMillis = 0;
    for (; ; ) {
        nativePollOnce(nextPollTimeoutMillis);
        synchronized (this) {
            //當(dāng)前時間
            final long now = SystemClock.uptimeMillis();

            Message msg = mMessages;
            if (msg != null) {
                if (now < msg.when) {
                    //如果當(dāng)前時間還未到達消息的的處理時間,那么就計算還需要等待的時間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //可以處理隊頭的消息了慷丽,第二條消息成為隊頭
                    mMessages = msg.next;
                    msg.next = null;
                    mBlocked = false;
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            mBlocked = true;
        }
    }
}

//將 next 方法的調(diào)用線程休眠指定時間
private void nativePollOnce(long nextPollTimeoutMillis) {

}

此時就需要考慮到一種情形:當(dāng) next()還處于阻塞狀態(tài)的時候蹦哼,外部向消息隊列插入了一個可以立即處理或者是阻塞等待時間比較短的 Message。此時就需要喚醒休眠的線程要糊,因此 enqueueMessage還需要再改動下纲熏,增加判斷是否需要喚醒next()方法的邏輯

void enqueueMessage(Message msg, long when) {
    synchronized (this) {
        //用于標(biāo)記是否需要喚醒 next 方法
        boolean needWake = false;         
        Message p = mMessages;
        //如果鏈表是空的,或者處于隊頭的消息的時間戳比 msg 要大锄俄,則將 msg 作為鏈表頭部
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;     
            //需要喚醒
            needWake = mBlocked;
        } else {
            Message prev;
            //從鏈表頭向鏈表尾遍歷局劲,尋找鏈表中第一條時間戳比 msg 大的消息,將 msg 插到該消息的前面
            for (; ; ) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
            }
            msg.next = p;
            prev.next = msg;
        }  
        if (needWake) {
            //喚醒 next() 方法
            nativeWake();
        }
    }
}

//喚醒 next() 方法
private void nativeWake() {

}

3奶赠、Handler

既然存放消息的地方已經(jīng)確定就是 MessageQueue 了鱼填,那么自然就還需要有一個類可以用來向 MessageQueue 發(fā)送消息了,就叫它 Handler 吧毅戈。Handler 可以實現(xiàn)哪些功能呢苹丸?

  • 希望除了可以發(fā)送 Message 類型的消息外還可以發(fā)送 Runnable 類型的消息。這個簡單苇经,Handler 內(nèi)部將 Runnable 包裝為 Message 即可
  • 希望可以發(fā)送延時消息谈跛,以此來執(zhí)行延時任務(wù)。這個也簡單塑陵,用 Message 內(nèi)部的 when 字段來標(biāo)識希望任務(wù)執(zhí)行時的時間戳即可
  • 希望可以實現(xiàn)線程切換感憾,即從子線程發(fā)送的 Message 可以在主線程被執(zhí)行,反過來也一樣令花。這個也不難阻桅,子線程可以向一個特定的 mainMessageQueue 發(fā)送消息,然后讓主線程負(fù)責(zé)循環(huán)從該隊列中取消息并執(zhí)行即可兼都,這樣不就實現(xiàn)了線程切換了嗎嫂沉?

所以說,Message 的定義和發(fā)送是由 Handler 來完成的扮碧,但 Message 的分發(fā)則可以交由其他線程來完成

根據(jù)以上需求:Runnable 要能夠包裝為 Message 類型趟章,Message 的處理邏輯要交由 Handler 來定義,所以 Message 就還需要增加兩個字段才行

/**
 * @Author: leavesCZY
 * @Desc:
 * @Github:https://github.com/leavesCZY
 */
public final class Message {
    //唯一標(biāo)識
    public int what;
    //數(shù)據(jù)
    public Object obj;
    //時間戳
    public long when;
    //下一個節(jié)點
    public Message next;
    //用于將 Runnable 包裝為 Message
    public Runnable callback;
    //指向 Message 的發(fā)送者慎王,同時也是 Message 的最終處理者
    public Handler target;
}

Handler 至少需要包含幾個方法:用于發(fā)送 Message 和 Runnable 的方法蚓土、用來處理消息的 handleMessage 方法、用于分發(fā)消息的 dispatchMessage方法

/**
 * @Author: leavesCZY
 * @Desc:
 * @Github:https://github.com/leavesCZY
 */
public class Handler {

    private MessageQueue mQueue;
    
    public Handler(MessageQueue mQueue) {
        this.mQueue = mQueue;
    }

    public final void post(Runnable r) {
        sendMessageDelayed(getPostMessage(r), 0);
    }

    public final void postDelayed(Runnable r, long delayMillis) {
        sendMessageDelayed(getPostMessage(r), delayMillis);
    }

    public final void sendMessage(Message r) {
        sendMessageDelayed(r, 0);
    }

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

    public void sendMessageAtTime(Message msg, long uptimeMillis) {
        msg.target = this;
        mQueue.enqueueMessage(msg, uptimeMillis);
    }

    private static Message getPostMessage(Runnable r) {
        Message m = new Message();
        m.callback = r;
        return m;
    }

    //由外部來重寫該方法赖淤,以此來消費 Message
    public void handleMessage(Message msg) {

    }

    //用于分發(fā)消息
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            msg.callback.run();
        } else {
            handleMessage(msg);
        }
    }

}

之后蜀漆,子線程就可以像這樣來使用 Handler 了:將子線程持有的 Handler 對象和主線程關(guān)聯(lián)的 mainMessageQueue 綁定在一起,主線程負(fù)責(zé)循環(huán)從 mainMessageQueue 取出 Message 后再來調(diào)用 Handler 的 dispatchMessage 方法咱旱,以此實現(xiàn)線程切換的目的

Handler handler = new Handler(mainThreadMessageQueue) {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1: {
                String ob = (String) msg.obj;
                break;
            }
            case 2: {
                List<String> ob = (List<String>) msg.obj;
                break;
            }
        }
    }
};
Message messageA = new Message();
messageA.what = 1;
messageA.obj = "https://github.com/leavesCZY";
Message messageB = new Message();
messageB.what = 2;
messageB.obj = new ArrayList<String>();
handler.sendMessage(messageA);
handler.sendMessage(messageB);

4确丢、Looper

現(xiàn)在就再來想想怎么讓 Handler 拿到和主線程關(guān)聯(lián)的 MessageQueue绷耍,以及主線程怎么從 MessageQueue 獲取 Message 并回調(diào) Handler。這之間就一定需要一個中轉(zhuǎn)器鲜侥,就叫它 Looper 吧褂始。Looper 具體需要實現(xiàn)什么功能呢?

  • 每個 Looper 對象應(yīng)該都是對應(yīng)一個獨有的 MessageQueue 實例和 Thread 實例描函,這樣子線程和主線程才可以互相發(fā)送 Message 交由對方線程處理
  • Looper 內(nèi)部需要開啟一個無限循環(huán)病袄,其關(guān)聯(lián)的線程就負(fù)責(zé)從 MessageQueue 循環(huán)獲取 Message 進行處理
  • 因為主線程較為特殊,所以和主線程關(guān)聯(lián)的 Looper 對象要能夠被子線程直接獲取到赘阀,可以考慮將其作為靜態(tài)變量存著

這樣益缠,Looper 的大體框架就出來了。通過 ThreadLocal 來為不同的線程單獨維護一個 Looper 實例基公,每個線程通過 prepare()方法來初始化本線程獨有的 Looper 實例 幅慌,再通過 myLooper()方法來獲取和當(dāng)前線程關(guān)聯(lián)的 Looper 對象,和主線程關(guān)聯(lián)的 sMainLooper 作為靜態(tài)變量存在轰豆,方便子線程獲取

/**
 * @Author: leavesCZY
 * @Desc:
 * @Github:https://github.com/leavesCZY
 */
final class Looper {

    final MessageQueue mQueue;

    final Thread mThread;

    private static Looper sMainLooper;

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

    public static void prepareMainLooper() {
        prepare();
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    public static Looper myLooper() {
        return sThreadLocal.get();
    }

}

Looper 還需要有一個用于循環(huán)從 MessageQueue 獲取消息并處理的方法胰伍,就叫它loop()吧。其作用也能簡單酸休,就是循環(huán)從 MessageQueue 中取出 Message骂租,然后將 Message 再反過來分發(fā)給 Handler 即可

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;
    for (; ; ) {
        Message msg = queue.next();//可能會阻塞
        msg.target.dispatchMessage(msg);
    }
}

這樣,主線程就先通過調(diào)用prepareMainLooper()來完成 sMainLooper 的初始化斑司,然后調(diào)用loop()開始向 mainMessageQueue 循環(huán)取值并進行處理渗饮,沒有消息的話主線程就暫時休眠著。子線程拿到 sMainLooper 后就以此來初始化 Handler宿刮,這樣子線程向 Handler 發(fā)送的消息就都會被存到 mainMessageQueue 中互站,最終在主線程被消費掉

5、做一個總結(jié)

這樣一步步走下來后僵缺,讀者對于 Message胡桃、MessageQueue、Handler磕潮、Looper 這四個類的定位就應(yīng)該都很清晰了吧翠胰?不同線程之間就可以依靠拿到對方的 Looper 來實現(xiàn)消息的跨線程處理了

例如,對于以下代碼自脯,即使 Handler 是在 otherThread 中進行初始化之景,但 handleMessage 方法最終是會在 mainThread 被調(diào)用執(zhí)行的,

Thread mainThread = new Thread() {
    @Override
    public void run() {
        //初始化 mainLooper
        Looper.prepareMainLooper();
        //開啟循環(huán)
        Looper.loop();
    }
};

Thread otherThread = new Thread() {
    @Override
    public void run() {
        Looper mainLooper = Looper.getMainLooper();
        Handler handler = new Handler(mainLooper.mQueue) {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1: {
                        String ob = (String) msg.obj;
                        break;
                    }
                    case 2: {
                        List<String> ob = (List<String>) msg.obj;
                        break;
                    }
                }
            }
        };
        Message messageA = new Message();
        messageA.what = 1;
        messageA.obj = "https://github.com/leavesCZY";
        Message messageB = new Message();
        messageB.what = 2;
        messageB.obj = new ArrayList<String>();
        handler.sendMessage(messageA);
        handler.sendMessage(messageB);
    }
};

再來做個簡單的總結(jié):

  • Message:用來表示要執(zhí)行的任務(wù)
  • Handler:子線程持有的 Handler 如果綁定到的是主線程的 MessageQueue 的話冤今,那么子線程發(fā)送的 Message 就可以由主線程來消費闺兢,以此來實現(xiàn)線程切換茂缚,執(zhí)行 UI 更新操作等目的
  • MessageQueue:即消息隊列戏罢,通過 Handler 發(fā)送的消息并非都是立即執(zhí)行的屋谭,需要先按照 Message 的優(yōu)先級高低(延時時間的長短)保存到 MessageQueue 中,之后再來依次執(zhí)行
  • Looper:Looper 用于從 MessageQueue 中循環(huán)獲取 Message 并將之傳遞給消息處理者(即消息發(fā)送者 Handler 本身)來進行消費龟糕,每條 Message 都有個 target 變量用來指向 Handler桐磁,以此把 Message 和其處理者關(guān)聯(lián)起來。不同線程之間通過互相拿到對方的 Looper 對象讲岁,以此來實現(xiàn)跨線程發(fā)送消息

有了以上的認(rèn)知基礎(chǔ)后我擂,下面就來看看實際的源碼實現(xiàn) ~ ~

二、Handler 源碼

1缓艳、Handler 如何初始化

Handler 的構(gòu)造函數(shù)一共有七個校摩,除去兩個已經(jīng)廢棄的和三個隱藏的,實際上開發(fā)者可以使用的只有兩個阶淘。而不管是使用哪個構(gòu)造函數(shù)衙吩,最終的目的都是為了完成 mLooper、mQueue溪窒、mCallback坤塞、mAsynchronous 這四個常量的初始化,同時也可以看出來 MessageQueue 是由 Looper 來完成初始化的澈蚌,而且 Handler 對于 Looper 和 MessageQueue 都是一對一的關(guān)系摹芙,一旦初始化后就不可改變

大部分開發(fā)者使用的應(yīng)該都是 Handler 的無參構(gòu)造函數(shù),而在 Android 11 中 Handler 的無參構(gòu)造函數(shù)已經(jīng)被標(biāo)記為廢棄的了宛瞄。Google 官方更推薦的做法是通過顯式傳入 Looper 對象來完成初始化浮禾,而非隱式使用當(dāng)前線程關(guān)聯(lián)的 Looper

Handler 對于 Looper 和 MessageQueue 都是一對一的關(guān)系,但是 Looper 和 MessageQueue 對于 Handler 可以是一對多的關(guān)系份汗,這個后面會講到

@UnsupportedAppUsage
final Looper mLooper;
final MessageQueue mQueue;
@UnsupportedAppUsage
final Callback mCallback;
final boolean mAsynchronous;

//省略其它構(gòu)造函數(shù)

/**
 * @hide
 */
public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    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伐厌、Looper 如何初始化

在初始化 Handler 時,如果外部調(diào)用的構(gòu)造函數(shù)沒有傳入 Looper裸影,那就會調(diào)用Looper.myLooper()來獲取和當(dāng)前線程關(guān)聯(lián)的 Looper 對象挣轨,再從 Looper 中取 MessageQueue。如果獲取到的 Looper 對象為 null 就會拋出異常轩猩。根據(jù)異常信息 Can't create handler inside thread that has not called Looper.prepare() 可以看出來卷扮,在初始化 Handler 之前需要先調(diào)用 Looper.prepare()完成 Looper 的初始化

走進 Looper 類中可以看到,myLooper()方法是 Looper 類的靜態(tài)方法均践,其只是單純地從 sThreadLocal 變量中取值并返回而已晤锹。sThreadLocal 又是通過 prepare(boolean) 方法來進行初始化賦值的,且只能賦值一次彤委,重復(fù)調(diào)用將拋出異常

我們知道鞭铆,ThreadLocal 的特性就是可以為不同的線程分別維護單獨的一個變量實例,所以,不同的線程就會分別對應(yīng)著不同的 Looper 對象车遂,是一一對應(yīng)的關(guān)系

@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); 

/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

/** Initialize the current thread as a looper.
  * This gives you a chance to create handlers that then reference
  * this looper, before actually starting the loop. Be sure to call
  * {@link #loop()} after calling this method, and end it by calling
  * {@link #quit()}.
  */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
         //只允許賦值一次
        //如果重復(fù)賦值則拋出異常
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

此外封断,Looper 類的構(gòu)造函數(shù)也是私有的,會初始化兩個常量值:mQueue 和 mThread舶担,這說明了 Looper 對于 MessageQueue 和 Thread 都是一一對應(yīng)的關(guān)系坡疼,關(guān)聯(lián)之后不能改變

@UnsupportedAppUsage
final MessageQueue mQueue;

final Thread mThread;    

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

在日常開發(fā)中,我們在通過 Handler 來執(zhí)行 UI 刷新操作時衣陶,經(jīng)常使用的是 Handler 的無參構(gòu)造函數(shù)柄瑰,那么此時肯定就是使用了和主線程關(guān)聯(lián)的 Looper 對象,對應(yīng) Looper 類中的靜態(tài)變量 sMainLooper

@UnsupportedAppUsage
private static Looper sMainLooper;  // guarded by Looper.class

//被標(biāo)記為廢棄的原因是因為 sMainLooper 會交由 Android 系統(tǒng)自動來完成初始化剪况,外部不應(yīng)該主動來初始化
@Deprecated
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

/**
 * Returns the application's main looper, which lives in the main thread of the application.
 */
public static Looper getMainLooper() {
    synchronized (Looper.class) {
        return sMainLooper;
    }
}

prepareMainLooper()就用于為主線程初始化 Looper 對象教沾,該方法又是由 ActivityThread 類的 main() 方法來調(diào)用的。該 main() 方法即 Java 程序的運行起始點译断,所以當(dāng)應(yīng)用啟動時系統(tǒng)就自動為我們在主線程做好了 mainLooper 的初始化详囤,而且已經(jīng)調(diào)用了Looper.loop()方法開啟了消息的循環(huán)處理,應(yīng)用在使用過程中的各種交互邏輯(例如:屏幕的觸摸事件镐作、列表的滑動等)就都是在這個循環(huán)里完成分發(fā)的

正是因為 Android 系統(tǒng)已經(jīng)自動完成了主線程 Looper 的初始化藏姐,所以我們在主線程中才可以直接使用 Handler 的無參構(gòu)造函數(shù)來完成 UI 相關(guān)事件的處理

public final class ActivityThread extends ClientTransactionHandler {
 
    public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();
        ···
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
}

3、Handler 發(fā)送消息

Handler 用于發(fā)送消息的方法非常多该贾,有十幾個羔杨,其中大部分最終調(diào)用到的都是 sendMessageAtTime() 方法。uptimeMillis 即 Message 具體要執(zhí)行的時間戳杨蛋,如果該時間戳比當(dāng)前時間大兜材,那么就意味著要執(zhí)行的是延遲任務(wù)。如果為 mQueue 為 null逞力,就會打印異常信息并直接返回曙寡,因為 Message 是需要交由 MessageQueue 來處理的

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

需要注意 msg.target = this 這句代碼,target 指向了發(fā)送消息的主體寇荧,即 Handler 對象本身举庶,即由 Handler 對象發(fā)給 MessageQueue 的消息最后還是要交由 Handler 對象本身來處理

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //將消息交由 MessageQueue 處理
    return queue.enqueueMessage(msg, uptimeMillis);
}

4、MessageQueue

MessageQueue 通過 enqueueMessage 方法來接收消息

  • 因為存在多個線程同時往一個 MessageQueue 發(fā)送消息的可能揩抡,所以 enqueueMessage 內(nèi)部肯定需要進行線程同步
  • 可以看出 MessageQueue 內(nèi)部是以鏈表的結(jié)構(gòu)來存儲 Message 的(Message.next)户侥,根據(jù) Message 的時間戳大小來決定其在消息隊列中的位置
  • mMessages 代表的是消息隊列中的第一條消息。如果 mMessages 為空(消息隊列是空的)峦嗤,或者 mMessages 的時間戳要比新消息的時間戳大蕊唐,則將新消息插入到消息隊列的頭部;如果 mMessages 不為空烁设,則尋找消息列隊中第一條觸發(fā)時間比新消息晚的非空消息替梨,將新消息插到該消息的前面

到此,一個按照時間戳大小進行排序的消息隊列就完成了,后邊要做的就是從消息隊列中依次取出消息進行處理了

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ···
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        //用于標(biāo)記是否需要喚醒線程
        boolean needWake;
        //如果鏈表是空的副瀑,或者處于隊頭的消息的時間戳比 msg 要大弓熏,則將 msg 作為鏈表頭部
        //when == 0 說明 Handler 調(diào)用的是 sendMessageAtFrontOfQueue 方法,直接將 msg 插到隊列頭部 
        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 {
            //如果當(dāng)前線程處于休眠狀態(tài) + 隊頭消息是屏障消息 + msg 是異步消息
            //那么就需要喚醒線程
            needWake = mBlocked && p.target == null && msg.isAsynchronous();

            Message prev;
            //從鏈表頭向鏈表尾遍歷俗扇,尋找鏈表中第一條時間戳比 msg 大的消息硝烂,將 msg 插到該消息的前面
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    //如果在 msg 之前隊列中還有異步消息那么就不需要主動喚醒
                    //因為已經(jīng)設(shè)定喚醒時間了
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

知道了 Message 是如何保存的了箕别,再來看下 MessageQueue 是如何取出 Message 并回調(diào)給 Handler 的铜幽。在 MessageQueue 中讀取消息的操作對應(yīng)的是next() 方法。next() 方法內(nèi)部開啟了一個無限循環(huán)串稀,如果消息隊列中沒有消息或者是隊頭消息還沒到可以處理的時間除抛,該方法就會導(dǎo)致 Loop 線程休眠掛起,直到條件滿足后再重新遍歷消息

@UnsupportedAppUsage
Message next() {
    ···
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        //將 Loop 線程休眠掛起
        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;
            }
            ···
        }
        ···
        }
        ···
    }
}

next() 方法又是通過 Looper 類的 loop() 方法來循環(huán)調(diào)用的到忽,loop() 方法內(nèi)也是一個無限循環(huán),唯一跳出循環(huán)的條件就是 queue.next()方法返回為 null清寇。因為 next() 方法可能會觸發(fā)阻塞操作喘漏,所以沒有消息需要處理時也會導(dǎo)致 loop() 方法被阻塞著,而當(dāng) MessageQueue 有了新的消息华烟,Looper 就會及時地處理這條消息并調(diào)用 msg.target.dispatchMessage(msg) 方法將消息回傳給 Handler 進行處理

/**
 * 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.");
    }
    ··· 
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ···
        msg.target.dispatchMessage(msg);
        ···
    }
}

Handler 的dispatchMessage方法就是在向外部分發(fā)處理 Message 了翩迈,Message 最終是按如下順序進行處理的:

  • 如果該 Message 是通過 post(Runnable)等方法進行發(fā)送的页响,那么就直接回調(diào)該 Runnable 對象
  • 如果在初始化 Handler 時傳入了 Callback 對象创译,則優(yōu)先交由其處理,如果 Callback 的 handleMessage 方法返回了 true蜜暑,則結(jié)束流程
  • 調(diào)用 Handler 的handleMessage 方法來處理消息喂链,外部通過重寫該方法來定義業(yè)務(wù)邏輯
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();
}

public void handleMessage(@NonNull Message msg) {
}

至此返十,Message 的整個分發(fā)流程就結(jié)束了

5、消息屏障

Android 系統(tǒng)為了保證某些高優(yōu)先級的 Message(異步消息) 能夠被盡快執(zhí)行椭微,采用了一種消息屏障(Barrier)機制洞坑。其大致流程是:先發(fā)送一個屏障消息到 MessageQueue 中,當(dāng) MessageQueue 遍歷到該屏障消息時蝇率,就會判斷當(dāng)前隊列中是否存在異步消息检诗,有的話則先跳過同步消息(開發(fā)者主動發(fā)送的都屬于同步消息),優(yōu)先執(zhí)行異步消息瓢剿。這種機制就會使得在異步消息被執(zhí)行完之前逢慌,同步消息都不會得到處理

Handler 的構(gòu)造函數(shù)中的async參數(shù)就用于控制發(fā)送的 Message 是否屬于異步消息

public class Handler {

    final boolean mAsynchronous;

    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        ···
        mAsynchronous = async;
    }

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        if (mAsynchronous) {
            //設(shè)為異步消息
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

}

MessageQueue 在獲取隊頭消息的時候,如果判斷到隊頭消息的 target 對象為 null 的話间狂,就知道該 Message 屬于屏障消息攻泼,那么就會再向后遍歷找到第一條異步消息優(yōu)先進行處理,每次調(diào)用 next() 方法時都會重復(fù)此步驟知道所有異步消息均被處理完

@UnsupportedAppUsage
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) { //target 為 null 即屬于屏障消息
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                //循環(huán)遍歷,找到屏障消息后面的第一條異步消息進行處理
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            ···
        }
        ···
    }
}

我們可以通過調(diào)用 Message 對象的 setAsynchronous(boolean async) 方法來主動發(fā)送異步消息忙菠。而如果想要主動發(fā)送屏障消息的話何鸡,就需要通過反射來調(diào)用 MessageQueue 的 postSyncBarrier() 方法了,該方法被系統(tǒng)隱藏起來了

屏障消息的作用就是可以確保高優(yōu)先級的異步消息可以優(yōu)先被處理牛欢,ViewRootImpl 就通過該機制來使得 View 的繪制流程可以優(yōu)先被處理

6骡男、退出 Looper 循環(huán)

Looper 類本身做了方法限制,除了主線程外傍睹,子線程關(guān)聯(lián)的 MessageQueue 都支持退出 Loop 循環(huán)隔盛,即 quitAllowed 只有主線程才能是 false

public final class Looper {
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
    public static void prepare() {
        prepare(true);
    }

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

MessageQueue 支持兩種方式來退出 Loop:

  • safe 為 true,只移除所有尚未執(zhí)行的消息拾稳,不移除時間戳等于當(dāng)前時間的消息
  • safe 為 false吮炕,移除所有消息
void quit(boolean safe) {
    if (!mQuitAllowed) {
        //MessageQueue 設(shè)置了不允許退出循環(huán),直接拋出異常
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            //避免重復(fù)調(diào)用
            return;
        }
        mQuitting = true;
        if (safe) {
            //只移除所有尚未執(zhí)行的消息访得,不移除時間戳等于當(dāng)前時間的消息
            removeAllFutureMessagesLocked();
        } else {
            //移除所有消息
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

7龙亲、IdleHandler

IdleHandler 是 MessageQueue 的一個內(nèi)部接口,可以用于在 Loop 線程處于空閑狀態(tài)的時候執(zhí)行一些優(yōu)先級不高的操作悍抑,通過 MessageQueue 的 addIdleHandler 方法來提交要執(zhí)行的操作

public static interface IdleHandler {
    boolean queueIdle();
}

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();

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

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

MessageQueue 在執(zhí)行 next() 方法時鳄炉,如果發(fā)現(xiàn)當(dāng)前隊列是空的或者隊頭消息需要延遲處理的話,那么就會去嘗試遍歷 mIdleHandlers來依次執(zhí)行 IdleHandler

@UnsupportedAppUsage
Message next() {
    ···
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        ···
        synchronized (this) {
            ···
            //如果隊頭消息 mMessages 為 null 或者 mMessages 需要延遲處理
            //那么就來執(zhí)行 IdleHandler
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        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 {
                //執(zhí)行 IdleHandler
                //如果返回 false 的話說明之后不再需要執(zhí)行搜骡,那就將其移除
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        ···
    }
}

例如拂盯,ActivityThread 就向主線程 MessageQueue 添加了一個 GcIdler,用于在主線程空閑時嘗試去執(zhí)行 GC 操作

public final class ActivityThread extends ClientTransactionHandler {
    
    @UnsupportedAppUsage
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            //添加 IdleHandler
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
    
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            //嘗試 GC
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }
    
}

8浆兰、做一個總結(jié)

再來總結(jié)下以上的所有內(nèi)容

  1. 每個 Handler 都會和一個 Looper 實例關(guān)聯(lián)在一起磕仅,可以在初始化 Handler 時通過構(gòu)造函數(shù)主動傳入實例,否則就會默認(rèn)使用和當(dāng)前線程關(guān)聯(lián)的 Looper 對象
  2. 每個 Looper 都會和一個 MessageQueue 實例關(guān)聯(lián)在一起簸呈,每個線程都需要通過調(diào)用 Looper.prepare()方法來初始化本線程獨有的 Looper 實例榕订,并通過調(diào)用Looper.loop()方法來使得本線程循環(huán)向 MessageQueue 取出消息并執(zhí)行。Android 系統(tǒng)默認(rèn)會為每個應(yīng)用初始化和主線程關(guān)聯(lián)的 Looper 對象蜕便,并且默認(rèn)就開啟了 loop 循環(huán)來處理主線程消息
  3. MessageQueue 按照鏈接結(jié)構(gòu)來保存 Message劫恒,執(zhí)行時間早(即時間戳小)的 Message 會排在鏈表的頭部轿腺,Looper 會循環(huán)從鏈表中取出 Message 并回調(diào)給 Handler两嘴,取值的過程可能會包含阻塞操作
  4. Message、Handler族壳、Looper憔辫、MessageQueue 這四者就構(gòu)成了一個生產(chǎn)者和消費者模式。Message 相當(dāng)于產(chǎn)品仿荆,MessageQueue 相當(dāng)于傳輸管道贰您,Handler 相當(dāng)于生產(chǎn)者坏平,Looper 相當(dāng)于消費者
  5. Handler 對于 Looper、Handler 對于 MessageQueue锦亦、Looper 對于 MessageQueue舶替、Looper 對于 Thread ,這幾個之間都是一一對應(yīng)的關(guān)系杠园,在關(guān)聯(lián)后無法更改顾瞪,但 Looper 對于 Handler、MessageQueue 對于 Handler 可以是一對多的關(guān)系
  6. Handler 能用于更新 UI 包含了一個隱性的前提條件:Handler 與主線程 Looper 關(guān)聯(lián)在了一起抛蚁。在主線程中初始化的 Handler 會默認(rèn)與主線程 Looper 關(guān)聯(lián)在一起陈醒,所以其 handleMessage(Message msg) 方法就會由主線程來調(diào)用。在子線程初始化的 Handler 如果也想執(zhí)行 UI 更新操作的話篮绿,則需要主動獲取 mainLooper 來初始化 Handler
  7. 對于我們自己在子線程中創(chuàng)建的 Looper孵延,當(dāng)不再需要的時候我們應(yīng)該主動退出循環(huán)吕漂,否則子線程將一直無法得到釋放亲配。對于主線程 Loop 我們則不應(yīng)該去主動退出,否則將導(dǎo)致應(yīng)用崩潰
  8. 我們可以通過向 MessageQueue 添加 IdleHandler 的方式惶凝,來實現(xiàn)在 Loop 線程處于空閑狀態(tài)的時候執(zhí)行一些優(yōu)先級不高的任務(wù)吼虎。例如,假設(shè)我們有個需求是希望當(dāng)主線程完成界面繪制等事件后再執(zhí)行一些 UI 操作苍鲜,那么就可以通過 IdleHandler 來實現(xiàn)思灰,這可以避免拖慢用戶看到首屏頁面的速度

三、Handler 在系統(tǒng)中的應(yīng)用

1混滔、HandlerThread

HandlerThread 是 Android SDK 中和 Handler 在同個包下的一個類洒疚,從其名字就可以看出來它是一個線程,而且使用到了 Handler

其用法類似于以下代碼坯屿。通過 HandlerThread 內(nèi)部的 Looper 對象來初始化 Handler油湖,同時在 Handler 中聲明需要執(zhí)行的耗時任務(wù),主線程通過向 Handler 發(fā)送消息來觸發(fā) HandlerThread 去執(zhí)行耗時任務(wù)

class MainActivity : AppCompatActivity() {

    private val handlerThread = HandlerThread("I am HandlerThread")

    private val handler by lazy {
        object : Handler(handlerThread.looper) {
            override fun handleMessage(msg: Message) {
                Thread.sleep(2000)
                Log.e("MainActivity", "這里是子線程领跛,可以用來執(zhí)行耗時任務(wù):" + Thread.currentThread().name)
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {
            handler.sendEmptyMessage(1)
        }
        handlerThread.start()
    }

}

HandlerThread 的源碼還是挺簡單的乏德,只有一百多行

HandlerThread 是 Thread 的子類,其作用就是為了用來執(zhí)行耗時任務(wù)吠昭,其 run()方法會自動為自己創(chuàng)建一個 Looper 對象并保存到 mLooper喊括,之后就主動開啟消息循環(huán),這樣 HandlerThread 就會來循環(huán)處理 Message 了

public class HandlerThread extends Thread {
    
    //線程優(yōu)先級
    int mPriority;
    //線程ID
    int mTid = -1;
    //當(dāng)前線程持有的 Looper 對象
    Looper mLooper;
    
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    @Override
    public void run() {
        mTid = Process.myTid();
        //觸發(fā)當(dāng)前線程創(chuàng)建 Looper 對象
        Looper.prepare();
        synchronized (this) {
            //獲取 Looper 對象
            mLooper = Looper.myLooper();
            //喚醒所有處于等待狀態(tài)的線程
            notifyAll();
        }
        //設(shè)置線程優(yōu)先級
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        //開啟消息循環(huán)
        Looper.loop();
        mTid = -1;
    }
    
}

此外矢棚,HandlerThread 還包含一個getLooper()方法用于獲取 Looper郑什。當(dāng)我們在外部調(diào)用handlerThread.start()啟動線程后,由于其run()方法的執(zhí)行時機依然是不確定的蒲肋,所以 getLooper()方法就必須等到 Looper 初始化完畢后才能返回蘑拯,否則就會由于wait()方法而一直阻塞等待劫拢。當(dāng)run()方法初始化 Looper 完成后,就會調(diào)用notifyAll()來喚醒所有處于等待狀態(tài)的線程强胰。所以外部在使用 HandlerThread 前就記得必須先調(diào)用 start() 方法來啟動 HandlerThread

//獲取與 HandlerThread 關(guān)聯(lián)的 Looper 對象
//因為 getLooper() 可能先于 run() 被執(zhí)行
//所以當(dāng) mLooper 為 null 時調(diào)用者線程就需要阻塞等待 Looper 對象創(chuàng)建完畢
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;
}

HandlerThread 起到的作用就是方便了主線程和子線程之間的交互舱沧,主線程可以直接通過 Handler 來聲明耗時任務(wù)并交由子線程來執(zhí)行。使用 HandlerThread 也方便在多個線程間共享偶洋,主線程和其它子線程都可以向 HandlerThread 下發(fā)任務(wù)熟吏,且 HandlerThread 可以保證多個任務(wù)執(zhí)行時的有序性

2、IntentService

IntentService 是系統(tǒng)提供的 Service 子類玄窝,用于在后臺串行執(zhí)行耗時任務(wù)牵寺,在處理完所有任務(wù)后會自動停止,不必來手動調(diào)用 stopSelf() 方法恩脂。而且由于IntentService 是四大組件之一帽氓,擁有較高的優(yōu)先級,不易被系統(tǒng)殺死俩块,因此適合用于執(zhí)行一些高優(yōu)先級的異步任務(wù)

Google 官方以前也推薦開發(fā)者使用 IntentService黎休,但是在 Android 11 中已經(jīng)被標(biāo)記為廢棄狀態(tài)了,但這也不妨礙我們來了解下其實現(xiàn)原理

IntentService 內(nèi)部依靠 HandlerThread 來實現(xiàn)玉凯,其 onCreate()方法會創(chuàng)建一個 HandlerThread势腮,拿到 Looper 對象來初始化 ServiceHandler。ServiceHandler 會將其接受到的每個 Message 都轉(zhuǎn)交由抽象方法 onHandleIntent來處理漫仆,子類就通過實現(xiàn)該方法來聲明耗時任務(wù)

public abstract class IntentService extends Service {
    
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
    
    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 + "]");
        //觸發(fā) HandlerThread 創(chuàng)建 Looper 對象
        thread.start();
        //獲取 Looper 對象捎拯,構(gòu)建可以向 HandlerThread 發(fā)送 Message 的 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
    
}

每次 start IntentService 時,onStart()方法就會被調(diào)用盲厌,將 intentstartId 包裝為一個 Message 對象后發(fā)送給mServiceHandler署照。需要特別注意的是 startId 這個參數(shù),它用于唯一標(biāo)識每次對 IntentService 發(fā)起的任務(wù)請求吗浩,每次回調(diào) onStart() 方法時建芙,startId 的值都是自動遞增的。IntentService 不應(yīng)該在處理完一個 Message 之后就立即停止 IntentService拓萌,因為此時 MessageQueue 中可能還有待處理的任務(wù)還未取出來岁钓,所以如果當(dāng)調(diào)用 stopSelf(int)方法時傳入的參數(shù)不等于當(dāng)前最新的 startId 值的話,那么stopSelf(int) 方法就不會導(dǎo)致 IntentService 被停止微王,從而避免了將尚未處理的 Message 給遺漏了

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

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

四屡限、Handler 在三方庫中的應(yīng)用

1、EventBus

EventBus 的 Github 上有這么一句介紹:EventBus is a publish/subscribe event bus for Android and Java. 這說明了 EventBus 是普遍適用于 Java 環(huán)境的炕倘,只是對 Android 系統(tǒng)做了特殊的平臺支持而已钧大。EventBus 的四種消息發(fā)送策略包含了ThreadMode.MAIN 用于指定在主線程進行消息回調(diào),其內(nèi)部就是通過 Handler 來實現(xiàn)的

EventBusBuilder 會去嘗試獲取 MainLooper罩旋,如果拿得到的話就可以用來初始化 HandlerPoster啊央,從而實現(xiàn)主線程回調(diào)

MainThreadSupport getMainThreadSupport() {
    if (mainThreadSupport != null) {
        return mainThreadSupport;
    } else if (AndroidLogger.isAndroidLogAvailable()) {
        Object looperOrNull = getAndroidMainLooperOrNull();
        return looperOrNull == null ? null :
                new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
    } else {
        return null;
    }
}

static Object getAndroidMainLooperOrNull() {
    try {
        return Looper.getMainLooper();
    } catch (RuntimeException e) {
        // Not really a functional Android (e.g. "Stub!" maven dependencies)
        return null;
    }
}
public class HandlerPoster extends Handler implements Poster {

    protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        ···
    }
    
    ···
        
    @Override
    public void handleMessage(Message msg) {
        ···
    }
}

2眶诈、Retrofit

和 EventBus 一樣,Retrofit 的內(nèi)部實現(xiàn)也不需要依賴于 Android 平臺瓜饥,而是可以用于任意的 Java 客戶端逝撬,Retrofit 只是對 Android 平臺進行了特殊實現(xiàn)而已

在構(gòu)建 Retrofit 對象的時候,我們可以選擇傳遞一個 Platform 對象用于標(biāo)記調(diào)用方所處的平臺

public static final class Builder {
    
    private final Platform platform;
    
    Builder(Platform platform) {
      this.platform = platform;
    }

    ···
}

Platform 類只具有一個唯一子類乓土,即 Android 類宪潮。其主要邏輯就是重寫了父類的 defaultCallbackExecutor()方法,通過 Handler 來實現(xiàn)在主線程回調(diào)網(wǎng)絡(luò)請求結(jié)果

static final class Android extends Platform {
    
    @Override
    public Executor defaultCallbackExecutor() {
      return new MainThreadExecutor();
    }

    ···

    static final class MainThreadExecutor implements Executor {
      private final Handler handler = new Handler(Looper.getMainLooper());

      @Override
      public void execute(Runnable r) {
        handler.post(r);
      }
    }
  }

五趣苏、提問環(huán)節(jié)

1狡相、Handler、Looper食磕、MessageQueue尽棕、Thread 的對應(yīng)關(guān)系

首先,Looper 中的 MessageQueue 和 Thread 兩個字段都屬于常量彬伦,且 Looper 實例是存在 ThreadLocal 中滔悉,這說明了 Looper 和 MessageQueue 之間是一對一應(yīng)的關(guān)系,且一個 Thread 在其整個生命周期內(nèi)都只會關(guān)聯(lián)到同一個 Looper 對象和同一個 MessageQueue 對象

public final class Looper {
 
   final MessageQueue mQueue;
   final Thread mThread;
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
   private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    
}

Handler 中的 Looper 和 MessageQueue 兩個字段也都屬于常量媚朦,說明 Handler 對于 Looper 和 MessageQueue 都是一對一的關(guān)系氧敢。但是 Looper 和 MessageQueue 對于 Handler 卻可以是一對多的關(guān)系日戈,例如询张,多個子線程內(nèi)聲明的 Handler 都可以關(guān)聯(lián)到 mainLooper

public class Handler {
    
    @UnsupportedAppUsage
    final Looper mLooper;
    final MessageQueue mQueue;
    
}

2、Handler 的同步機制

MessageQueue 在保存 Message 的時候浙炼,enqueueMessage方法內(nèi)部已經(jīng)加上了同步鎖份氧,從而避免了多個線程同時發(fā)送消息導(dǎo)致競態(tài)問題。此外弯屈,next()方法內(nèi)部也加上了同步鎖蜗帜,所以也保障了 Looper 分發(fā) Message 的有序性。最重要的一點是资厉,Looper 總是由一個特定的線程來執(zhí)行遍歷厅缺,所以在消費 Message 的時候也不存在競態(tài)

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    synchronized (this) {
        ···
    }
    return true;
}

@UnsupportedAppUsage
Message next() {
    ···
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            ···
        }

        ···
    }
}

3、Handler 發(fā)送同步消息

如果我們在子線程通過 Handler 向主線程發(fā)送了一個消息宴偿,希望等到消息執(zhí)行完畢后子線程才繼續(xù)運行湘捎,這該如何實現(xiàn)?其實像這種涉及到多線程同步等待的問題窄刘,往往都是需要依賴于線程休眠+線程喚醒機制來實現(xiàn)的

Handler 本身就提供了一個runWithScissors方法可以用于實現(xiàn)這種功能窥妇,只是被隱藏了,我們無法直接調(diào)用到娩践。runWithScissors首先會判斷目標(biāo)線程是否就是當(dāng)前線程活翩,是的話則直接執(zhí)行 Runnable烹骨,否則就需要使用到 BlockingRunnable

/**
 * @hide
 */
public final boolean runWithScissors(@NonNull Runnable r, long timeout) {
    if (r == null) {
        throw new IllegalArgumentException("runnable must not be null");
    }
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout must be non-negative");
    }

    if (Looper.myLooper() == mLooper) {
        r.run();
        return true;
    }

    BlockingRunnable br = new BlockingRunnable(r);
    return br.postAndWait(this, timeout);
}

BlockingRunnable 的邏輯也很簡單,在 Runnable 執(zhí)行完前會通過調(diào)用 wait()方法來使發(fā)送者線程轉(zhuǎn)為阻塞等待狀態(tài)材泄,當(dāng)任務(wù)執(zhí)行完畢后再通過notifyAll()來喚醒發(fā)送者線程沮焕,從而實現(xiàn)了在 Runnable 被執(zhí)行完之前發(fā)送者線程都會一直處于等待狀態(tài)

private static final class BlockingRunnable implements Runnable {
    
        private final Runnable mTask;
        //用于標(biāo)記 mTask 是否已經(jīng)執(zhí)行完畢 
        private boolean mDone;

        public BlockingRunnable(Runnable task) {
            mTask = task;
        }

        @Override
        public void run() {
            try {
                mTask.run();
            } finally {
                synchronized (this) {
                    mDone = true;
                    notifyAll();
                }
            }
        }

        public boolean postAndWait(Handler handler, long timeout) {
            if (!handler.post(this)) {
                return false;
            }

            synchronized (this) {
                if (timeout > 0) {
                    final long expirationTime = SystemClock.uptimeMillis() + timeout;
                    while (!mDone) {
                        long delay = expirationTime - SystemClock.uptimeMillis();
                        if (delay <= 0) {
                            return false; // timeout
                        }
                        try {
                            //限時等待
                            wait(delay);
                        } catch (InterruptedException ex) {
                        }
                    }
                } else {
                    while (!mDone) {
                        try {
                            //無限期等待
                            wait();
                        } catch (InterruptedException ex) {
                        }
                    }
                }
            }
            return true;
        }
    }

雖然 runWithScissors 方法我們無法直接調(diào)用,但是我們也可以依靠這思路自己來實現(xiàn) BlockingRunnable拉宗,折中實現(xiàn)這個功能遇汞。但這種方式并不安全,如果 Loop 意外退出循環(huán)導(dǎo)致該 Runnable 無法被執(zhí)行的話簿废,就會導(dǎo)致被暫停的線程一直無法被喚醒空入,需要謹(jǐn)慎使用

4、Handler 避免內(nèi)存泄漏

當(dāng)退出 Activity 時族檬,如果作為內(nèi)部類的 Handler 中還保存著待處理的延時消息的話歪赢,那么就會導(dǎo)致內(nèi)存泄漏,可以通過調(diào)用Handler.removeCallbacksAndMessages(null)來移除所有待處理的 Message

該方法會將消息隊列中所有 Message.obj 等于 token 的 Message 均給移除掉单料,如果 token 為 null 的話則會移除所有 Message

public final void removeCallbacksAndMessages(@Nullable Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

5埋凯、Message 如何復(fù)用

因為 Android 系統(tǒng)本身就存在很多事件需要交由 Message 來交付給 mainLooper,所以 Message 的創(chuàng)建是很頻繁的扫尖。為了減少 Message 頻繁重復(fù)創(chuàng)建的情況白对,Message 提供了 MessagePool 用于實現(xiàn) Message 的緩存復(fù)用,以此來優(yōu)化內(nèi)存使用

當(dāng) Looper 消費了 Message 后會調(diào)用recycleUnchecked()方法將 Message 進行回收换怖,在清除了各項資源后會緩存到 sPool 變量上甩恼,同時將之前緩存的 Message 置為下一個節(jié)點 next,通過這種鏈表結(jié)構(gòu)來緩存最多 50 個Message沉颂。這里使用到的是享元設(shè)計模式

obtain()方法則會判斷當(dāng)前是否有可用的緩存条摸,有的話則將 sPool 從鏈表中移除后返回,否則就返回一個新的 Message 實例铸屉。所以我們在發(fā)送消息的時候應(yīng)該盡量通過調(diào)用Message.obtain()或者Handler.obtainMessage()方法來獲取 Message 實例

public final class Message implements Parcelable {
    
    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    
    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();
    }
    
    @UnsupportedAppUsage
    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++;
            }
        }
    }
    
}

6钉蒲、Message 復(fù)用機制存在的問題

由于 Message 采用了緩存復(fù)用機制,從而導(dǎo)致了一個 Message 失效問題彻坛。當(dāng) handleMessage 方法被回調(diào)后顷啼,Message 攜帶的所有參數(shù)都會被清空,而如果外部的 handleMessage方法是使用了異步線程來處理 Message 的話昌屉,那么異步線程只會得到一個空白的 Message

val handler = object : Handler() {
    override fun handleMessage(msg: Message) {
        handleMessageAsync(msg)
    }
}

fun handleMessageAsync(msg: Message) {
    thread {
        //只會得到一個空白的 Message 對象
        println(msg.obj)
    }
}

7钙蒙、Message 如何提高優(yōu)先級

Handler 包含一個 sendMessageAtFrontOfQueue方法可以用于提高 Message 的處理優(yōu)先級。該方法為 Message 設(shè)定的時間戳是 0怠益,使得 Message 可以直接插入到 MessageQueue 的頭部仪搔,從而做到優(yōu)先處理。但官方并不推薦使用這個方法蜻牢,因為最極端的情況下可能會使得其它 Message 一直得不到處理或者其它意想不到的情況

public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, 0);
}

8烤咧、檢測 Looper 分發(fā) Message 的效率

Looper 在進行 Loop 循環(huán)時偏陪,會通過 Observer 向外回調(diào)每個 Message 的回調(diào)事件。且如果設(shè)定了 slowDispatchThresholdMsslowDeliveryThresholdMs 這兩個閾值的話煮嫌,則會對 Message 的分發(fā)時機分發(fā)耗時進行監(jiān)測笛谦,存在異常情況的話就會打印 Log。該機制可以用于實現(xiàn)應(yīng)用性能監(jiān)測昌阿,發(fā)現(xiàn)潛在的 Message 處理異常情況饥脑,但可惜監(jiān)測方法被系統(tǒng)隱藏了

public static void loop() {
    final Looper me = myLooper();
    ···
    for (;;) {
        Message msg = queue.next(); // might block
        ···
        //用于向外回調(diào)通知 Message 的分發(fā)事件
        final Observer observer = sObserver;

        final long traceTag = me.mTraceTag;
        //如果Looper分發(fā)Message的時間晚于預(yù)定時間且超出這個閾值,則認(rèn)為Looper分發(fā)過慢
        long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
        //如果向外分發(fā)出去的Message的處理時間超出這個閾值懦冰,則認(rèn)為外部處理過慢
        long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
        if (thresholdOverride > 0) {
            slowDispatchThresholdMs = thresholdOverride;
            slowDeliveryThresholdMs = thresholdOverride;
        }
        final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
        final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

        final boolean needStartTime = logSlowDelivery || logSlowDispatch;
        final boolean needEndTime = logSlowDispatch;

        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }

        //開始分發(fā) Message 的時間
        final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
        //Message 分發(fā)結(jié)束的時間
        final long dispatchEnd;
        Object token = null;
        if (observer != null) {
            //開始分發(fā) Message 
            token = observer.messageDispatchStarting();
        }
        long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
        try {
            msg.target.dispatchMessage(msg);
            if (observer != null) {
                //完成 Message 的分發(fā)灶轰,且沒有拋出異常
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                //分發(fā) Message 時拋出了異常
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        if (logSlowDelivery) {
            if (slowDeliveryDetected) {
                if ((dispatchStart - msg.when) <= 10) {
                    //如果 Message 的分發(fā)時間晚于預(yù)定時間,且間隔超出10毫秒刷钢,則認(rèn)為屬于延遲交付
                    Slog.w(TAG, "Drained");
                    slowDeliveryDetected = false;
                }
            } else {
                if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                        msg)) {
                    // Once we write a slow delivery log, suppress until the queue drains.
                    slowDeliveryDetected = true;
                }
            }
        }
        ···
    }
}

9笋颤、主線程 Looper 在哪里創(chuàng)建

由 ActivityThread 類的 main() 方法來創(chuàng)建。該 main() 方法即 Java 程序的運行起始點内地,當(dāng)應(yīng)用啟動時系統(tǒng)就自動為我們在主線程做好了 mainLooper 的初始化伴澄,而且已經(jīng)調(diào)用了Looper.loop()方法開啟了消息的循環(huán)處理,應(yīng)用在使用過程中的各種交互邏輯(例如:屏幕的觸摸事件阱缓、列表的滑動等)就都是在這個循環(huán)里完成分發(fā)的非凌。正是因為 Android 系統(tǒng)已經(jīng)自動完成了主線程 Looper 的初始化,所以我們在主線程中才可以直接使用 Handler 的無參構(gòu)造函數(shù)來完成 UI 相關(guān)事件的處理

public final class ActivityThread extends ClientTransactionHandler {
 
    public static void main(String[] args) {
        ···
        Looper.prepareMainLooper();
        ···
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
}

10荆针、主線程 Looper 什么時候退出循環(huán)

當(dāng) ActivityThread 內(nèi)部的 Handler 收到了 EXIT_APPLICATION 消息后敞嗡,就會退出 Looper 循環(huán)

public void handleMessage(Message msg) {
    switch (msg.what) {
        case EXIT_APPLICATION:
            if (mInitialApplication != null) {
                mInitialApplication.onTerminate();
            }
            Looper.myLooper().quit();
            break;
    }
}

11、主線程 Looper.loop() 為什么不會導(dǎo)致 ANR

這個問題在網(wǎng)上很常見祭犯,我第一次看到時就覺得這種問題很奇怪秸妥,主線程憑啥會 ANR?這個問題感覺本身就是特意為了來誤導(dǎo)人

看以下例子沃粗。doSomeThing()方法是放在 for 循環(huán)這個死循環(huán)的后邊,對于該方法來說键畴,主線程的確是被阻塞住了最盅,導(dǎo)致該方法一直無法得到執(zhí)行∑鹛瑁可是對于應(yīng)用來說涡贱,應(yīng)用在主線程內(nèi)的所有操作其實都是被放在了 for 循環(huán)之內(nèi),一直有得到執(zhí)行惹想,是個死循環(huán)也無所謂问词,所以對于應(yīng)用來說主線程并沒有被阻塞,自然不會導(dǎo)致 ANR嘀粱。此外激挪,當(dāng) MessageQueue 中當(dāng)前沒有消息需要處理時辰狡,也會依靠 epoll 機制掛起主線程,避免了其一直占用 CPU 資源

public static void main(String[] args) {
    for (; ; ) {
        //主線程執(zhí)行....
    }
    doSomeThing();
}

所以在 ActivityThread 的 main 方法中垄分,在開啟了消息循環(huán)之后宛篇,并沒有聲明什么有意義的代碼。正常來說應(yīng)用是不會退出 loop 循環(huán)的薄湿,如果能夠跳出循環(huán)叫倍,也只會導(dǎo)致直接就拋出異常

public static void main(String[] args) {
    ···
    Looper.prepareMainLooper();
    ···
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

所以說,loop 循環(huán)本身不會導(dǎo)致 ANR豺瘤,會出現(xiàn) ANR 是因為在 loop 循環(huán)之內(nèi) Message 處理時間過長

12吆倦、子線程一定無法彈 Toast 嗎

不一定,只能說是在子線程中無法直接彈出 Toast坐求,但可以實現(xiàn)逼庞。因為 Toast 的構(gòu)造函數(shù)中會要求拿到一個 Looper 對象,如果構(gòu)造參數(shù)沒有傳入不為 null 的 Looper 實例的話瞻赶,則嘗試使用調(diào)用者線程關(guān)聯(lián)的 Looper 對象赛糟,如果都獲取不到的話則會拋出異常

public Toast(Context context) {
    this(context, null);
}

public Toast(@NonNull Context context, @Nullable Looper looper) {
    mContext = context;
    mToken = new Binder();
    looper = getLooper(looper);
    mHandler = new Handler(looper);
    ···
}

private Looper getLooper(@Nullable Looper looper) {
    if (looper != null) {
        return looper;
    }
    //Looper.myLooper() 為 null 的話就會直接拋出異常
    return checkNotNull(Looper.myLooper(),
            "Can't toast on a thread that has not called Looper.prepare()");
}

為了在子線程彈 Toast,就需要主動為子線程創(chuàng)建 Looper 對象及開啟 loop 循環(huán)砸逊。但這種方法會導(dǎo)致子線程一直無法退出循環(huán)璧南,需要通過Looper.myLooper().quit()來主動退出循環(huán)

inner class TestThread : Thread() {

    override fun run() {
        Looper.prepare()
        Toast.makeText(
            this@MainActivity,
            "Hello: " + Thread.currentThread().name,
            Toast.LENGTH_SHORT
        ).show()
        Looper.loop()
    }

}

13、子線程一定無法更新 UI师逸?主線程就一定可以司倚?

在子線程能夠彈出 Toast 就已經(jīng)說明了子線程也是可以更新 UI 的,Android 系統(tǒng)只是限制了必須在同個線程內(nèi)進行 ViewRootImpl 的創(chuàng)建和更新這兩個操作篓像,而不是要求必須在主線程進行

如果使用不當(dāng)?shù)脑挾词乖谥骶€程更新 UI 也可能會導(dǎo)致應(yīng)用崩潰。例如员辩,在子線程先通過 show+hide 來觸發(fā) ViewRootImpl 的創(chuàng)建盒粮,然后在主線程再來嘗試顯示該 Dialog,此時就會發(fā)現(xiàn)程序直接崩潰了

class MainActivity : AppCompatActivity() {

    private lateinit var alertDialog: AlertDialog

    private val thread = object : Thread("hello") {
        override fun run() {
            Looper.prepare()
            Handler().post {
                alertDialog =
                    AlertDialog.Builder(this@MainActivity).setMessage(Thread.currentThread().name)
                        .create()
                alertDialog.show()
                alertDialog.hide()
            }
            Looper.loop()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn_test.setOnClickListener {
            alertDialog.show()
        }
        thread.start()
    }

}
E/AndroidRuntime: FATAL EXCEPTION: main
Process: github.leavesc.test, PID: 5243
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6892)
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
    at android.view.View.requestLayout(View.java:19781)
    at android.view.View.setFlags(View.java:11478)
    at android.view.View.setVisibility(View.java:8069)
    at android.app.Dialog.show(Dialog.java:293)

ViewRootImpl 在初始化的時候會將當(dāng)前線程保存到 mThread奠滑,在后續(xù)進行 UI 更新的時候就會調(diào)用checkThread()方法進行線程檢查丹皱,如果發(fā)現(xiàn)存在多線程調(diào)用則直接拋出以上的異常信息

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
            
     final Thread mThread;       
            
     public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        mThread = Thread.currentThread();
        ···
    }       
            
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
            
}

14、為什么 UI 體系要采用單線程模型

其實這很好理解宋税,就是為了提高運行效率和降低實現(xiàn)難度摊崭。如果允許多線程并發(fā)訪問 UI 的話,為了避免競態(tài)杰赛,很多即使只是小范圍的局部刷新操作(例如呢簸,TextView.setText)都勢必需要加上同步鎖,這無疑會加大 UI 刷新操作的“成本”,降低了整個應(yīng)用的運行效率根时。而且會導(dǎo)致 Android 的 UI 體系在實現(xiàn)時就被迫需要對多線程環(huán)境進行“防御”瘦赫,即使開發(fā)者一直是使用同個線程來更新 UI,這就加大了系統(tǒng)的實現(xiàn)難度

所以啸箫,最為簡單高效的方式就是采用單線程模型來訪問 UI

15耸彪、如何跨線程下發(fā)任務(wù)

通常情況下,兩個線程之間的通信是比較麻煩的忘苛,需要做很多線程同步操作蝉娜。而依靠 Looper 的特性,我們就可以用比較簡單的方式來實現(xiàn)跨線程下發(fā)任務(wù)

看以下代碼扎唾,從 TestThread 運行后彈出的線程名可以知道召川, Toast 是在 Thread_1 被彈出來的。如果將 Thread_2 想像成主線程的話胸遇,那么以下代碼就相當(dāng)于從主線程向子線程下發(fā)耗時任務(wù)了荧呐,這個實現(xiàn)思路就相當(dāng)于 Android 提供的 HandlerThread 類

inner class TestThread : Thread("Thread_1") {

    override fun run() {
        Looper.prepare()
        val looper = Looper.myLooper()
        object : Thread("Thread_2") {
            override fun run() {
                val handler = Handler(looper!!)
                handler.post {
                    //輸出結(jié)果是:Thread_1
                    Toast.makeText(
                        this@MainActivity,
                        Thread.currentThread().name,
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        }.start()
        Looper.loop()
    }

}

16、如何判斷當(dāng)前是不是主線程

通過 Looper 來判斷

if (Looper.myLooper() == Looper.getMainLooper()) {
    //是主線程
}

if (Looper.getMainLooper().isCurrentThread){
    //是主線程
}

17纸镊、如何全局捕獲主線程異常

比較臥槽的一個做法就是通過嵌套 Loop 循環(huán)來實現(xiàn)倍阐。向主線程 Loop 發(fā)送 一個 Runnable,在 Runnable 里死循環(huán)執(zhí)行 Loop 循環(huán)逗威,這就會使得主線程消息隊列中的所有任務(wù)都會被交由該 Runnable 來調(diào)用峰搪,只要加上 try catch 后就可以捕獲主線程的任意異常了,做到主線程永不崩潰

Handler(Looper.getMainLooper()).post {
    while (true) {
        try {
            Looper.loop()
        } catch (throwable: Throwable) {
            throwable.printStackTrace()
            Log.e("TAG", throwable.message ?: "")
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凯旭,一起剝皮案震驚了整個濱河市概耻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罐呼,老刑警劉巖鞠柄,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嫉柴,居然都是意外死亡厌杜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門差凹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來期奔,“玉大人,你說我怎么就攤上這事危尿。” “怎么了馁痴?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵谊娇,是天一觀的道長。 經(jīng)常有香客問我,道長济欢,這世上最難降的妖魔是什么赠堵? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮法褥,結(jié)果婚禮上茫叭,老公的妹妹穿的比我還像新娘。我一直安慰自己半等,他們只是感情好揍愁,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杀饵,像睡著了一般莽囤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上切距,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天朽缎,我揣著相機與錄音,去河邊找鬼谜悟。 笑死话肖,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葡幸。 我是一名探鬼主播最筒,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼礼患!你這毒婦竟也來了是钥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤缅叠,失蹤者是張志新(化名)和其女友劉穎悄泥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肤粱,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡弹囚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了领曼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鸥鹉。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡征椒,死狀恐怖铐炫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芳来,我是刑警寧澤单刁,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布灸异,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏肺樟。R本人自食惡果不足惜檐春,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望么伯。 院中可真熱鬧疟暖,春花似錦、人聲如沸田柔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凯楔。三九已至窜骄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摆屯,已是汗流浹背邻遏。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留虐骑,地道東北人准验。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像廷没,于是被迫代替她去往敵國和親糊饱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361

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