解析ConstraintLayout的效能優勢

谷歌開發者_發表於2017-09-05

文 / Google 開發者計劃工程師 Takeshi Hagikura

自從在去年的 Google I/O 大會上釋出 ConstraintLayout 以來,我們一直不斷改進該佈局的穩定性,完善對佈局編輯器的支援。我們還針對 ConstraintLayout 增加了一些新功能,幫助您構建不同型別的佈局,例如引入鏈和按比例設定大小。


除了這些功能之外,使用 ConstraintLayout 還可以獲得一項顯著的效能優勢。在本文中,我們將向您介紹如何從這些效能改進中獲益。



Android 如何繪製檢視?

為了更好地理解 ConstraintLayout 的效能,我們先回過頭來看看 Android 如何繪製檢視。

當使用者將某個 Android 檢視作為焦點時,Android 框架會指示該檢視進行自我繪製。這個繪製過程包括 3 個階段:


1. 測量

系統自頂向下遍歷檢視樹,以確定每個 ViewGroup 和 View 元素應當有多大。在測量 ViewGroup 的同時也會測量其子物件。


2. 佈局

系統執行另一個自頂向下的遍歷操作,每個 ViewGroup 都根據測量階段中所確定的大小來確定其子物件的位置。


3. 繪製

系統再次執行一個自頂向下的遍歷操作。對於檢視樹中的每個物件,系統會為其建立一個 Canvas 物件,以便向 GPU 傳送一個繪製命令列表。這些命令包含系統在前面 2 個階段中確定的 ViewGroup 和 View 物件的大小和位置。


640?wx_fmt=gif

▲ 測量階段如何遍歷檢視樹的示例


繪製過程中的每個階段都需要對檢視樹執行一次自頂向下的遍歷操作。因此,檢視層次結構中嵌入(或巢狀)的檢視越多,裝置繪製檢視所需的時間和計算功耗也就越多。通過在 Android 應用佈局中保持扁平的層次結構,您可以為應用建立響應快速而靈敏的介面。


傳統佈局層次結構的開銷

請牢記上述解釋,下面我們來建立一個使用 LinearLayout 和 RelativeLayout 物件的傳統佈局層次結構。


640?wx_fmt=png

▲ 佈局示例


假設我們想構建一個像上圖那樣的佈局。如果您使用傳統佈局來構建,XML 檔案會包含類似於下面這樣的元素層次結構(在本例中,我們忽略屬性):

<RelativeLayout> <ImageView /> <ImageView /> <RelativeLayout> <TextView /> <LinearLayout> <TextView /> <RelativeLayout> <EditText /> </RelativeLayout> </LinearLayout> <LinearLayout> <TextView /> <RelativeLayout> <EditText /> </RelativeLayout> </LinearLayout> <TextView /> </RelativeLayout> <LinearLayout > <Button /> <Button /> </LinearLayout> </RelativeLayout>

儘管一般來說,這種型別的檢視層次結構都有改進的空間,但您幾乎必定還需要建立一個包含一些巢狀檢視的層次結構。

如前所述,巢狀的層次結構會給效能造成負面影響。我們使用 Android Studio 的 Systrace 工具來看看巢狀檢視對介面效能到底有何實際影響。我們通過程式設計方式針對每個 ViewGroup(ConstraintLayout 和 RelativeLayout)呼叫了測量和佈局階段並在執行測量和佈局呼叫期間觸發了 Systrace。以下命令可生成一個包含 20 秒間隔週期內發生的關鍵 Event 的概覽檔案,例如開銷巨大的測量/佈局階段:

python $ANDROID_HOME/platform-tools/systrace/systrace.py --time=20 -o ~/trace.html gfx view res


有關如何使用 Systrace 的詳細資訊,請參閱使用 Systrace 分析介面效能指南:

https://developer.android.google.cn/studio/profile/systrace.html


Systrace 會自動突出顯示此佈局中的(大量)效能問題,並給出修復這些問題的建議。通過點選“Alerts”標籤,您會發現,繪製此檢視層次結構需要反覆執行 80 次的測量和佈局階段,開銷極為龐大!

觸發開銷如此龐大的測量和佈局階段當然很不理想,如此龐大的繪製 Activity 會導致使用者能夠覺察到丟幀的現象。我們可以得出這樣的結論:這種巢狀式層次結構和 RelativeLayout(會對其每個子物件重複測量兩次)的特性導致效能低下。


640?wx_fmt=png

▲ 觀察 Systrace 針對使用 RelativeLayout 的佈局版本發出的提醒


您可以在我們的 GitHub 程式碼庫中檢視我們用來執行這些測量的完整程式碼:

https://github.com/googlesamples/android-constraint-layout-performance



ConstraintLayout 物件的優勢

如果您使用 ConstraintLayout 來構建相同的佈局,XML 檔案會包含類似於下面這樣的元素層次結構(再次忽略屬性):

<android.support.constraint.ConstraintLayout> <ImageView /> <ImageView /> <TextView /> <EditText /> <TextView /> <TextView /> <EditText /> <Button /> <Button /> <TextView /> </android.support.constraint.ConstraintLayout>


如本例所示,現在,該佈局擁有一個完全扁平的層次結構。這是因為 ConstraintLayout 允許您構建複雜的佈局,而不必巢狀 View 和 ViewGroup 元素。

舉個例子,我們來看一下佈局中間的 TextView 和 EditText:


640?wx_fmt=png


使用 RelativeLayout 時,您需要建立一個新的 ViewGroup 來垂直對齊 EditText 和 TextView:

<LinearLayout android:id="@+id/camera_area" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_below="@id/title" > <TextView android:text="@string/camera" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:id="@+id/cameraLabel" android:labelFor="@+id/cameraType" android:layout_marginStart="16dp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/cameraType" android:ems="10" android:inputType="textPersonName" android:text="@string/camera_value" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginTop="8dp" android:l ayout_marginStart="8dp"            android:layout_marginEnd="8dp" /> </RelativeLayout> </LinearLayout>


通過改用 ConstraintLayout,您只需新增一個從 TextView 基線到 EditText 基線之間的約束,即可實現同樣的效果,而不必建立另一個 ViewGroup:


640?wx_fmt=png

▲ EditText 和 TextView 之間的約束


在針對我們使用 ConstraintLayout 的佈局版本執行 Systrace 工具時,您會發現,同樣 20 秒間隔週期內執行的測量/佈局次數大大減少,開銷也隨之大大減少。這種效能的改進很有意義,現在,我們保持了扁平的檢視層次結構!


640?wx_fmt=png

▲ 觀察 Systrace 針對使用 ConstraintLayout 的佈局版本發出的提醒


同樣值得一提的是,我們構建 ConstraintLayout 版本的佈局時僅僅使用了佈局編輯器,而不是手工編輯 XML。而要使用 RelativeLayout 來實現同樣的視覺效果,我們很可能必須手工編輯 XML。



測量效能差異

我們使用 Android 7.0(API 級別 24)中引入的 OnFrameMetricsAvailableListener 分析了 ConstraintLayout 和 RelativeLayout 這兩種型別的佈局所執行的每次測量和佈局操作所花費的時間。通過該類,您可以收集有關應用介面渲染的逐幀時間資訊。

通過呼叫以下程式碼,您可以開始記錄每個幀的介面操作:

window.addOnFrameMetricsAvailableListener(        frameMetricsAvailableListener, frameMetricsHandler);

在能夠獲取時間資訊之後,該應用觸發 frameMetricsAvailableListener() 回撥。我們對測量/佈局的效能感興趣,因此,我們在檢索實際幀的持續時間時呼叫了 FrameMetrics.LAYOUT_MEASURE_DURATION。

Window.OnFrameMetricsAvailableListener {        _, frameMetrics, _ ->        val frameMetricsCopy = FrameMetrics(frameMetrics);        // Layout measure duration in nanoseconds        val layoutMeasureDurationNs =                frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);


如需詳細瞭解 FrameMetrics 可以檢索的其他型別的持續時間資訊,請參閱 FrameMetricsAPI 參考:

https://developer.android.google.cn/reference/android/view/FrameMetrics.html



測量結果:ConstraintLayout 速度更快

我們的效能比較結果表明:ConstraintLayout 在測量/佈局階段的效能比 RelativeLayout大約高 40%:


640?wx_fmt=png

▲ 測量/佈局(單位:毫秒,100 幀的平均值)


這些結果表明:ConstraintLayout 很可能比傳統佈局的效能更出色。不僅如此,ConstraintLayout 還具備其他一些功能,能夠幫助您構建複雜的高效能佈局。


有關詳情,請參閱使用 ConstraintLayout 構建快速響應的介面指南:

https://medium.com/google-developers/building-interfaces-with-constraintlayout-3958fa38a9f7


我們建議您在設計應用佈局時使用 ConstraintLayout。在過去,幾乎所有情形下,您都需要一個深度巢狀的佈局,因此,ConstraintLayout 應當成為您優化效能和易用性的不二之選。



附錄:測量環境 & 後續計劃

上述所有測量均在以下環境中執行:

  • 裝置 -    Nexus 5X    

  • Android 版本 -    8.0    

  • ConstraintLayout 版本 -    1.0.2


檢視開發者指南:

https://developer.android.google.cn/training/constraint-layout/index.html


API 參考文件:

https://developer.android.google.cn/reference/android/support/constraint/ConstraintLayout.html


媒體文章:

https://medium.com/google-developers/building-interfaces-with-constraintlayout-3958fa38a9f7


以完全理解 ConstraintLayout 能夠給您帶來什麼。再次感謝您,感謝自從我們的 Alpha 版 ConstraintLayout 釋出以來的幾個月裡提交反饋和問題的所有同仁。我們得以在今年早些時候釋出 ConstraintLayout 的 1.0 正式版,離不開您的支援,我們在此謹致以誠摯的謝意!我們將繼續改進 ConstraintLayout,請您繼續使用 Android Issue Tracker 向我們傳送反饋。


檢視全文及文中連結,請點選文末“閱讀原文”。


640?wx_fmt=gif

相關文章