架構(gòu)設(shè)計分析(二)Android消息機(jī)制篇二 為什么不能在子線程中更新UI

來點前奏說明

當(dāng)你打開這個文檔的時候,你已經(jīng)做好準(zhǔn)備了,話不多說開搞。
本文以Android 9.0 版本進(jìn)行分析,當(dāng)然你也可以在線看源碼
在線源碼查看
Android源碼下載編譯
9.0源碼百度網(wǎng)盤下載鏈:https://pan.baidu.com/s/1OEek7vXE9FUhzVnOfTVJzg 提取碼:d0ks
在此特別說明,我這篇主要分析流程和注釋。我把英語注釋也粘貼了,大家自己去翻譯自己消化,個人意見重點是流程+注釋,流程+注釋,流程+注釋。

為什么有Handler:
  • 主線程不能做耗時操作
  • 子線程不能更新UI
那么為什么不能在子線程更新UI呢
  • 如果你沒看過源碼或者分析過,我想你會回答谷歌這樣設(shè)計的。
  • 如果繼續(xù)問谷歌為什么這么設(shè)計呢,我猜想你內(nèi)心已經(jīng)開始罵娘了,我TM哪知道他為什么這么設(shè)計。
  • 這篇文章只是我自己對這個事情的看法,如果有誤,歡迎各位評論指正。
子線程更新UI常見的報錯信息
  • android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
    at android.view.ViewRootImpl.checkThread(ViewRootImpl.Java:6581)
    at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:924)
堆棧代碼部分

framework/base/core/java/android/view/ViewRootImpl.java

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

接著往下看異常日志,發(fā)現(xiàn)了ViewRootImpl中調(diào)用的地方是requestLayout方法

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

scheduleTraversals看字面意思計劃遍歷

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

framework/base/core/java/android/view/Choreographer.java 調(diào)postCallback方法

剛開始對這個類注釋說明了一下,我翻譯了一下。協(xié)調(diào)動畫、輸入和繪圖的計時
Coordinates the timing of animations, input and drawing

postCallback方法的的第二個參數(shù):TraversalRunnable,意思是遍歷線程,是一個后臺任務(wù)

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

里面除了調(diào)用了doTraversal()方法,我們繼續(xù)看doTraversal()方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
ViewRootImpl啥時候創(chuàng)建的?

可以看到里面調(diào)用了performTraversals()方法,View的繪制過程就是從performTraversals方法開始的。我們現(xiàn)在知道了,每一次訪問UI,Android都會重新繪制View。ViewRoot會調(diào)用checkThread方法檢查當(dāng)前訪問UI的線程是哪個,如果不是UI線程則會拋出異常。

為什么谷歌要提出:“UI更新一定要在UI線程里實現(xiàn)”這一規(guī)則呢?

目的在于提高移動端更新UI的效率和和安全性,以此帶來流暢的體驗。原因是:Android的UI訪問是沒有加鎖的,多個線程可以同時訪問更新操作同一個UI控件。也就是說訪問UI的時候,android系統(tǒng)當(dāng)中的控件都不是線程安全的,這將導(dǎo)致在多線程模式下,當(dāng)多個線程共同訪問更新操作同一個UI控件時容易發(fā)生不可控的錯誤,這是致命的。所以Android中規(guī)定只能在UI線程中訪問UI,這相當(dāng)于從另一個角度給Android的UI訪問加上鎖

FAQ

Question1:Handler Message MessageQueue對應(yīng)關(guān)系?
Answer1:一個線程只能有一個Looper和MessageQueue 但可以接收多個Handler發(fā)過來的多個消息
一個Message只能屬于一個Handler
一個Handler只能處理自己發(fā)送給Looper的消息。
一個MessageQueue對應(yīng)多個Message

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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