Handler消息機制

在這里插入圖片描述

消息分發(fā)和處理是如何工作的仁热?

消息處理框架如何創(chuàng)建和啟動

1.創(chuàng)建消息循環(huán)分發(fā)對象Looper
    private Looper(boolean quitAllowed) {
        //消息儲存的隊列
        mQueue = new MessageQueue(quitAllowed);
        //創(chuàng)建消息處理框架的線程
        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));
    }
    
    //開啟消息循環(huán)
    public static void loop() {
        final Looper me = myLooper();
        //檢查當前線程是否已經(jīng)創(chuàng)建了Looper對象并緩存
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//當前l(fā)ooper需要分發(fā)的消息的消息隊列
        boolean slowDeliveryDetected = false;//日志開關
        //注意:死循環(huán)遍歷
        for (;;) {
            Message msg = queue.next(); // 獲取下一個消息 => 可能會阻塞(比如消息隊列為空時,后續(xù)分析該源碼)但是阻塞會釋放cpu,這是為什么死循環(huán)遍歷卻不會導致主線程卡住的原因
            if (msg == null) {
                //這里標示著消息分發(fā)流程已經(jīng)終止了
                return;
            }

            // 獲取當前日志打印服務提供者,Looper允許外部設置自己的打印服務棵帽,比如主線程就在ActivityThread中設置了LogPrinter對象,一些卡頓檢測框架就是根據(jù)消息處理前后的日志中的時間來統(tǒng)計耗時的
            final Printer logging = me.mLogging;
            if (logging != null) {
                //打印當前消息開始分發(fā)和處理的時間
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            ...//Trace對象來跟蹤處理時間
            try {
                //將消息分發(fā)給其目標處理者 => 消息不能沒有目標處理者 
                msg.target.dispatchMessage(msg);//消息處理流程后續(xù)分析
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            if (logging != null) {
                //打印消息處理結(jié)束日志
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            //回收該消息:將該消息加入對象池 并重置其狀態(tài)
            msg.recycleUnchecked();
        }
    }
  1. Looper只能通過prepare及其重載方法創(chuàng)建频鉴,且同一線程中只能創(chuàng)建一次(即調(diào)用prepare方法一次)榄檬,創(chuàng)建后存儲在ThreadLocal中
  2. Looper對象創(chuàng)建時必須創(chuàng)建MessageQueue隊列,可理解為將消息分發(fā)者和消息來源(消息隊列)建立聯(lián)系
  3. 消息循環(huán)是死循環(huán)遍歷刹悴,其不卡住的原因是消息隊列的next方法雖然阻塞行楞,但是會釋放cpu
擴展:ThreadLocal的set方法是如何工作的?
    public void set(T value) {
        //獲取此方法被調(diào)用時所在的線程
        Thread t = Thread.currentThread();
        //獲取Thread對象的threadLocals變量 類型是ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //將當前value保存到map中土匀,key為當前的ThreadLocal子房,即:ThreadLocalMap對象可以保存多個value,但同一個ThreadLocal對象就轧,只會有一個value被保存
            map.set(this, value);
        else
        //如果map為空 則會創(chuàng)建一個ThreadLocalMap對象并設置初始值
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
2.創(chuàng)建消息處理者
    public Handler(Callback callback, boolean async) {
        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;
    }

1.創(chuàng)建Handler對象時证杭,將消息分發(fā)對象、消息隊列等復制給處理者妒御,這里callback在消息處理時會用到解愤,mAsynchronous表示是否為異步,后面解釋異步消息原理時用到

消息如何創(chuàng)建和發(fā)送給消息框架

創(chuàng)建一個空消息
    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);
    }
    
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // 清空消息的狀態(tài)
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

1.Message對象可以直接創(chuàng)建或者從對象緩存池中獲取

發(fā)送消息給消息隊列
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //發(fā)送一個在將來時間點uptimeMillis處執(zhí)行的消息
        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);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //消息的目標設置當前handler
        msg.target = this;
        if (mAsynchronous) {
            //如果handler是異步handler乎莉,則其發(fā)送的消息都是異步消息 => 異步消息在后續(xù)分析
            msg.setAsynchronous(true);
        }
        //將消息添加到消息隊列
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    boolean enqueueMessage(Message msg, long when) {
        //先檢查消息狀態(tài):即 有沒有處理者和是否已經(jīng)在使用了
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            //如果消息處理正在停止送讲,則不發(fā)送該消息
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            //設置消息狀態(tài):在使用 和 時間點
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;//消息頭指針=>消息隊列由鏈表實現(xiàn) 其最大容量是多少呢奸笤?
            boolean needWake;//是否需要喚醒正在消息隊列上等待的對象
            if (p == null || when == 0 || when < p.when) {
                // 插入到隊列頭部
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;//mBlocked表示是否在消息分發(fā)時阻塞 =>如果之前l(fā)ooper在此隊列上阻塞,則需要喚醒
            } else {
                //如果之前已在等待 并且 隊列非空哼鬓,頭節(jié)點沒有處理者 且 當前插入消息為異步监右,則需要喚醒等待
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //遍歷隊列,按照時間先后順序插入消息到隊列中
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            //如果需要喚醒等待者异希,則通過native的方法喚醒秸侣,這里mPtr為native層消息隊列的地址
            if (needWake) {
                //實際上,native層是通過向管道寫入某個字符來喚醒管道另一端的等待者宠互,
                nativeWake(mPtr);//這里調(diào)用后味榛,會喚醒在MessageQueue的next方法上等待的Looper,
            }
        }
        return true;
    }

1.消息隊列由鏈表實現(xiàn)予跌,其容量無限制

消息框架如何分發(fā)和處理消息

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
  1. 如果消息自己攜帶了callback(Runnable類型)搏色,則直接調(diào)用callback的run方法 否則到第2步
  2. 如果Handler中mCallback不為空,則調(diào)用mCallback的handleMessage方法處理 如果其放回true券册,則終止后續(xù)流程
  3. 直接調(diào)用Handler自身的handleMessage方法處理消息

Handler是如何切換線程的

Handler是用于同一個進程的線程間通信频轿。Looper讓主線程無限循環(huán)地從自己的MessageQueue拿出消息處理,所以我們就可以知道處理消息肯定是在主線程中處理的烁焙,那么是怎樣在其他的線程往主線程的隊列里放入消息呢航邢?道理其實很簡單,我們知道在同一進程中線程和線程之間資源是共享的骄蝇,也就是對于任何變量在任何線程都是可以訪問和修改的膳殷,只要考慮并發(fā)性做好同步就行了,那么只要拿到MessageQueue 的實例九火,就可以往主線程的MessageQueue放入消息赚窃,主線程在輪詢的時候就可以在主線程處理這個消息。那么是怎么拿到主線程 MessageQueue的實例呢岔激,當然是可以拿到的(在主線程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Google 為了統(tǒng)一添加消息和消息的回調(diào)處理勒极,又專門構(gòu)建了Handler類,你只要在主線程構(gòu)建Handler類虑鼎,那么這個Handler實例就獲取主線程MessageQueue實例的引用(獲取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;)辱匿,Handler 在sendMessage的時候就通過這個引用往消息隊列里插入新消息。Handler 的另外一個作用炫彩,就是能統(tǒng)一處理消息的回調(diào)匾七。這樣一個Handler發(fā)出消息又確保消息處理也是自己來做,這樣的設計非常的贊媒楼。具體做法就是在隊列里面的Message持有Handler的引用(哪個handler 把它放到隊列里乐尊,message就持有了這個handler的引用)戚丸,然后等到主線程輪詢到這個message的時候划址,就來回調(diào)我們經(jīng)常重寫的Handler的handleMessage(Message msg)方法扔嵌。

空閑消息是如何工作的?及其實際應用

    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            //表示消息處理已經(jīng)停止
            return null;
        }

        int pendingIdleHandlerCount = -1; // 需要空閑消息數(shù)量
        int nextPollTimeoutMillis = 0;//poll消息需等待的時間 此處只有native層會使用該值夺颤,對native而言 其值有多重含義:0:直接返回 -1:無限等待 其他值:等待時常 [詳情](https://www.kancloud.cn/alex_wsc/android-deep2/413394)
        //死循環(huán)遍歷
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //通知native層消費其消息痢缎,該方法會阻塞,其是當前方法阻塞的原因
            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;
                //如果頭節(jié)點消息沒有target 這種情況是由于給消息隊列設置了異步柵欄
                if (msg != null && msg.target == null) {
                    //找到第一個異步消息世澜,此處是為了柵欄設計
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 下一個消息還沒有準備好 所以設置一個下一次喚醒的延時時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //找到了一個msg,將消息從隊列中移除
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //直接返回該消息
                        return msg;
                    }
                } else {
                    // 沒有更多消息 nextPollTimeoutMillis = -1時 nativePollOnce方法會無限阻塞
                    nextPollTimeoutMillis = -1;
                }

                // 正在退出
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //pendingIdleHandlerCount只可能在第一次賦值時時小于0
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    /此時沒有消息需要處理:即空閑狀態(tài)
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // 沒有需要在空閑狀態(tài)下執(zhí)行的任務 則標記進入阻塞狀態(tài)
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //拷貝空閑時任務 數(shù)組
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //處理空暇時任務
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // 釋放保存的空閑時任務數(shù)組

                boolean keep = false;//是否需要保存該任務
                try {
                    keep = idler.queueIdle();//如果這里返回了true 即保持該任務 那么該任務下次空閑時會再被執(zhí)行
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                //如果不需要保持 則會移除該任務
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 重置空閑時任務數(shù)量
            pendingIdleHandlerCount = 0;

            //設置等待時間為0 防止在處理空閑任務時添加了新的任務
            nextPollTimeoutMillis = 0;
        }
    }

柵欄設計原理及其應用独旷?

柵欄的作用:添加柵欄后寥裂,消息隊列的next不會返回同步消息嵌洼,只會返回異步消息封恰。又因為消息默認時同步的,所以此時Looper線程將阻塞業(yè)務消息的分發(fā)和處理诺舔。待移除柵欄后鳖昌,才會分發(fā)和處理。
柵欄的應用:ViewRootImpl請求垂直同步信號時许昨,添加一個柵欄給主線程的隊列,之后業(yè)務方post的消息都不會執(zhí)行糕档,直到處置同步回調(diào)時移除了柵欄

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

    //向消息隊列中添加一個異步柵欄 返回值為該柵欄的token  移除柵欄需要使用到token
    private int postSyncBarrier(long when) {
        //這里添加柵欄的目的是 攔住同步消息的分發(fā)和處理 所以不用去喚醒隊列
        synchronized (this) {
            final int token = mNextBarrierToken++;//柵欄的token 標示柵欄唯一性
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
            //將消息添加到隊列中 需要注意 這里的消息沒有target 即沒有處理者 如果記得文章前面的next方法的實現(xiàn),就會知道柵欄是怎么攔截同步消息的
            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

最后附上類圖


在這里插入圖片描述
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拌喉,隨后出現(xiàn)的幾起案子翼岁,更是在濱河造成了極大的恐慌司光,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榆俺,死亡現(xiàn)場離奇詭異坞淮,居然都是意外死亡,警方通過查閱死者的電腦和手機回窘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烁涌,“玉大人苍碟,你說我怎么就攤上這事撮执。” “怎么了蜓肆?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵谋币,是天一觀的道長。 經(jīng)常有香客問我蕾额,道長,這世上最難降的妖魔是什么逼友? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任秤涩,我火速辦了婚禮帜乞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黎烈。我一直安慰自己匀谣,他們只是感情好,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布烈炭。 她就那樣靜靜地躺著宝恶,像睡著了一般符隙。 火紅的嫁衣襯著肌膚如雪垫毙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天丽蝎,我揣著相機與錄音膀藐,去河邊找鬼红省。 笑死栏笆,一個胖子當著我的面吹牛臊泰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缸逃,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丁眼!你這毒婦竟也來了昭殉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蹂风,失蹤者是張志新(化名)和其女友劉穎乾蓬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體任内,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年趋距,在試婚紗的時候發(fā)現(xiàn)自己被綠了越除。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡铜跑,死狀恐怖骡澈,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肋殴,我是刑警寧澤坦弟,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布酿傍,位于F島的核電站,受9級特大地震影響赤炒,放射性物質(zhì)發(fā)生泄漏亏较。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一遵岩、第九天 我趴在偏房一處隱蔽的房頂上張望巡通。 院中可真熱鬧尘执,春花似錦宴凉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至图仓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惶看,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工纬黎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劫窒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓冠息,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逛艰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

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