Android | 異步消息處理機(jī)制(源碼分析+面試題)

參考文獻(xiàn):

1 概述

?主線程不能執(zhí)行耗時(shí)操作邓馒,因?yàn)闀枞谧泳€程里進(jìn)行耗時(shí)操作勋拟;子線程不能更新UI法梯,用handler發(fā)送一個(gè)更新UI的消息苔货,handler分發(fā)消息,處理消息立哑。

子線程為何不能訪問UI?

  • 源碼角度:當(dāng)訪問UI時(shí)夜惭,ViewRootImpl會調(diào)用checkThread()方法檢查當(dāng)前線程是哪個(gè)線程,如果不是UI線程會拋出異常;
  • 線程安全角度:訪問UI不是線程安全的铛绰;
    • 訪問UI為什么不加鎖:邏輯復(fù)雜诈茧、效率低;

?Handler作用:

  • 線程間通信(例如捂掰,子線程通知主線程更新UI)敢会;
  • 執(zhí)行計(jì)劃任務(wù);

?下面源碼分析基于Android 8.0镊叁;

2 Message 消息

?Message消息,是多線程間通信的實(shí)體走触,是Handler發(fā)送和處理的對象晦譬。Message對象實(shí)現(xiàn)了Parcelable接口,說明Message對象支持序列化/反序列化操作互广。

2.1 屬性

    //msg ID
    public int what;
    //存儲int類型的數(shù)據(jù)域
    public int arg1;
    //存儲int類型的數(shù)據(jù)域
    public int arg2;
    //存儲Object類型數(shù)據(jù)域
    public Object obj;
    //存儲Bundle類型數(shù)據(jù)域
    /*package*/ Bundle data;

    /*package*/ static final int FLAG_IN_USE = 1 << 0;
    //消息標(biāo)識敛腌,當(dāng)消息對象進(jìn)入消息隊(duì)列或回收時(shí)設(shè)置為FLAG_IN_USE,msg.obtain時(shí)設(shè)置為0
    /*package*/ int flags;
    
    //處理消息的時(shí)間
    /*package*/ long when;
    //發(fā)送和處理消息的Handler
    /*package*/ Handler target;
    //post的Runnable
    /*package*/ Runnable callback;

    // 鏈?zhǔn)浇Y(jié)構(gòu)惫皱,指向下一個(gè)Message對象像樊,用于維護(hù)鏈表結(jié)構(gòu)的消息池(消息隊(duì)列)
    /*package*/ Message next;
    
    //信號量,消息池的加鎖對象
    private static final Object sPoolSync = new Object();
    //消息池的表頭旅敷,由它維護(hù)了一個(gè)鏈?zhǔn)较⒊厣鳎?dāng)消息被回收的時(shí)候,會加入到這個(gè)消息池中
    private static Message sPool;
    //消息池大小
    private static int sPoolSize = 0;
    //消息池最大容量50媳谁,消息隊(duì)列的最大容量是50
    private static final int MAX_POOL_SIZE = 50;
  • Message可傳輸int , Object ,Bundle類型的數(shù)據(jù)涂滴;
  • 如果你的message只需要攜帶簡單的int,請優(yōu)先使用Message.arg1Message.arg2來傳遞信息晴音,這比用Bundle更省內(nèi)存柔纵;
  • 擅用message.what來標(biāo)識信息,以便用不同方式處理message;
  • Message維護(hù)了一個(gè)全局的消息池(消息隊(duì)列)锤躁,消息隊(duì)列最大容量是50搁料;消息被回收后,會放入到消息池中系羞,并將flag字段設(shè)置為FLAG_IN_USE;

2.2 靜態(tài)obtain()方法

    public static Message obtain() {
        synchronized (sPoolSync) {//對消息池加鎖
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // flags設(shè)為0
                sPoolSize--;//從鏈表刪除
                return m;
            }
        }
        return new Message();//若消息池為空郭计,直接new
    }

?obtain方法用于獲取一個(gè)消息對象,如果當(dāng)前消息池為空椒振,直接new昭伸,否則從消息池頭部取一個(gè)消息對象進(jìn)行復(fù)用;
obtain方法還有好幾個(gè)重載方法杠人,但最終都會調(diào)用該該無參方法勋乾。

2.3 recycle()方法

//可手動回收消息
public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }
//真正回收Message的方法,looper.loop()在從消息隊(duì)列取出并處理消息后調(diào)用這個(gè)方法
    void recycleUnchecked() {
        flags = FLAG_IN_USE; //修改標(biāo)記嗡善?辑莫?
        //為了無差別(handler發(fā)送的所有消息)復(fù)用消息對象,清空所有域
        what = 0; arg1 = 0; arg2 = 0;
        obj = null; replyTo = null; sendingUid = -1;
                when = 0;target = null;callback = null;data = null;

        synchronized (sPoolSync) {//將使用完的消息回收后放入消息池罩引,頭插法
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

?在Message對象被處理或從消息隊(duì)列移除后各吨,可以手動調(diào)用recycle()方法回收消息對象;當(dāng)然recycleUnchecked()方法才是真正回收消息的方法,looper.loop()在從消息隊(duì)列取出并處理消息后調(diào)用這個(gè)方法進(jìn)行消息回收揭蜒;這個(gè)方法首先會將flag標(biāo)記為FLAG_IN_USE横浑,并把清空所有屬性;并在消息池沒有達(dá)到最大限定值的情況下屉更,把這個(gè)對象插入消息池的表頭徙融。同樣,在操作消息池的時(shí)候需要先對sPoolSync信號量加鎖瑰谜。

回收消息放入消息池

3 MessageQueue消息隊(duì)列

?MessageQueue是一個(gè)常量類欺冀,不允許被繼承;
?消息隊(duì)列用來存放Handler發(fā)送過來的消息萨脑,內(nèi)部通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護(hù)消息列表隐轩,等待Looper的抽取。

3.1 消息出隊(duì)next()

    Message next() {
                //...
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        //死循環(huán)從隊(duì)列取Message渤早,直到返回一個(gè)Message职车,或者M(jìn)essageQueue退出
        for (;;) {
            //...
            synchronized (this) {//消息隊(duì)列加鎖
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //取隊(duì)頭消息,若該消息不為空且者是屏障消息(target為空)鹊杖,則繼續(xù)遍歷悴灵,直到取到一個(gè)異步消息為止
                //屏障消息:target為空時(shí)是屏障消息;用于區(qū)分同步消息和異步消息仅淑;如果設(shè)置了屏障消息称勋,只執(zhí)行異步消息,不執(zhí)行同步消息涯竟,直到移除了屏障;如果沒設(shè)置屏障消息空厌,同步消息和異步消息都執(zhí)行
                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) {//如果消息執(zhí)行時(shí)間未到庐船,繼續(xù)循環(huán),等待時(shí)間到
                        // 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) {
                            //preMsg不空嘲更,說明此時(shí)隊(duì)列頭結(jié)點(diǎn)是一個(gè)target為空的屏障消息筐钟,同時(shí)msg此時(shí)是異步消息。
                            prevMsg.next = msg.next;//直接從鏈表取下該消息
                        } else {//此時(shí)msg是隊(duì)列頭結(jié)點(diǎn)赋朦,直接刪除隊(duì)頭即可
                            mMessages = msg.next;
                        }
                        msg.next = null;//斷開next鏈接
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();//修改標(biāo)記
                        return msg;//取道非空消息退出
                    }
                } else {//隊(duì)列為空篓冲,next方法阻塞,繼續(xù)循環(huán)宠哄,等待新消息到來
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                //若消息隊(duì)列已退出壹将,返回true退出死循環(huán)
                if (mQuitting) {
                    dispose();
                    return null; //返回null后Looper.loop()方法也會結(jié)束循環(huán)
                }
                                //...
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;//當(dāng)隊(duì)列為空時(shí),next()方法會阻塞毛嫉,繼續(xù)循環(huán)诽俯,直到有新消息到達(dá)隊(duì)列
                    continue;
                }
                                //...
        }
    }
  • next()方法用于將隊(duì)列頭部消息出隊(duì)并返回
  • 該方法內(nèi)部有個(gè)死循環(huán)承粤,如果消息隊(duì)列中沒消息暴区,next方法會阻塞闯团,繼續(xù)循環(huán),直到取道新消息仙粱;如果消息隊(duì)列中有消息房交,先判斷執(zhí)行時(shí)間是否到了,如果時(shí)間沒到則等待伐割,繼續(xù)循環(huán)候味;如果時(shí)間到了就將消息出隊(duì)返回
  • 在循環(huán)過程中會對消息隊(duì)列加鎖口猜,所以該方法是線程安全的负溪;

3.2 消息入隊(duì)enqueueMessage()

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {//入隊(duì)的消息的target,必須不為空济炎,否則會拋異常
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {//消息在回收之前川抡,不可以被重復(fù)使用
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        synchronized (this) {
            if (mQuitting) {//如果消息隊(duì)列在退出狀態(tài) ,則直接回收消息须尚,返回false
                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;
            }
            //把消息標(biāo)記為在使用狀態(tài)崖堤,設(shè)置when
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;//此時(shí)p是鏈表頭部
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //如果隊(duì)列為空或者when等于0,或者when小于隊(duì)頭Message的when耐床,則直接把消息插入隊(duì)頭
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;//prev是p的前驅(qū)節(jié)點(diǎn)密幔,依次遍歷
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;//當(dāng)p已經(jīng)到隊(duì)尾或者找到一個(gè)節(jié)點(diǎn)msg.when < p.when時(shí)退出循環(huán)
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //鏈表插入操作,把msg插入到p節(jié)點(diǎn)前邊撩轰,并把p的前驅(qū)節(jié)點(diǎn)的next改為msg
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
            //...
        }
        return true;//插入成功胯甩,返回true
    }
  • 該方法用于將消息入隊(duì),其實(shí)就是單鏈表的插入操作;
  • 該方法對鏈表隊(duì)列操作時(shí)堪嫂,依然是進(jìn)行了加鎖同步偎箫,所以是線程安全的;
  • 隊(duì)列是一個(gè)(消息執(zhí)行時(shí)間)when升序鏈表皆串,所以插入也必須找到合適的節(jié)點(diǎn)進(jìn)行插入淹办;如果待插入Message不設(shè)置when或when=0,直接插入隊(duì)列頭部恶复;否則遍歷隊(duì)列結(jié)點(diǎn)怜森,直到找到第一個(gè)大于when的結(jié)點(diǎn),插入到該結(jié)點(diǎn)的前面谤牡;
消息入隊(duì)

4 Looper消息泵

?通過Looper.loop()不斷地從MessageQueue中抽取Message副硅,將消息分發(fā)給目標(biāo)處理者(Handler);

4.1 主要屬性和構(gòu)造器

    //線程本地變量拓哟,每個(gè)線程有一個(gè)獨(dú)立的Looper對象想许,不存在線程安全問題
    //如果不調(diào)用prepare()方法,sThreadLocal.get()返回null
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // 主線程的Looper,由Looper.class維護(hù)
    final MessageQueue mQueue;//looper的MessageQueue
    final Thread mThread;//(創(chuàng)建)Looper線程
        //私有構(gòu)造器流纹,不允許外部調(diào)用
        private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
        }
  • Looper的構(gòu)造器是私有的糜烹,不能在Looper類外部new Looper,所以在Looper類外部必須調(diào)用prepare()方法來創(chuàng)建一個(gè)Looper對象漱凝;
  • Looper內(nèi)部有一個(gè)MessageQueue屬性疮蹦;

4.2 創(chuàng)建Looper

?Looper.prepare()創(chuàng)建Looper,而不是new茸炒;

//公有方法愕乎,開發(fā)者只能調(diào)用這個(gè)方法為當(dāng)前線程創(chuàng)建Looper,允許退出
public static void prepare() {
    prepare(true);
}
//私有方法壁公,開發(fā)者無法調(diào)用感论,同一個(gè)線程只允許調(diào)用一次prepare(),否則會拋出異常
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));//將Looper對象設(shè)置為線程本地變量
}
//主線程的Looper初始化紊册,雖然是公有方法比肄,我們無法調(diào)用,
//因?yàn)橄到y(tǒng)啟動的時(shí)候已經(jīng)調(diào)用過了囊陡,如果再次調(diào)用芳绩,會拋異常
public static void prepareMainLooper() {
    prepare(false);
    synchronized(Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //把得到的主線程Looper賦值給sMainLooper 
        sMainLooper = myLooper();
    }
}
//獲取當(dāng)前線程的Looper對象
public static@Nullable Looper myLooper() {
    return sThreadLocal.get();//獲取線程本地變量
}
  • 調(diào)用Looper.prepare()為當(dāng)前線程創(chuàng)建Looper對象,并將Looper對象設(shè)置為線程本地變量撞反;
  • 調(diào)用Looper.myLooper()方法來獲取當(dāng)前線程的Looper對象;
  • 主線程的Looper允許MessageQueue退出妥色,而其他線程不允許;
  • 一個(gè)線程只能調(diào)用一次Looper.prepare()方法遏片,否則會拋出異常嘹害,所以在prepare()創(chuàng)建Looper對象之前,應(yīng)該先調(diào)用Looper.myLooper()方法判斷是否為空吮便;同時(shí)也說明了一個(gè)線程只有一個(gè)Looper對象吼拥;
  • 主線程的Looper在ActivityThread中的main()方法中創(chuàng)建的,所以主線程不需要手動創(chuàng)建Looper线衫;
//主線程中不需要自己創(chuàng)建Looper
public static void main(String[] args) {
        //...
        Looper.prepareMainLooper();//為主線程創(chuàng)建Looper,該方法內(nèi)部又調(diào)用 Looper.prepare()
        //...
        Looper.loop();//開啟消息輪詢
        //...
    }
  • 另外可以在任何地方調(diào)用Looper.getMainLooper();獲取主線程的Looper惑折;

?但是子線程就不一樣了授账,子線程在創(chuàng)建Handler對象前必須手動調(diào)用Looper.prepare()方法創(chuàng)建Looper對象;

//子線程中創(chuàng)建Looper的標(biāo)準(zhǔn)寫法
new Thread(new Runnable() {
    @Override
    public void run() {
        if(Looper.myLooper()==null){//保證一個(gè)線程只有一個(gè)Looper
            Looper.prepare();//創(chuàng)建Looper
        }
        Handler handler=new Handler();
        Looper.loop();//開啟消息輪詢
    }
}).start();

4.3 開啟消息輪詢loop()

 //代碼省去打印等其他無關(guān)邏輯
public static void loop() {
    //獲取當(dāng)前線程的sThreadLocal變量惨驶,即Looper對象
    final Looper me = myLooper();
    //如果當(dāng)前線程沒有調(diào)用過prepare()方法白热,則me為null,拋出異常
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //從me里獲得本線程的MessageQueue對象
    final MessageQueue queue = me.mQueue;
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //開啟消息輪詢
    for (;;) {
        //從消息隊(duì)列取消息
        Message msg = queue.next();  // 當(dāng)消息隊(duì)列為空且未退出時(shí)粗卜,next方法會阻塞
        if (msg == null) {
            //next返回null表明消息隊(duì)列已退出
            return;//結(jié)束輪詢屋确,loop方法唯一出口
        }
        try {
            //target是Message的Handler,調(diào)用它的dispatchMessage()方法來分發(fā)消息
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked();//當(dāng)消息被處理完后,回收當(dāng)前消息
    }
}
  • 通過調(diào)用Looper.loop()方法開啟消息輪詢攻臀;該方法會調(diào)用queue.next()方法從隊(duì)列中取頭部消息焕数;
  • 該方法會循環(huán)調(diào)用msg.target.dispatchMessage(msg);來進(jìn)行消息分發(fā),分發(fā)給消息的target刨啸,也就是消息對應(yīng)的的Handler對象堡赔;
  • 當(dāng)消息被處理完后,會調(diào)用msg.recycleUnchecked();回收消息设联,當(dāng)前消息放入消息池善已,以便以后復(fù)用;
  • handler是在它關(guān)聯(lián)的looper線程(創(chuàng)建Looper對象的線程)中處理消息的离例;

5 Handler 消息處理器

5.1 主要屬性和構(gòu)造器

5.1.1 主要屬性

//是否發(fā)現(xiàn)潛在的內(nèi)存泄漏换团,默認(rèn)為false
private static final boolean FIND_POTENTIAL_LEAKS = false;
private static final String TAG = "Handler";
//靜態(tài)全局變量,主線程的Handler
private static Handler MAIN_THREAD_HANDLER = null;
//綁定的Looper對象
final Looper mLooper;
//綁定的MessageQueue消息隊(duì)列宫蛆,通過looper獲取
final MessageQueue mQueue;
//回調(diào)接口
final Callback mCallback;
//是否是異步的艘包,如果是異步的,在發(fā)送消息的時(shí)候洒扎,
//會調(diào)用Message.setAsynchronous(true)把消息設(shè)為異步消息
final boolean mAsynchronous;
  • 由此可見一個(gè)Handler持有一個(gè)Looper類型的屬性辑甜;即一個(gè)Handler對應(yīng)一個(gè)唯一的Looper;而對應(yīng)的MessageQueue消息隊(duì)列袍冷,通過Looper屬性獲攘状住;

5.1.2 構(gòu)造器

public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            //如果為true胡诗,如果Handler實(shí)現(xiàn)類是匿名類或內(nèi)部類或非static類邓线,會給出警告,告知開發(fā)者存在內(nèi)存泄漏的風(fēng)險(xiǎn)
            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());
            }
        }
        //獲取當(dāng)前線程的線程本地變量煌恢,即Looper對象骇陈;
        mLooper = Looper.myLooper();
        if (mLooper == null) {//創(chuàng)建Handler對象前要調(diào)用Looper.prepare
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //mQueue直接拿Looper里的MessageQueue類型的引用對象
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
   //該構(gòu)造器可為當(dāng)前Handler指定Looper對象,所以Handler對象和Looper對象不一定是在同一線程創(chuàng)建的
   public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    public Handler() {this(null, false);}
        
    public Handler(Callback callback) {this(callback, false);}
    
    public Handler(Looper looper) {this(looper, null, false);}
  • Handler對應(yīng)的MessageQueue對象來自其關(guān)聯(lián)的Looper對象瑰抵;

  • 其他構(gòu)造方法都會調(diào)用前面?zhèn)z構(gòu)造器中一個(gè)你雌;早常用的構(gòu)造器是Handler()Handler(Callback);

  • Handler(Looper looper)用于為當(dāng)前Handler指定關(guān)聯(lián)的Looper對象;

  • Handler關(guān)聯(lián)的Looper對象既可以來自當(dāng)前線程(創(chuàng)建Handler實(shí)例的線程)的本地變量二汛,也可以在構(gòu)造器里指定婿崭;前面一種情況,Handler和Looper是在同一線程里實(shí)例化的肴颊,后面一種情況不一定氓栈;

5.1.3obtainMessage

public final Message obtainMessage() {
    return Message.obtain(this);
}

?使用該方法獲取handler當(dāng)前處理的Message;Handler中有一系列obtanMessage()重載方法婿着,最后調(diào)用的還是該方法授瘦;需要注意的是醋界,在Message中,obtain()方法是靜態(tài)方法提完,在Handler中形纺,是非靜態(tài)的,需要通過具體的Handler實(shí)例對象來獲得氯葬,但是禁止子類進(jìn)行覆寫挡篓;

5.2 發(fā)送消息

?在Handler中,可以發(fā)送一個(gè)Runnable對象帚称,也可以發(fā)送一個(gè)Message對象官研;通過sendMessage(Message)方式發(fā)送一個(gè)Message對象;通過post(Runnable)方式發(fā)送一個(gè)Runnable對象闯睹,這個(gè)Runnable對象最終也會被包裝成一個(gè)Message對象發(fā)送戏羽;

5.2.1 post方式

//立即post一個(gè)Runnable對象到MessageQueue中,此時(shí)Runnable對象被包裝成Message后入隊(duì)(when == 當(dāng)前系統(tǒng)時(shí)間楼吃,可能是隊(duì)頭始花,也可能不是隊(duì)頭,隊(duì)列中已經(jīng)有when值小于當(dāng)前時(shí)間的Message)
public final boolean post(Runnable r) {
    return sendMessageDelayed(getPostMessage(r), 0);
}
//Runnable包裝成Message后加入到MessageQueue中孩锡,但此時(shí)
//Message.when=uptimeMillis酷宵,uptimeMills是消息的執(zhí)行時(shí)間,
public final boolean postAtTime(Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
//同上躬窜,只是又傳入了token對象浇垦,存儲在Message.obj中
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
//Runnable包裝成Message后加入到MessageQueue中,
//但是Message.when = now + delayMillis荣挨,
//表示延遲delayMills后執(zhí)行
public final boolean postDelayed(Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
//Runnable包裝成Message后加入到MessageQueue中男韧,此時(shí)when=0,所以一定是在MessageQueue的隊(duì)頭
public final boolean postAtFrontOfQueue(Runnable r) {
    return sendMessageAtFrontOfQueue(getPostMessage(r));
}
//把Runnable對象包裝成Message對象默垄,可見只是把Runnable對象賦值給了Message的callback域
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
//上述方法的重載方法此虑,把token賦值給了Message的obj域,可以用這個(gè)方法進(jìn)行傳Object數(shù)據(jù)
private static Message getPostMessage(Runnable r, Object token) {
    Message m = Message.obtain();
    m.obj = token;
    m.callback = r;
    return m;
}

5.2.2 sendMessage方式

public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}
//延遲發(fā)送口锭,把當(dāng)前時(shí)間加上延遲時(shí)間后調(diào)用了sendMessageAtTime()方法
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//發(fā)送消息的方法朦前,對queue判空后,調(diào)用enqueueMessage進(jìn)行實(shí)際入隊(duì)
public boolean sendMessageAtTime(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);
}
//實(shí)際對消息入隊(duì)的方法鹃操,在該方法中况既,會把Message的target域進(jìn)行賦值,
//如果mAsynchronous是true组民,則會調(diào)用setter方法把消息設(shè)置為異步消息,
//調(diào)用的入隊(duì)方法其實(shí)是調(diào)用的MessageQueue的enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
//...
//省略其他方法悲靴,基本上跟post系列方法是一一對應(yīng)的
  • 幾個(gè)時(shí)間關(guān)系:when = now + delay,when 表示分發(fā)消息(dispatchMessage)的時(shí)間臭胜,now表示當(dāng)前(相對系統(tǒng)啟動)時(shí)間SystemClock.uptimeMillis()莫其,delay表示延遲時(shí)間delayMillis;

?post(runnable)方式和sendMessage(msg)方式發(fā)送消息的聯(lián)系和區(qū)別:

  • 聯(lián)系:調(diào)用鏈都是handler.sendMessageAtTime()->messageQueue.enqueueMessage()將Message發(fā)送到消息隊(duì)列;
  • 區(qū)別:
    • 消息內(nèi)容不同:sendMessage發(fā)送的消息側(cè)重于傳數(shù)據(jù)耸三,而handleCallback側(cè)重于傳任務(wù)(Runnable)乱陡;
    • 處理消息的方式不同:一般情況,sendMessage發(fā)送的消息最終會調(diào)用handler或callback的handleMessage方法來處理仪壮;而post(runnable) 發(fā)送的消息最終會調(diào)用handler.handleCallback方法來處理憨颠;

5.3 處理消息

        //消息分發(fā)
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//post的Runnable參數(shù)
            handleCallback(msg);
        } else {
            if (mCallback != null) {//Handler(Callback)構(gòu)造器,Handler無需派生子類
                if (mCallback.handleMessage(msg)) {//一般為true积锅,若為false爽彤,還要執(zhí)行handleMessage
                    return;
                }
            }
            handleMessage(msg);//優(yōu)先級最低
        }
    }
    
    private static void handleCallback(Message message) {//處理消息方式1,優(yōu)先級最高
        message.callback.run();//執(zhí)行post的Runnable參數(shù)地run回調(diào)方法缚陷,因此Runnable run里可以有一些更新UI的操作
    }
    
    public interface Callback {//優(yōu)先級次之
        public boolean handleMessage(Message msg);//處理消息方式2适篙,在調(diào)用Handler(Callback)構(gòu)造器實(shí)例化Handler時(shí)實(shí)現(xiàn)該方法
    }
    
   //Handler子類必須實(shí)現(xiàn)這個(gè)空方法來接收消息
    public void handleMessage(Message msg) {//處理消息方式3,優(yōu)先級最低
    }
  • 有兩類發(fā)送消息的方式: sendMessage(msg)方式和post(Runnable r)方式箫爷;
  • 處理消息方式有三種:handleCallback方式嚷节、callback.handleMessage方式、handler.handleMessage方式虎锚;并且優(yōu)先級遞減硫痰;

6 四要素之間的關(guān)系

6.1 四要素之間的關(guān)系

四要素ER圖
  • 一個(gè)線程中可創(chuàng)建多個(gè)Handler對象;但是一個(gè)線程中只能創(chuàng)建一個(gè)Looper對象(因?yàn)橐粋€(gè)線程中只能調(diào)用一次Looper.preapre()窜护,否則會報(bào)異常)效斑;
  • 一個(gè)Looper對象可以對應(yīng)多個(gè)線程,比如主線程的mainLooper柄慰,供主線程和所屬子線程(Looper.getMainLooper())共同使用鳍悠;
  • Looper類中有一個(gè)final MessageQueue mQueue屬性;
  • Handler類中有一個(gè)屬性final Looper mLooper屬性,Handler關(guān)聯(lián)的消息隊(duì)列通過Looper獲茸Α藏研;
  • 一個(gè)消息隊(duì)列中有多個(gè)Message對象,不同消息的target可以不同概行,所以消息隊(duì)列中的消息可以來自不同的Handler對象;
  • Message類有一個(gè) Handler target屬性蠢挡,這是消息對象關(guān)聯(lián)的Handler對象;

6.2 異步消息處理機(jī)制的原理(圖非常重要)

?以下面應(yīng)用場景為例:在主線程里實(shí)例化Looper和Handler凳忙;在子線程(工作線程)處理耗時(shí)任務(wù)业踏,由于要將任務(wù)執(zhí)行結(jié)果在UI上展示,需要更新UI涧卵;在子線程中創(chuàng)建一個(gè)更新UI的Message對象勤家,并使用Handler對象的引用發(fā)送該消息;最后在主線程里處理消息柳恐,更新UI伐脖;下面是sample代碼:

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.textView) TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.button)
    public void getData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //執(zhí)行耗時(shí)操作...
                Message msg=new Message();msg.what=7;msg.obj= "網(wǎng)絡(luò)數(shù)據(jù)";
                //處理消息時(shí)回調(diào)handler.handleMessage
//                handler1.sendMessage(msg);
                //處理消息時(shí)回調(diào)callback.handleMessage
//                handler2.sendMessage(msg);
                //此處使用handler1,2,3 post消息都可以热幔,但是不會執(zhí)行handleMessage
                handler3.post(new Runnable() {
                    @Override
                    public void run() {
                        //不會開啟新線程執(zhí)行,handleCallback執(zhí)行run里的代碼讼庇,所以不會報(bào)錯(cuò)
                        textView.setText("耗時(shí)操作處理結(jié)果");
                    }
                });
            }
        }).start();
    }

    Handler handler3=new Handler();

    //匿名內(nèi)部類向上轉(zhuǎn)型Handler()方式绎巨,派生子類
    Handler handler1=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 7:
                    textView.setText(msg.obj.toString());//更新UI
                    break;
            }
        }
    };
    
    //Handler(Callback)方式,不派生子類
    Handler handler2=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            textView.setText(msg.obj.toString());//更新UI
            return true;//修改為true
        }
    });
}
異步消息處理機(jī)制原理圖

7 常見面試問題

(1) 為什么創(chuàng)建 Message 對象推薦使用 Message.obtain()獲取而不是new方式蠕啄?

?Handler 機(jī)制在 Android 系統(tǒng)中使用太頻繁场勤,為了提神效率,為Message設(shè)置了一個(gè)靜態(tài)的消息池歼跟,當(dāng)消息被處理完或移除后和媳,會放入到消息池;下次需要使用Message時(shí)從消息池中取出消息進(jìn)行復(fù)用嘹承;

(2) 簡述MessageQueue 如何入隊(duì)和出隊(duì)窗价?

  • 消息入隊(duì):調(diào)用enquueMessage(msg,when);如果消息沒設(shè)置when或者when是0叹卷,直接將消息放到隊(duì)列頭部撼港;否則遍歷隊(duì)列鏈表,找到第一個(gè)大于當(dāng)前消息when的消息結(jié)點(diǎn)骤竹,插入到該節(jié)點(diǎn)前面帝牡;最后會形成一個(gè)按when升序的單鏈表
  • 消息出隊(duì):調(diào)用next()方法蒙揣,直接取出隊(duì)列頭部消息并返回靶溜;

(3) 發(fā)送消息兩種主要方式 sendMessage(msg)方式和post(Runnable r)方式的區(qū)別?

?post(runnable)方式和sendMessage(msg)方式發(fā)送消息的聯(lián)系和區(qū)別:

  • 聯(lián)系:調(diào)用鏈都是handler.sendMessageAtTime()->messageQueue.enqueueMessage()將Message發(fā)送到消息隊(duì)列懒震;
  • 區(qū)別:
    • 消息內(nèi)容不同:sendMessage發(fā)送的消息側(cè)重于傳數(shù)據(jù)罩息,而handleCallback側(cè)重于傳任務(wù)(Runnable);
    • 處理消息的方式不同:一般情況个扰,sendMessage發(fā)送的消息最終會調(diào)用handler或callback的handleMessage方法來處理瓷炮;而post(runnable) 發(fā)送的消息最終會調(diào)用handler.handleCallback方法來處理;

(4) 處理消息有哪幾種方式递宅,他們之間優(yōu)先級娘香?

  • 處理消息方式有三種:handler.handleCallback方式、callback.handleMessage方式办龄、handler.handleMessage方式烘绽;并且優(yōu)先級遞減;

(5) Handler發(fā)送俐填、處理消息有哪幾種方式安接?

  • 結(jié)合有以下三種常見的 Handler發(fā)送、處理消息 的方式:
        //方式1:send+派生方式
        //發(fā)送消息sendMessage英融;構(gòu)造器Handler();處理消息handler.handleMessage;
        //注意這種方式由于存在Handler子類內(nèi)部類赫段,可能存在內(nèi)存泄漏的情況呀打,需要處理這種情況
        handler1.sendMessage(msg);
        //匿名內(nèi)部類向上轉(zhuǎn)型Handler()方式,需要派生子類
    Handler handler1=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                    textView.setText(msg.obj.toString());//更新UI
                    break;
            }
        }
    };
    
    //方式2:send+Callback方式
    //發(fā)送消息sendMessage糯笙;構(gòu)造器Handler(Callback);處理消息callback.handleMessage;
    handler2.sendMessage(msg);
    //Handler(Callback)方式,不派生子類
    Handler handler2=new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            textView.setText(msg.obj.toString());//更新UI
            return true;//修改為true
        }
    });
    
    //方式3:post(Runnable r)方式
    //發(fā)送消息post(Runnable r)撩银;構(gòu)造器Handler();處理消息handleCallback;
    //此處使用handler1,2,3 post消息都可以给涕,但是不會執(zhí)行handleMessage
    handler3.post(new Runnable() {
        @Override
        public void run() {
      //不會開啟新線程,handleCallback執(zhí)行run里的代碼额获,所以不會報(bào)錯(cuò)
                textView.setText("耗時(shí)操作處理結(jié)果");
        }
        });
    Handler handler3=new Handler();

(6) 異步消息處理機(jī)制的原理(Handler發(fā)送消息够庙、處理消息的流程)?(高頻題也是本章核心和概要抄邀,很重要)

異步消息處理機(jī)制原理圖
  • Handler消息處理器 作用:發(fā)送消息(任務(wù))耘眨,處理消息;
  • Looper消息泵 作用:調(diào)用Looper.loop()方法進(jìn)行消息輪詢境肾;
  • MessageQueue消息隊(duì)列 作用:存放消息的場所剔难,是一個(gè)按when值遞增的單鏈表;queue.enqueue(msg,when)用于消息入隊(duì)奥喻;queue.next()方法用于消息出隊(duì)偶宫,取隊(duì)首消息;

(7) post(Runnable r)方式是否會開啟新線程环鲤?

??這種方式不會開啟新線程纯趋;

  • Runnable對象會包裝成Message對象,r作為Message對象的callback屬性冷离;
  • 然后調(diào)用handler.sendMessageDelay()->handler.sendMessageAtTime()->messageQueue.enqueueMessage()將Message對象發(fā)送到消息隊(duì)列吵冒;
  • Looper在開啟消息輪詢后,到一定時(shí)間會從消息隊(duì)列中取出該消息對象西剥,交給對應(yīng)的target(Handler對象)進(jìn)行消息分發(fā)痹栖;
  • 然后調(diào)用handler.handleCallback()方法處理消息,在這個(gè)方法里Runnable任務(wù)會得到執(zhí)行蔫耽;

(8) 區(qū)分兩個(gè)callback结耀?區(qū)分兩個(gè)handleMessage

?兩個(gè)callback:

  • MessageRunnable callback屬性:使用post(Runnable r)里的Runnable對象初始化匙铡;
  • Handlerfinal Callback mCallback屬性:在Handler(Callback callabck)構(gòu)造器進(jìn)行初始化图甜;

?兩個(gè)handleMessage方法,對應(yīng)處理消息的兩種方式:

  • handler.handleMessage:send+派生方式調(diào)用該方法來處理消息鳖眼;
  • callback.handleMessage:send+Callback方式調(diào)用該方法來處理消息黑毅;

(9) 為什么Handler會造成內(nèi)存泄漏?如何解決钦讳?

(i) 內(nèi)存泄漏的原因矿瘦?
  • 一般造成內(nèi)存泄漏的原因:長生命周期對象引用短生命周期對象枕面;
  • Handler造成Activity內(nèi)存泄漏的原因:Handler生命周期比Activity長,非靜態(tài)內(nèi)部類默認(rèn)持有外部類的引用,導(dǎo)致Activity對象無法回收(Activity對象先回收時(shí)缚去,Handler對象可能還在處理消息潮秘,此時(shí)Handler對象還持有Activity對象的引用,導(dǎo)致Activity對象無法回收)易结。
(i) 解決辦法枕荞?

?把Handler子類定義為靜態(tài)(static)內(nèi)部類;同時(shí)用WeakReference包裝外部類的對象activity搞动;

  • 為什么Handler子類要定義為靜態(tài)(static)內(nèi)部類躏精? ?
    ?因?yàn)殪o態(tài)內(nèi)部類不持有外部類的引用,所以使用靜態(tài)的Handler不會導(dǎo)致Activity內(nèi)存泄露鹦肿。
  • 為什么Handler子類定義為靜態(tài)(static)內(nèi)部類同時(shí)矗烛,還要用WeakReference包裝外部類的對象activity ?
    ?因?yàn)槲覀冃枰L問外部類的非靜態(tài)成員箩溃,可以通過強(qiáng)引用"activity. "訪問瞭吃,如果直接使用強(qiáng)引用訪問,顯然會導(dǎo)致activity泄露碾篡。
    MyHandler mHandler=new MyHandler(this);
    
    private static class  MyHandler extends Handler{
        //static和WeakReference是為了解決內(nèi)存泄漏
        private WeakReference<MainActivity> weakReference;
        public MyHandler(MainActivity activity) {
           weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 1:
                MainActivity activity=weakReference.get();
                if(activity != null){//判空是為了避免空引用異常
                     activity.textView.setText(msg.obj.toString());
                }
                break;
            }
        }
    }

(10) 為何Looper.loop()死循環(huán)不會造成應(yīng)用卡死虱而?

?Looper.loop()不會造成應(yīng)用卡死,因?yàn)槔锩媸褂昧?strong>Linux 的epoll機(jī)制开泽;

(11) 創(chuàng)建Handler前要注意什么牡拇?

?創(chuàng)建Handler對象前必須調(diào)用Looper.prepare()方法創(chuàng)建一個(gè)Looper對象馋嗜;值得一體的是够吩,子線程中必須手動調(diào)用Looper.prepare()方法玻淑,而主線程中可以不調(diào)用卧秘;因?yàn)橹骶€程ActivityThread的main方法中默認(rèn)調(diào)用了Looper.prepareMainLooper()方法暮蹂,這個(gè)方法會調(diào)用Looper.prepare()方法創(chuàng)建一個(gè)主線程Looper對象夭委;

(12) 異步消息處理機(jī)制是如何保證消息處理器的唯一性(即某條消息的發(fā)送者和處理者是同一Handler對象)杆麸?

?在Handler的enqueueMessage方法中會把自引用賦值給被發(fā)送的Message的target屬性旷赖;而在Looper的loop方法中會調(diào)用msg.target.dispatchMessage(msg)來分發(fā)辅髓、處理消息泣崩;

        //Handler.java
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
                //...
    }
    //Looper.java
    public static void loop() {
            //...
            msg.target.dispatchMessage(msg);
            //...
    } 

(13) 子線程中是否可以創(chuàng)建Handler對象?

?子線程中可以創(chuàng)建Handler對象洛口,但是子線程在創(chuàng)建Handler對象前必須手動調(diào)用Looper.prepare()方法創(chuàng)建Looper對象矫付;

new Thread(new Runnable() {
    @Override
    public void run() {
        if(Looper.myLooper()==null){//保證一個(gè)線程只有一個(gè)Looper
            Looper.prepare();//創(chuàng)建Looper
        }
        Handler handler1=new Handler();
        Looper.loop();//開啟消息輪詢
        
        //主線程Looepr對象早已創(chuàng)建,并早已開啟消息輪詢
        Handler handler2=new Handler(Looper.prepareMainLooper());
    }
}).start();

ps:如果在主線程中使用子線程中創(chuàng)建的Handler對象的引用發(fā)送消息第焰,最后消息是在子線程中處理的买优;這樣就實(shí)現(xiàn)了主線程向子線程發(fā)送消息,而在本文6.2節(jié)中的sample代碼中實(shí)現(xiàn)了子線程向主線程發(fā)送消息;所以兩個(gè)線程可以通過Handler進(jìn)行雙向通信杀赢;

(14) Handler 與 Looper 是如何關(guān)聯(lián)的?

  • 對于有Looper參數(shù)的構(gòu)造器Handler(looper):直接通過構(gòu)造器參數(shù)設(shè)置關(guān)聯(lián)的Looepr對象烘跺;
  • 對于無Looper參數(shù)的Handler構(gòu)造器:無論是Handler()還是Handler(callback)都會調(diào)用下面的構(gòu)造器,在這個(gè)構(gòu)造其中會為當(dāng)前Handler關(guān)聯(lián)當(dāng)前線程的Looper對象脂崔;
    public Handler(Callback callback, boolean async) {
                //...
        mLooper = Looper.myLooper();//獲取當(dāng)前線程的Looepr對象
                //...
    }
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//獲取當(dāng)前線程的本地變量
    }

(15) Thread 與 Looper 是如何關(guān)聯(lián)的?

?Looper 與 Thread 之間是通過 ThreadLocal 關(guān)聯(lián)的滤淳,這個(gè)可以看 Looper.prepare(quitAllowed)方法:

// Looper.java:93
private static void prepare(boolean quitAllowed) {
        //...
    sThreadLocal.set(new Looper(quitAllowed));//設(shè)置當(dāng)前線程本地變量
}

?Looper 類有一個(gè) ThreadLocal 類型的 sThreadLocal靜態(tài)屬性,Looper通過它的 get 和 set 方法來賦值和取值砌左;
?由于 ThreadLocal是與當(dāng)前線程是綁定的娇钱,所以我們只要把 Looper 與 ThreadLocal 綁定了,那 Looper 和 Thread 也就關(guān)聯(lián)上了绊困;

(16) 如何在子線程中獲取當(dāng)前線程的 Looper?

Looper.myLooper();//獲取當(dāng)前線程的Looepr對象

?內(nèi)部原理就是sThreadLocal.get()

// Looper.java:203
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

(16) 如何在任意線程獲取主線程的 Looper?

Looper.getMainLooper();//獲取主線程的Looper對象

?這個(gè)在我們開發(fā) API時(shí)特別有用适刀,畢竟你不知道開發(fā)者在使用你的API時(shí)會在哪個(gè)線程初始化Looper秤朗,所以我們在創(chuàng)建 Handler 時(shí)每次都通過指定主線程的 Looper 的方式保證API正常運(yùn)行。所以一般使用主線程Looper來進(jìn)行異步消息處理笔喉;

(17) 如何判斷當(dāng)前線程是不是主線程取视?

?

//方式1
Looper.myLooper() == Looper.getMainLooper();
//方式2
Looper.getMainLooper().getThread() == Thread.currentThread();
//方式3:方式2簡化版
Looper.getMainLooper().isCurrentThread();

(18) Looper.loop() 方法會退出嗎?

?不會自動退出常挚,但是我們可以手動調(diào)用 looper.quit()looper.quitSafely()方法退出消息輪詢作谭。
?這兩個(gè)方法都會調(diào)用MessageQueue#quit(boolean) 方法MessageQueue#mQuitting屬性置為true,標(biāo)記消息隊(duì)列已退出奄毡;消息隊(duì)列退出后折欠,MessageQueue#next() 方法發(fā)現(xiàn)已經(jīng)調(diào)用過 MessageQueue#quit(boolean) 時(shí)會 return null ;然后Looper.loop() 方法退出消息輪詢吼过;

?如果looper.quit()锐秦,looper.quitSafely(),MessageQueue#quit(boolean)都不手動調(diào)用并且消息隊(duì)列為空,消息隊(duì)列不會退出盗忱;next()方法會一直死循環(huán)(有的說法稱為next方法阻塞)酱床,loop()方法會在Message msg = queue.next();處阻塞等待新消息到達(dá)消息隊(duì)列,繼續(xù)消息輪詢趟佃。所以建議當(dāng)所有Message都被處理完之后手動調(diào)用looper.quit()looper.quitSafely()方法退出消息輪詢扇谣,避免loop()方法一直阻塞等待

        //Looper.java#322
    public void quit() {//Looper退出
        mQueue.quit(false);
    }
    //Looper.java#338
    public void quitSafely() {
        mQueue.quit(true);
    }
    //Looper.java#137
    public static void loop() {//開啟消息輪詢
            //...
        for (;;) {
            Message msg = queue.next(); // 當(dāng)消息隊(duì)列為空且未退出時(shí)闲昭,next方法會阻塞
            if (msg == null) {
                //next返回null表明消息隊(duì)列已退出
                return;//結(jié)束輪詢罐寨,loop方法唯一出口
            }
                        //...
            msg.target.dispatchMessage(msg);
                //...
        }
    }
    
    //MessageQueue.java#416
    void quit(boolean safe) {
            //...
        mQuitting = true;
        //...
    }
    //MessageQueue.java#310
    Message next() {
        //...
        for (;;) {
             synchronized (this) {
                                //...
                Message msg = mMessages;
                                //...
                if (msg != null) {
                    if (now < msg.when) {
                        //...
                    }else{
                        //...
                        return msg;//取到非空消息退出
                    }
                }else{
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                //若消息隊(duì)列已退出,返回true退出死循環(huán)
                if (mQuitting) {
                    dispose();
                    return null;
                }
        }

(19) MessageQueue#next()方法在消息隊(duì)列為空時(shí)會阻塞汤纸,如何恢復(fù)衩茸?

?使用Handler的sendMessage、post 等一系列方法發(fā)送消息,這些發(fā)送消息的方法會調(diào)用MessageQueue#enqueueMessage將新消息入隊(duì)楞慈,從而使得next()方法不再阻塞幔烛;

(20) IdleHandler作用和使用場景?

?把頁面啟動時(shí)的復(fù)雜邏輯交給IdleHandler去處理囊蓝,這樣可以讓主線程Handler先處理完相關(guān)UI邏輯后再去處理復(fù)雜邏輯饿悬,可以減少頁面啟動白屏?xí)r間,從而優(yōu)化頁面啟動聚霜;

(21) 子線程為何不能訪問UI?

  • 源碼角度:當(dāng)訪問UI時(shí)狡恬,ViewRootImpl會調(diào)用checkThread()方法檢查當(dāng)前線程是哪個(gè)線程,如果不是UI線程會拋出異常;
  • 線程安全角度:訪問UI不是線程安全的蝎宇;
    • 訪問UI為什么不加鎖:邏輯復(fù)雜弟劲、效率低;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姥芥,一起剝皮案震驚了整個(gè)濱河市兔乞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凉唐,老刑警劉巖庸追,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異台囱,居然都是意外死亡淡溯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門簿训,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咱娶,“玉大人,你說我怎么就攤上這事煎楣〔蜃埽” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵择懂,是天一觀的道長喻喳。 經(jīng)常有香客問我,道長困曙,這世上最難降的妖魔是什么表伦? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮慷丽,結(jié)果婚禮上蹦哼,老公的妹妹穿的比我還像新娘。我一直安慰自己要糊,他們只是感情好纲熏,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般局劲。 火紅的嫁衣襯著肌膚如雪勺拣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天鱼填,我揣著相機(jī)與錄音药有,去河邊找鬼。 笑死苹丸,一個(gè)胖子當(dāng)著我的面吹牛愤惰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赘理,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼宦言,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了商模?” 一聲冷哼從身側(cè)響起蜡励,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阻桅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兼都,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫂沉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扮碧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片趟章。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖慎王,靈堂內(nèi)的尸體忽然破棺而出蚓土,到底是詐尸還是另有隱情,我是刑警寧澤赖淤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布蜀漆,位于F島的核電站,受9級特大地震影響咱旱,放射性物質(zhì)發(fā)生泄漏确丢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一吐限、第九天 我趴在偏房一處隱蔽的房頂上張望鲜侥。 院中可真熱鬧,春花似錦诸典、人聲如沸描函。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舀寓。三九已至胆数,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間基公,已是汗流浹背幅慌。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轰豆,地道東北人胰伍。 一個(gè)月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像酸休,于是被迫代替她去往敵國和親骂租。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

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