Android View渲染效能優化

Android View 渲染效能優化

好久沒寫文章了,應該至少兩年多了,這兩年工作轉換了很多,現在又重回 Android 開發的行列,最近工作上剛好遇到需要優化一個頁面的渲染性能(解決卡頓問題),所以剛好透過這篇文章來覆盤跟總結一下 Android 性能優化

造成 View 渲染緩慢的原因

在這個 Case 中,有幾個非常常見的 View 卡頓原因,這邊就列出這幾個常見的 Case

Layout 佈局過於複雜

因為 inflate 需要花費的時間過久,如果你的 Layout 層次過於複雜的話,所以需要盡量減少 Layout 佈局的複雜度,善用ConstraintLayout去減少佈局的複雜度

RecycleView 頻繁全局刷新

這個蠻容易在開發過程中出現的一個錯誤,RecyclerView 主要的更新流程是透過

GetData –> setAdapter –> 通知 RecyclerView 更新
在通知 RecyclerView 更新這個環節有許多的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
final void	notifyDataSetChanged()
Notify any registered observers that the data set has changed.

final void notifyItemChanged(int position, Object payload)
Notify any registered observers that the item at position has changed with an optional payload object.

final void notifyItemChanged(int position)
Notify any registered observers that the item at position has changed.

final void notifyItemInserted(int position)
Notify any registered observers that the item reflected at position has been newly inserted.

final void notifyItemMoved(int fromPosition, int toPosition)
Notify any registered observers that the item reflected at fromPosition has been moved to toPosition.

final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload)
Notify any registered observers that the itemCount items starting at position positionStart have changed.

final void notifyItemRangeChanged(int positionStart, int itemCount)
Notify any registered observers that the itemCount items starting at position positionStart have changed.

final void notifyItemRangeInserted(int positionStart, int itemCount)
Notify any registered observers that the currently reflected itemCount items starting at positionStart have been newly inserted.

final void notifyItemRangeRemoved(int positionStart, int itemCount)
Notify any registered observers that the itemCount items previously located at positionStart have been removed from the data set.

final void notifyItemRemoved(int position)
Notify any registered observers that the item previously located at position has been removed from the data set.

但是這邊常犯的錯誤是直接呼叫 notifyDataSetChanged,這個會直接使整個 RecyclerView 全局刷新,但我們有時候獲得的資料只是一小部分的改動,所以我們應該根據當下情況去使用其他的 function,或者使用 Android 官方提供的DiffUtil去幫助做資料更新的比對

ref : https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil

局部更新造成全局更新

這個在這個 case 上,很容易發生的是在動態改變 text 內容的 TextView 之上,假設今天有一個場景,是有一個倒數計時的 UI 呈現,那麼就會定時地去更新該 TextView,但是如果 TextView 的 width 跟 height 是wrap_content,那就會造成這個 TextView 有機會的去更新全局的 UI。

onDraw, onMeasure, onLayout 時做了耗時操作

這幾個關鍵函數,都應該只做他們的自我權責,不應該在這些 Function 裡面做不必要的耗時操作,尤其是 onDraw,這也會造成 UI 渲染的效能瓶頸

頻繁 Add/Remove

頻繁 Add/Remove 也是會觸發整個佈局的重新繪製,所以應該避免

如何找出緩慢的貧頸

上述就是常見的幾種錯誤使用方式,但是我們在開發的時候,可能會遇到難以查找是什麼原因造成整體重繪(有可能上述狀況都有,不好定義),可以透過 Android 自帶的工具或者一些方式

在 RootView 的 requestLayout print stacktree

可以複寫 RootView 的 requestLayout function,並且在裡面打印 stacktree,這樣可以查找出是哪一個 UI 元件呼叫 stacktree,

透過開發者工具測量速度

在開發者選項裡有一個”硬件加速渲染“,裡面有一個“調試 GPU 過度繪製”,這個會在屏幕上以顏色來區分 overdraw(過度繪製,也就是進行了不必要的繪製)的嚴重重度:

  • 藍色 1 倍 overdraw
  • 綠色 2 倍 overdraw
  • 紅色 3 倍 overdraw
  • 紫色 4 倍 overdraw

在開發者選項面有一個是監控
選擇GPU使用情況,選擇第二個
在螢幕上顯示條型,就可以在手機上即時觀測到目前的使用情況

參考文件 :