Android View 知識(shí)體系

工作原理

Android 中通過 Window 作為屏幕的抽象,而 Window 的具體實(shí)現(xiàn)類是 PhoneWindow 。通過 WindowManager 和 WindowManagerService 配合工作,來管理屏幕的顯示內(nèi)容。

WindowManager 內(nèi)部真正調(diào)用的是 WindowManagerGobal 方法,添加視圖的是 addView 方法。在WindowManagerGobal 中,最終是通過 ViewRootImpl 來將 View 和 布局參數(shù)添加到屏幕上。實(shí)際上,真正管理 View 樹的是 ViewRootImpl 。

ViewRootImpl 通過調(diào)用 IWindowSession 接口定義的方法,通過 Binder 通信機(jī)制最終調(diào)用到 WMS 中的 Session 的 addToDisplay 方法。

在 WMS 中會(huì)為 Window 分配一個(gè) Surface,并確定窗口的顯示次序;Surface負(fù)責(zé)顯示界面,Window 本身并不具有繪制能力;WMS 會(huì)將 Surface 交給 SurfaceFlinger 來處理,SurfaceFlinger 會(huì)將這些 Surface混合,并繪制到屏幕上。

WMS 中執(zhí)行了 ViewRootImpl 中添加 Window 的請(qǐng)求,主要做了四件事情:

  • 對(duì)要添加的 Window 進(jìn)行檢查,如果不符合添加條件,就不會(huì)執(zhí)行下面的操作
  • WindowToken 相關(guān)處理,比如有的窗口類型需要提供 WindowToken,沒有提供就會(huì)終止添加邏輯;有的窗口需要由 WMS 隱式創(chuàng)建 WindowToken。
  • WindowState 的創(chuàng)建和相關(guān)處理,將 WindowToken 和 WindowState 相關(guān)聯(lián)。
  • 創(chuàng)建和配置 DisplayContent,完成窗口添加到系統(tǒng)前的準(zhǔn)備工作。

View 構(gòu)建視圖樹

在 Activity 的 setContentView 方法中,調(diào)用了 PhoneWindow 的 setContentView 方法,其中又經(jīng)過一系列調(diào)用,把布局文件解析成 View ,并塞到 DecorView 的一個(gè) id 為 R.id.content 的 mContentView 中。

DecorView 本身是一個(gè) FrameLayout,它還承載了 StatusBar 和 NavigationBar。

隨后,調(diào)用 handleResumeActivity 來喚醒 Activity 。在這個(gè)方法中,會(huì)通過 WindowManager 的 addView 方法,通過 WindowManager 來添加 DecorView,其中就用到 ViewRootImpl,ViewRootImpl 構(gòu)建視圖樹,并負(fù)責(zé) View 樹的測(cè)量、布局、繪制,以及通過 Choreographer 來控制 View 的刷新。

最后,調(diào)用 ViewRoot 的 setView 方法將 DecorView 賦值給 viewRoot 并調(diào)用 updateViewLayout 方法。

updateViewLayout

在這個(gè)方法中, 會(huì)調(diào)用一個(gè) scheduleTraversals 方法,scheduleTraversals 方法內(nèi)部通過一個(gè)Choreographer 對(duì)象的 postCallback 執(zhí)行一個(gè) Runnable ,在這個(gè) Runnable 中,調(diào)用 doTraversal 方法。

doTraversal 中又調(diào)用了 performTraversalsperformTraversals 方法使得 ViewTree 開始 View 的工作流程:

  1. relayoutWindow,內(nèi)部調(diào)用 IWindowSession 的 relayout 方法來更新 Window ,在這里會(huì)把 Java 層的 Surface 與 Native 層的 Surface 關(guān)聯(lián)起來;
  2. performMeasure,內(nèi)部調(diào)用 View 的 measure;
  3. performLayout,內(nèi)部調(diào)用 View 的 layout;
  4. performDraw,內(nèi)部調(diào)用 View 的 draw這樣就完成了對(duì) Window 的更新。

ViewRootImpl 與 WMS 交互

從上面的流程中,ViewRootImpl 的 setView 方法實(shí)際上通過調(diào)用到了 WMS 的 Session 的 addToDisplay 方法來通知 WMS 更新 View 的,WMS 會(huì)為每一個(gè) Window 關(guān)聯(lián)一個(gè) WindowState。除此之外,ViewRootImpl 的 setView 還做了一件重要的事就是注冊(cè) InputEventReceiver,這和 View 事件分發(fā)有關(guān)。

在 WindowState 中,會(huì)創(chuàng)建 SurfaceSession,它會(huì)在 Native 層構(gòu)造一個(gè) SurfaceComposerClient 對(duì)象,它是應(yīng)用程序與 SurfaceFlinger 溝通的橋梁。

總結(jié)

View 的最底層是一個(gè) Window 容器, Window 承載 View 。

Window 添加過程中,通過 WindowManager 中的 ViewRootImpl 的 setView 通知 WMS 去添加 Window 。

ViewRootImpl 通過 Binder 機(jī)制調(diào)用 WMS 的 Session 的 addToDisplay 方法,來實(shí)現(xiàn)發(fā)送請(qǐng)求的。

在 WMS 接收到添加 Window 的請(qǐng)求時(shí),會(huì)為 Window 分配一個(gè) Surface ,并且對(duì) Window 進(jìn)行處理,關(guān)聯(lián) WindowState。

WMS 將 Surface 交給 SurfaceFlinger 處理,渲染視圖。而 WindowState 中同時(shí)會(huì)創(chuàng)建 SurfaceSession ,與 SurfaceFlinger 進(jìn)行通信。

View 的交互,同時(shí)實(shí)在 ViewRootImpl 的 setView 時(shí)進(jìn)行注冊(cè)的,注冊(cè)了一個(gè) InputEventReceiver 接收事件。

繪制流程

ViewRootImpl最終的 performTraversals 方法依次調(diào)用了 performMeasureperformLayout、performDraw。

Measure

第一步是獲取 DecorView 的寬高的 MeasureSpec 然后執(zhí)行 performMeasure 流程。

performMeasure 中,因?yàn)?View 樹結(jié)構(gòu),通過遞歸的方式,層層調(diào)用子 View 的 measure 方法。

// 偽代碼
fun measure(args) {
        onMeasure(args)
}

fun onMeasure(args) {
        val size = getDefaultSize() // 獲取寬高
        setMeasureDimension(width, height) // 設(shè)置寬高
        if (this is ViewGroup) { // 如果是 ViewGroup 遍歷子 View 
                chidren.foreach { view ->
                        view.measure() // 調(diào)用子 View 的 measure 
                }
        }
}

Layout

performLayout 方法,會(huì)先調(diào)用 DecorView 的 layout 方法,然后遞歸調(diào)用子 View ,在這個(gè)過程會(huì)決定 View 的四個(gè)頂點(diǎn)的坐標(biāo)和實(shí)際的 View 的寬/高。

如果 View 大小發(fā)生變化,則會(huì)回調(diào) onSizeChanged 方法。

如果 View 發(fā)生變化,則會(huì)回調(diào) onLayout 方法。

  • onLayout 方法在 View 中是空實(shí)現(xiàn)。
  • 在 ViewGroup 中,會(huì)遞歸遍歷調(diào)用子 View 的 layout 法。

Draw

最后的是 performDraw 方法,里面會(huì)調(diào)用 drawSoftware 方法,這個(gè)方法需要先通過 mSurface#lockCanvas 屬性 獲取一個(gè) Canvas 對(duì)象,作為參數(shù)傳給 DecorView 的 draw 方法。

這個(gè)方法調(diào)用的是 View 的 draw 方法,先繪制 View 背景,然后繪制 View 的內(nèi)容。

如果有子 View 則會(huì)調(diào)用子 View 的 draw 方法,層層遞歸調(diào)用,最終完成繪制。

完成這三步之后,會(huì)在 ActivityThread 的 handleResumeActivity 最后調(diào)用 Activity 的 makeVisible 方法,這個(gè)方法就是將 DecorView 設(shè)置為可見狀態(tài)。

屏幕刷新機(jī)制

在一個(gè)典型的顯示系統(tǒng)中,一般包括CPU、GPU、Display三個(gè)部分, CPU負(fù)責(zé)計(jì)算幀數(shù)據(jù),把計(jì)算好的數(shù)據(jù)交給GPU,GPU會(huì)對(duì)圖形數(shù)據(jù)進(jìn)行渲染,渲染好后放到buffer(圖像緩沖區(qū))里存起來,然后Display(屏幕或顯示器)負(fù)責(zé)把buffer里的數(shù)據(jù)呈現(xiàn)到屏幕上。如下圖:

基礎(chǔ)概念

屏幕刷新率

一秒內(nèi)屏幕刷新的次數(shù)(一秒內(nèi)顯示了多少幀的圖像),單位 Hz(赫茲),如常見的 60 Hz。刷新頻率取決于硬件的固定參數(shù)(不會(huì)變的)。

逐行掃描

顯示器并不是一次性將畫面顯示到屏幕上,而是從左到右邊,從上到下逐行掃描,順序顯示整屏的一個(gè)個(gè)像素點(diǎn),不過這一過程快到人眼無法察覺到變化。以 60 Hz 刷新率的屏幕為例,這一過程即 1000 / 60 ≈ 16ms。

幀率

表示 GPU 在一秒內(nèi)繪制操作的幀數(shù),單位 fps。例如在電影界采用 24 幀的速度足夠使畫面運(yùn)行的非常流暢。而 Android 系統(tǒng)則采用更加流程的 60 fps,即每秒鐘GPU最多繪制 60 幀畫面。幀率是動(dòng)態(tài)變化的,例如當(dāng)畫面靜止時(shí),GPU 是沒有繪制操作的,屏幕刷新的還是buffer中的數(shù)據(jù),即GPU最后操作的幀數(shù)據(jù)。

畫面撕裂

一個(gè)屏幕內(nèi)的數(shù)據(jù)來自2個(gè)不同的幀,畫面會(huì)出現(xiàn)撕裂感,如下圖:

雙緩存

畫面撕裂的原因

屏幕刷新頻是固定的,比如每 16.6ms 從 buffer 取數(shù)據(jù)顯示完一幀,理想情況下幀率和刷新頻率保持一致,即每繪制完成一幀,顯示器顯示一幀。但是 CPU/GPU 寫數(shù)據(jù)是不可控的,所以會(huì)出現(xiàn) buffer 里有些數(shù)據(jù)根本沒顯示出來就被重寫了,即 buffer 里的數(shù)據(jù)可能是來自不同的幀的, 當(dāng)屏幕刷新時(shí),此時(shí)它并不知道 buffer 的狀態(tài),因此從 buffer 抓取的幀并不是完整的一幀畫面,即出現(xiàn)畫面撕裂。

簡(jiǎn)單說就是 Display 在顯示的過程中,buffer 內(nèi)數(shù)據(jù)被 CPU/GPU 修改,導(dǎo)致畫面撕裂。

雙緩存

由于圖像繪制和屏幕讀取 使用的是同個(gè)buffer,所以屏幕刷新時(shí)可能讀取到的是不完整的一幀畫面。

雙緩存,讓繪制和顯示器擁有各自的buffer:GPU 始終將完成的一幀圖像數(shù)據(jù)寫入到 Back Buffer,而顯示器使用 Frame Buffer,當(dāng)屏幕刷新時(shí),F(xiàn)rame Buffer 并不會(huì)發(fā)生變化,當(dāng)Back buffer準(zhǔn)備就緒后,它們才進(jìn)行交換。如下圖:

VSync

那么什么時(shí)候進(jìn)行兩個(gè)buffer的交換呢?

假如是 Back buffer 準(zhǔn)備完成一幀數(shù)據(jù)以后就進(jìn)行,那么如果此時(shí)屏幕還沒有完整顯示上一幀內(nèi)容的話,肯定是會(huì)出問題的。看來只能是等到屏幕處理完一幀數(shù)據(jù)后,才可以執(zhí)行這一操作了。

當(dāng)掃描完一個(gè)屏幕后,設(shè)備需要重新回到第一行以進(jìn)入下一次的循環(huán),此時(shí)有一段時(shí)間空隙,稱為VerticalBlanking Interval(VBI) 。這個(gè)時(shí)間點(diǎn)就是我們進(jìn)行緩沖區(qū)交換的最佳時(shí)間。因?yàn)榇藭r(shí)屏幕沒有在刷新,也就避免了交換過程中出現(xiàn)屏幕撕裂的狀況。

**VSync **(垂直同步)是 VerticalSynchronization 的簡(jiǎn)寫,它利用 VBI 時(shí)期出現(xiàn)的 vertical sync pulse(垂直同步脈沖)來保證雙緩沖在最佳時(shí)間點(diǎn)才進(jìn)行交換。另外,交換是指各自的內(nèi)存地址,可以認(rèn)為該操作是瞬間完成。

在Android4.1之前,屏幕刷新也遵循 上面介紹的 雙緩存+VSync 機(jī)制。

以時(shí)間的順序來看下將會(huì)發(fā)生的過程:

  1. Display 顯示第 0 幀數(shù)據(jù),此時(shí) CPU/GPU 在渲染第 1 幀畫面,且需在 Display 顯示下一幀前完成。
  2. 因?yàn)殇秩炯皶r(shí),Display 在第 0 幀顯示完成后,也就是第 1 個(gè) VSync 后,緩存進(jìn)行交換,然后正常顯示第 1 幀。
  3. 接著第 2 幀開始處理,是直到第 2 個(gè) VSync 快來前才開始處理的。
  4. 第 2 個(gè) VSync 來時(shí),由于第 2 幀數(shù)據(jù)還沒有準(zhǔn)備就緒,緩存沒有交換,顯示的還是第 1 幀。這種情況被Android 開發(fā)組命名為 “Jank”,即發(fā)生了丟幀。
  5. 當(dāng)?shù)?2 幀數(shù)據(jù)準(zhǔn)備完成后,它并不會(huì)馬上被顯示,而是要等待下一個(gè) VSync 進(jìn)行緩存交換再顯示。

因?yàn)榈?2 幀渲染不及時(shí),導(dǎo)致造成了第 1 幀多展示了一個(gè) VSync 周期,丟失了正常應(yīng)該展示的第 2 幀。

雙緩存的buffer 交換是在 Vsync 信號(hào)到來時(shí)進(jìn)行。交換后屏幕會(huì)取Frame buffer內(nèi)的新數(shù)據(jù),而實(shí)際 此時(shí)的Back buffer 就可以供 GPU 準(zhǔn)備下一幀數(shù)據(jù)了。 如果 Vsync 到來時(shí) CPU/GPU 就開始操作的話,是有完整的16.6ms的,這樣應(yīng)該會(huì)基本避免 jank 的出現(xiàn)了(除非CPU/GPU計(jì)算超過了16.6ms)。那如何讓 CPU/GPU計(jì)算在 Vsync 到來時(shí)進(jìn)行呢?

為了優(yōu)化顯示性能,在 Android 4.1 系統(tǒng)中對(duì) Android Display 系統(tǒng)進(jìn)行了重構(gòu),實(shí)現(xiàn)了Project Butter(黃油工程):系統(tǒng)在收到 VSync 脈沖后,將馬上開始下一幀的渲染。即一旦收到 VSync 通知(16ms觸發(fā)一次),CPU/GPU 立刻開始計(jì)算然后把數(shù)據(jù)寫入buffer。如下圖:

CPU/GPU 根據(jù) VSYNC 信號(hào)到來時(shí),同步處理數(shù)據(jù),可以讓 CPU/GPU 有完整的 16ms 時(shí)間來處理數(shù)據(jù),減少了 jank。

題又來了,如果界面比較復(fù)雜,CPU/GPU的處理時(shí)間較長(zhǎng) 超過了16.6ms呢?如下圖:

  1. 在第一次 VSync 信號(hào)到來時(shí),因?yàn)?GPU 還在處理 B 幀,緩存沒能交換,導(dǎo)致 A 幀被重復(fù)顯示。
  2. 而 B 完成后,又因?yàn)槿狈?VSync pulse 信號(hào),它只能等待下一次來臨。于是在這一過程中,有一大段時(shí)間是被浪費(fèi)的。
  3. 當(dāng)下一個(gè) VSync 出現(xiàn)時(shí),CPU/GPU 馬上執(zhí)行操作(A幀),且緩存交換,相應(yīng)的顯示屏對(duì)應(yīng)的就是B。這時(shí)看起來就是正常的。只不過由于執(zhí)行時(shí)間仍然超過 16ms ,導(dǎo)致下一次應(yīng)該執(zhí)行的緩沖區(qū)交換又被推遲了——如此循環(huán)反復(fù),便出現(xiàn)了越來越多的 “Jank”。

為什么 CPU 不能在第二個(gè) 16ms 處理繪制工作呢?

原因是只有兩個(gè) buffer,Back buffer正在被GPU用來處理B幀的數(shù)據(jù), Frame buffer的內(nèi)容用于Display的顯示,這樣兩個(gè)buffer都被占用,CPU 則無法準(zhǔn)備下一幀的數(shù)據(jù)。 那么,如果再提供一個(gè)buffer,CPU、GPU 和顯示設(shè)備都能使用各自的buffer工作,互不影響。

三緩存機(jī)制

三緩存就是在雙緩沖機(jī)制基礎(chǔ)上增加了一個(gè) Graphic Buffer 緩沖區(qū),這樣可以最大限度的利用空閑時(shí)間,帶來的壞處是多使用的一個(gè) Graphic Buffer 所占用的內(nèi)存。

  1. 第一個(gè) Jank,是不可避免的。但是在第二個(gè) 16ms 時(shí)間段,CPU/GPU 使用 第三個(gè) Buffer 完成C幀的計(jì)算,雖然還是會(huì)多顯示一次 A 幀,但后續(xù)顯示就比較順暢了,有效避免 Jank 的進(jìn)一步加劇。
  2. 注意在第 3 段中,A 幀的計(jì)算已完成,但是在第 4 個(gè) VSync 來的時(shí)候才顯示,如果是雙緩沖,那在第三個(gè)VSync 到來時(shí)就可以顯示了。

三緩沖有效利用了等待 VSync 的時(shí)間,減少了 jank ,但是帶來了延遲。

Choreographer

在 ViewRootImpl 去調(diào)用 updateViewLayout 方法真正執(zhí)行 繪制流程前,提到了一個(gè) Choreographer 對(duì)象。Choreographer 是用來進(jìn)行渲染的類。

在屏幕刷新原理中,我們知道,當(dāng) VSync 信號(hào)到來時(shí),CPU/GPU 立刻開始計(jì)算數(shù)據(jù)并存入 buffer 中,那么這個(gè)邏輯的實(shí)際實(shí)現(xiàn),就是 Choreographer 。

它的作用是:

  • 收到VSync信號(hào) 才開始繪制,保證繪制擁有完整的16.6ms,避免繪制的隨機(jī)性。
  • 協(xié)調(diào)動(dòng)畫、輸入和繪圖的計(jì)時(shí)。
  • 應(yīng)用層不會(huì)直接使用 Choreographer,而是使用更高級(jí)的API,例如動(dòng)畫和View繪制相關(guān)的ValueAnimator.start()、View.invalidate() 等。
  • 業(yè)界一般通過 Choreographer 來監(jiān)控應(yīng)用的幀率。

在 Android 源碼中,關(guān)于 Choreographer ,是從 ViewRootImpl 的 scheduleTraversals 方法中開始的。當(dāng)我們使用 ValueAnimator.start()、View.invalidate() 時(shí),最后也是走到 ViewRootImpl 的 scheduleTraversals 方法。即所有 UI 的變化都是走到 ViewRootImpl 的 scheduleTraversals() 方法。

    void scheduleTraversals() {
        if (!mTraversalScheduled) { // 保證同時(shí)間多次更改只會(huì)刷新一次
            mTraversalScheduled = true;
            // 添加消息屏障(異步消息插隊(duì)并優(yōu)先執(zhí)行),屏蔽同步消息,保證VSync到來立即執(zhí)行繪制
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

在這個(gè)方法中,最終調(diào)用 mChoreographer.postCallback() 方法,執(zhí)行一個(gè) Runnable,這個(gè) Runnable 會(huì)在下一個(gè) VSync 到來時(shí),執(zhí)行 doTraversal()。

doTraversal() 內(nèi)部會(huì)調(diào)用performTraversals 方法,進(jìn)行渲染流程。

而這個(gè) mChoreographer 對(duì)象,是在 ViewRootImpl 對(duì)象構(gòu)造方法中進(jìn)行初始化的:

public ViewRootImpl(Context context, Display display) {
    // ...
    mChoreographer = Choreographer.getInstance();
    // ...
}

看一下 Choreographer 類:

public final class Choreographer {
        private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }
}

Choreographer 和 Looper 一樣,是線程單例的。

它的 postCallaback 方法,第一個(gè)參數(shù)是 int 枚舉,包括:

      //輸入事件,首先執(zhí)行
    public static final int CALLBACK_INPUT = 0;
    //動(dòng)畫,第二執(zhí)行
    public static final int CALLBACK_ANIMATION = 1;
    //插入更新的動(dòng)畫,第三執(zhí)行
    public static final int CALLBACK_INSETS_ANIMATION = 2;
    //繪制,第四執(zhí)行
    public static final int CALLBACK_TRAVERSAL = 3;
    //提交,最后執(zhí)行,
    public static final int CALLBACK_COMMIT = 4;

內(nèi)部調(diào)用了 postCallbackDelayedInternal :


    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
                // ...
        synchronized (mLock) {
            // 當(dāng)前時(shí)間
            final long now = SystemClock.uptimeMillis();
            // 加上延遲時(shí)間
            final long dueTime = now + delayMillis;
            //取對(duì)應(yīng)類型的CallbackQueue添加任務(wù)
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            if (dueTime <= now) {
                //立即執(zhí)行
                scheduleFrameLocked(now);
            } else {
                //延遲運(yùn)行,最終也會(huì)走到scheduleFrameLocked()
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

內(nèi)部最后執(zhí)行的是 scheduleFrameLocked 方法。

通過 postCallback 方法名稱和與 Looper 一樣的結(jié)構(gòu)我們就能猜測(cè)到,這里用到了 Handler 機(jī)制,來處理 VSync 。

Choreographer 內(nèi)部有自己的消息隊(duì)列 mCallbackQueues ,有自己的 Handler 。

    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    // 執(zhí)行doFrame,即繪制過程
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    //申請(qǐng)VSYNC信號(hào),例如當(dāng)前需要繪制任務(wù)時(shí)
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    //需要延遲的任務(wù),最終還是執(zhí)行上述兩個(gè)事件
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }

doScheduleCallback 方法內(nèi)部執(zhí)行的也是 scheduleFrameLocked

在 scheduleFrameLocked 中,邏輯大概是:

  • 如果系統(tǒng)未開啟 VSYNC 機(jī)制,此時(shí)直接發(fā)送 MSG_DO_FRAME 消息到 FrameHandler。此時(shí)直接執(zhí)行 doFrame 方法。
  • Android 4.1 之后系統(tǒng)默認(rèn)開啟 VSYNC,在 Choreographer 的構(gòu)造方法會(huì)創(chuàng)建一個(gè) FrameDisplayEventReceiver,scheduleVsyncLocked 方法將會(huì)通過它申請(qǐng) VSYNC 信號(hào)。
  • 調(diào)用 isRunningOnLooperThreadLocked 方法,其內(nèi)部根據(jù) Looper 判斷是否在原線程,否則發(fā)送消息到 FrameHandler。最終還是會(huì)調(diào)用 scheduleVsyncLocked 方法申請(qǐng) VSYNC 信號(hào)。

FrameHandler的作用:發(fā)送異步消息,這些異步消息會(huì)被插隊(duì)優(yōu)先處理。有延遲的任務(wù)發(fā)延遲消息、不在原線程的發(fā)到原線程、沒開啟VSYNC的直接走 doFrame 方法取執(zhí)行繪制。

繼續(xù)跟進(jìn), scheduleVsyncLocked 方法是如何申請(qǐng) VSYNC 信號(hào)的呢?

    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }
    public DisplayEventReceiver(Looper looper, int vsyncSource) {
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }
        mMessageQueue = looper.getQueue();
        // 注冊(cè)VSYNC信號(hào)監(jiān)聽者
        mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, vsyncSource);
        mCloseGuard.open("dispose");
    }

在 DisplayEventReceiver 的構(gòu)造方法會(huì)通過 JNI 創(chuàng)建一個(gè) IDisplayEventConnection 的 VSYNC 的監(jiān)聽者。

FrameDisplayEventReceiver的scheduleVsync()就是在 DisplayEventReceiver中:

    public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
            // 申請(qǐng)VSYNC中斷信號(hào),會(huì)回調(diào)onVsync方法
            nativeScheduleVsync(mReceiverPtr);
        }
    }

scheduleVsync 調(diào)用到了 native 方法 nativeScheduleVsync 去申請(qǐng) VSYNC 信號(hào)。VSYNC 信號(hào)的接受回調(diào)是onVsync() 。在 FrameDisplayEventReceiver 中有具體實(shí)現(xiàn):

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;
        // ...

        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            long now = System.nanoTime();
            if (timestampNanos > now) {
                Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
                        + " ms in the future!  Check that graphics HAL is generating vsync "
                        + "timestamps using the correct timebase.");
                timestampNanos = now;
            }

            if (mHavePendingVsync) {
                Log.w(TAG, "Already have a pending vsync event.  There should only be "
                        + "one at a time.");
            } else {
                mHavePendingVsync = true;
            }

            mTimestampNanos = timestampNanos;
            mFrame = frame;
            //將本身作為runnable傳入msg, 發(fā)消息后 會(huì)走run(),即doFrame(),也是異步消息
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
    }

onVsync() 中,將接收器本身作為 Runnable 傳入異步消息 msg,并使用 mHandler 發(fā)送 msg,最終執(zhí)行doFrame 方法。

最后是 doFrame 方法:

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            // ...
                        // 預(yù)期執(zhí)行時(shí)間
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            // 超時(shí)時(shí)間是否超過一幀的時(shí)間
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                    // 計(jì)算掉幀數(shù)
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                        // 掉幀超過30幀打印Log提示
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  ");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                // ...
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            // ...
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            // Frame標(biāo)志位恢復(fù)
            mFrameScheduled = false;
            // 記錄最后一幀時(shí)間
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
                        // 按類型順序 執(zhí)行任務(wù)
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            // input 最先
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            // animation
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
                        // CALLBACK_TRAVERSAL
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
                        // commit 最后
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

重繪相關(guān)的方法

  • invalidate

    調(diào)用 View.invalidate() 方法后會(huì)逐級(jí)往上調(diào)用父 View 的相關(guān)方法,最終在 Choreographer 的控制下調(diào)用 ViewRootImpl.performTraversals() 方法。只有滿足條件才會(huì)執(zhí)行 measure 和 layout 流程,否則只執(zhí)行 draw 流程。

    draw 流程的執(zhí)行過程與是否開啟硬件加速有關(guān):

    • 關(guān)閉硬件加速則從 DecorView 開始往下的所有子 View 都會(huì)被重新繪制。
    • 開啟硬件加速則只有調(diào)用 invalidate 方法的 View 才會(huì)重新繪制。
  • requestLayout

    調(diào)用 View.requestLayout 方法后會(huì)依次調(diào)用 performMeasure, performLayoutperformDraw 方法。

    調(diào)用者 View 及其父 View 會(huì)重新從上往下進(jìn)行 measure , layout 流程,一般情況下不會(huì)執(zhí)行 draw 流程。

    因此,當(dāng)只需要進(jìn)行重繪時(shí)可以使用 invalidate 方法,如果需要重新測(cè)量和布局則可以使用 requestLayout 方法,而 requestLayout 方法不一定會(huì)重繪,因此如果要進(jìn)行重繪可以再手動(dòng)調(diào)用 invalidate 方法。

事件分發(fā)

MotionEvent

事件分發(fā)就是把MotionEvent分發(fā)給View的過程。當(dāng)一個(gè)MotionEvent產(chǎn)生以后,系統(tǒng)需要把這個(gè)事件傳遞給一個(gè)具體的View,這個(gè)傳遞過程就是分發(fā)過程。

點(diǎn)擊事件的分發(fā)方法

涉及事件分發(fā)有三個(gè)方法:

  • dispatchTouchEvent

    如果事件能夠傳遞給當(dāng)前的 View ,那么此方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前 View 的 onTouchEvent 和下一級(jí) View 的 dispatchTouchEvent 方法的影響,表示是否消耗當(dāng)前事件。

  • onInterceptTouchEvent

    在 dispatchTouchEvent 內(nèi)部調(diào)用,用來判斷是否攔截某個(gè)事件,如果當(dāng)前 View 攔截了某事件,那么在同一個(gè)事件序列中,此方法不會(huì)再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。

  • onTouchEvent

    在 dispatchTouchEvent 方法中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前 View 無法再次接收到事件。

事件分發(fā)分為兩種情況,View 和 ViewGroup:

View
public boolean dispatchTouchEvent(MotionEvent event) {
    // ... 
    if (onFilterTouchEventForSecurity(event)) {
        // ... 
        // 優(yōu)先執(zhí)行 onTouchListener 的 onTouch 方法
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        // 其次執(zhí)行自己的 onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    // ...
    return result;
}

View 的事件分發(fā),主要是首先執(zhí)行 onTouchListener ,然后調(diào)用 onTouchEvent 返回是否消耗事件。

ViewGroup
// 偽代碼
public boolean dispatchTouchEvent(MotionEvent event) {
        // ...
        // 1\. 優(yōu)先處理攔截
        boolean intercepted = onInterceptTouchEvent(ev);
        if (!canceled && !intercepted) {
                // 去查找能夠消耗事件的子View
        final float x = isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                final float y = isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                final ArrayList<View> preorderedList = buildTouchDispatchChildList(); // 子 View 列表
                for (int i = childrenCount - 1; i >= 0; i--) {
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                break;
                        }       
                }
        }
        if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
    }
    // ...
}

ViewGroup 中,會(huì)優(yōu)先執(zhí)行 onInterceptTouchEvent 判斷是否需要攔截,如果不需要會(huì)繼續(xù)遍歷子 View ,通過

dispatchTransformedTouchEvent 方法,繼續(xù)分發(fā)事件:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);  // 【2】自身 dispatchTouchEvent
        } else {
            handled = child.dispatchTouchEvent(event);  // 【1】
        }
        event.setAction(oldAction);
        return handled;
    }
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            if (child == null) { 
                handled = super.dispatchTouchEvent(event); // 【2】自身 dispatchTouchEvent
            } else {    
                // 帶有偏移的事件響應(yīng)
                final float offsetX = mScrollX - child.mLeft;
                final float offsetY = mScrollY - child.mTop;
                event.offsetLocation(offsetX, offsetY);
                handled = child.dispatchTouchEvent(event); //【1】

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        transformedEvent = event.split(newPointerIdBits);
    }
    // Perform any necessary transformations and dispatch.
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix()); 
        }
        handled = child.dispatchTouchEvent(transformedEvent); // 【1】
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

dispatchTransformedTouchEvent 中會(huì)根據(jù)傳進(jìn)來的子 View 去繼續(xù)調(diào)用 dispatchTouchEvent ,向下分發(fā)。

當(dāng)調(diào)用 super.dispatchTouchEvent(event)時(shí),會(huì)調(diào)用 ViewGroup 的父類 View 的 dispatchTouchEvent 方法。也就是會(huì)先檢查 onTouchListener,然后執(zhí)行 onTouchEvent 。

真實(shí)的分發(fā)流程

ViewGroup 中包含 View 的情況:

  1. ViewGroup 先調(diào)用 dispatchTouchEvent 進(jìn)行事件分發(fā)

  2. ViewGroup 調(diào)用 onInterceptTouchEvent 判斷是否要攔截

    • return true, 攔截事件進(jìn)行處理,事件不會(huì)繼續(xù)向子 View 分發(fā),調(diào)用自身的 onTouchEvent。

    • return false,繼續(xù)執(zhí)行步驟 3。

  3. 調(diào)用子 View 的 dispatchTouchEvent

  4. 子 View 調(diào)用 onTouchEvent 判斷是否消耗事件

    • return true, 進(jìn)行攔截,事件不會(huì)繼續(xù)向子 View 分發(fā),調(diào)用自身的 onTouchEvent。
    • return false,不響應(yīng)事件。

卡頓分析

卡頓常見的原因

  1. 頻繁的 requestLayout

    如果頻繁調(diào)用rquestLayout()方法,會(huì)導(dǎo)致在一幀的周期內(nèi)頻繁的發(fā)生計(jì)算,導(dǎo)致整個(gè) Traversal 過程變長(zhǎng),所以,應(yīng)當(dāng)盡量避免頻繁的 requestLayout 的情況,常見的情況有,對(duì) View 進(jìn)行多次重復(fù)執(zhí)行動(dòng)畫,例如在列表中,對(duì)一個(gè) item 添加動(dòng)畫。

  2. UI 線程被阻塞

    經(jīng)典的不要在 UI 現(xiàn)場(chǎng)做耗時(shí)操作。

作者:自動(dòng)化BUG制造器
鏈接:https://juejin.cn/post/7083157254035210276

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

相關(guān)閱讀更多精彩內(nèi)容

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