效能與檢視區塊階層

View 物件階層的管理方式可能會對應用程式效能產生重大影響。本頁說明如何評估檢視區塊階層是否會導致應用程式速度變慢,並針對可能引起的問題提供解決策略。

本頁內容重點為改善以 View 為基礎的版面配置。如要進一步瞭解如何改善 Jetpack Compose 效能,請參閱「Jetpack Compose 效能」。

版面配置和測量效能

轉譯管道包含「版面配置與測量」階段,在這個階段,系統會在檢視區塊階層中適當定位相關項目。此階段的「測量」部分會決定 View 物件的尺寸和界線,「版面配置」部分會決定 View 物件在畫面上的位置。

這兩個管道階段都會耗用少量處理檢視區塊或版面配置的資源。在大多數情況下,這個資源用量很低,對效能沒有明顯影響。但是,如果應用程式新增或移除 View 物件 (例如 RecyclerView 物件回收或重複使用這些物件),影響就可能會較大。假如 View 物件需要根據限制調整大小,耗用的資源也可能比較多。舉例來說,如果應用程式對會將文字自動換行的 View 物件呼叫 SetText()View 就可能需要調整大小。

如果這類情況所需時間過長,影格就可能無法在 16 毫秒的允許時間內顯示,進而導致多個影格遭到捨棄,動畫也會出現卡頓。

由於無法將這些作業移至背景工作執行緒 (應用程式必須在主執行緒上進行處理),最好的解決方式是進行最佳化調整,盡可能縮短所需時間。

管理複雜的版面配置

您可以利用 Android Layouts,在檢視區塊階層中建立巢狀結構的 UI 物件。這個巢狀結構也可能會耗用版面配置的資源。應用程式處理版面配置的物件時,也會對該版面配置的所有子項執行相同程序。

如果是複雜的版面配置,有時只有在系統第一次計算版面配置時才會耗用資源。例如當應用程式回收 RecyclerView 物件中的某個複雜清單項目時,系統就需要安排所有物件的位置。在另一個範例中,細小的變更可能會向上傳播到鏈結中的父項,直到碰觸到不會影響父項尺寸的物件為止。

版面配置花費較長時間的常見原因,是多個階層的 View 物件構成彼此相互嵌套的巢狀結構。每個巢狀版面配置物件都會增加版面配置階段的耗用資源。階層越簡單,完成版面配置階段所需的時間就越短。

建議您使用版面配置編輯器建立 ConstraintLayout,而非 RelativeLayoutLinearLayout,因為這麼做通常更有效率,並且能減少版面配置的巢狀結構。不過,對於可以使用 FrameLayout 達成的簡易版面配置,則建議使用 FrameLayout

如果使用 RelativeLayout 類別,也許可以改為使用巢狀的非權重 LinearLayout 檢視區塊來達到相同效果,而且耗用資源較少。不過,如果您使用的是巢狀加權 LinearLayout 檢視區塊,則由於需要多項版面配置傳遞作業,所以版面配置耗用的資源會高很多,詳情請參閱下節。

建議您改用 RecyclerView 而非 ListView,因為這麼做可以回收個別清單項目的版面配置,不僅效率更高,也能改善捲動效能。

重複作業

一般來說,架構會在單次傳遞中執行版面配置或測量階段。然而,在一些較複雜的版面配置中,在決定元素最後的位置前,架構可能必須針對需多次傳遞才能解析的階層部分,多次執行疊代。這種必須執行多項版面配置和測量疊代作業的情形,稱為「重複作業」

舉例來說,使用 RelativeLayout 容器時,因為此容器可讓您依照其他 View 物件的位置,相對放置 View 物件,架構會執行以下動作:

  1. 執行版面配置和測量傳遞,架構會在此期間根據各子項的要求,計算每個子項物件的位置和尺寸。
  2. 使用這項資料,並將物件權重納入考量,算出相關檢視區塊的正確位置。
  3. 執行第二次版面配置傳遞,確定物件的位置。
  4. 繼續進行轉譯程序的下一階段。

檢視區塊階層的層級越多,對效能的負面影響就越大。

如先前所述,除了 FrameLayout 以外,ConstraintLayout 通常比其他版面配置更有效率。這麼做較不易發生多項版面配置傳遞作業,而且在許多情況下,也無需建立巢狀版面配置。

RelativeLayout 以外的容器也可能增加重複作業,例如:

  • 如果將 LinearLayout 檢視區塊設為水平方向,就可能重複進行版面配置和測量傳遞。如果在垂直方向加入 measureWithLargestChild,架構可能需要執行第二次傳遞來解析物件的正確尺寸,因此也可能發生兩次的版面配置和測量傳遞。
  • GridLayout 也允許相對定位,但通常可以藉由預先處理子項檢視區塊間的位置關係,避免發生重複作業。不過,如果版面配置使用權重或透過 Gravity 類別填入,就會失去預先處理的優勢,而在容器為 RelativeLayout 的情況下,架構可能必須執行多次傳遞。

多項版面配置和測量傳遞作業不一定會對效能造成負擔。但如果位置不對,就可能成為負擔。容器符合以下任一種情況時,請務必謹慎處理:

  • 容器是檢視區塊階層的根元素。
  • 容器底下的檢視區塊階層較深。
  • 畫面會填入容器的多個例項,類似 ListView 物件中的子項。

診斷檢視區塊階層的問題

版面配置效能是包含多個面向的複雜問題。以下工具可協助您找出效能瓶頸發生的位置。有些工具提供的資訊較不明確,但可提供實用的提示。

Perfetto

Perfetto 是一項可提供效能相關資料的工具。您可以在 Perfetto UI 中開啟這些 Android 追蹤記錄。

剖析 GPU 轉譯

如果您使用 Android 6.0 (API 級別 23) 以上版本的裝置,可透過裝置端剖析 GPU 轉譯工具獲得效能瓶頸的具體資訊。您可以利用這項工具,瞭解在版面配置和測量階段每個影格花費的轉譯時間。這項資料有助於診斷執行階段效能問題,以及判斷是否有哪些版面配置和測量問題需要解決。

剖析 GPU 轉譯會以圖形呈現擷取的資料,在其中使用藍色表示版面配置時間。如要進一步瞭解如何使用這項工具,請參閱「剖析 GPU 轉譯速度」。

Lint

Android Studio 的 Lint 工具可協助您瞭解檢視區塊階層中效率不佳的問題。如要使用這項工具,請依序選取「Analyze」>「Inspect Code」,如圖 1 所示。

圖 1. 在 Android Studio 中選取「Inspect Code」

在「Android」>「Lint」>「Performance」下方會顯示多個版面配置項目的資訊。如要瞭解細節,請點選展開各個項目,在畫面右側的窗格中查看詳細資訊。圖 2 是已展開資訊的範例。

圖 2. 查看 Lint 工具可識別的特定問題相關資訊。

只要按一下項目,即可在右側窗格中顯示該項目的相關問題。

如要進一步瞭解這部分的特定主題和問題,請參閱 Lint 說明文件。

版面配置檢查器

Android Studio 的版面配置檢查器工具提供應用程式的檢視區塊階層示意圖。這是瀏覽應用程式階層的好方法,您可以透過清楚的圖表查看特定檢視區塊的父項鏈結,便於檢查應用程式建構的版面配置。

您也可以透過版面配置檢查器顯示的檢視區塊,找出重複作業造成的效能問題。您也可以用這種方式識別巢狀版面配置的深層鏈結,或是具有大量巢狀子項的版面配置區域,這是另一個會耗用資源、影響效能的來源。在這種情況下,版面配置和測量階段可能會耗用大量資源,並導致效能問題。

詳情請參閱「使用版面配置檢查器和驗證功能,對版面配置進行偵錯」。

解決檢視區塊階層的問題

要實際解決檢視區塊階層引起的效能問題,背後的基本概念較難以實踐。要防止檢視區塊階層導致效能降低,需簡化檢視區塊階層和減少重複作業。本節會說明有助達成這些目標的策略。

移除多餘的巢狀版面配置

ConstraintLayout 是 Jetpack 程式庫,提供大量的不同機制,用於在版面配置中定位檢視畫面。這樣就不必建立巢狀 ConstaintLayout,有助於簡化檢視區塊階層。與其他版面配置類型相比,使用 ConstraintLayout 簡化階層通常較為簡單。

開發人員經常會使用過多的巢狀版面配置。舉例來說,RelativeLayout 容器包含的單一子項可能也是 RelativeLayout 容器。這樣的巢狀結構是多餘的,會為檢視區塊階層增加不必要的資源浪費。Lint 可以標記這個問題,減少偵錯所需時間。

採用 merge 或 include 標記

使用多冗餘巢狀版面配置的其中一個原因是 <include> 標記。舉例來說,您可以定義可重複使用的版面配置,如下所示:

<LinearLayout>
    <!-- some stuff here -->
</LinearLayout>

接著,您可以加入 <include> 標記,將下列項目新增至父項容器:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/app_bg"
    android:gravity="center_horizontal">

    <include layout="@layout/titlebar"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

此標記會以巢狀結構方式,在第二個版面配置中加入第一個版面配置,但這是沒必要的做法。

<merge> 標記有助於避免這個問題。如需此標記的詳細資訊,請參閱「使用 <merge> 標記」。

採用耗用資源較少的版面配置

您可能無法調整現有的版面配置設定,移除多餘的版面配置。在某些情況下,唯一的解決方式可能是改用完全不同的版面配置類型,從而簡化階層。

舉例來說,您可能會發現 TableLayout 能夠與更複雜的版面配置,提供與許多定位依附元件相同的功能。Jetpack 程式庫 ConstraintLayout 提供與 RelativeLayout 類似的功能,並且具備更多功能可協助您建立簡化且更有效率的版面配置。