【Android 性能优化】布局渲染优化 ( CPU 渲染优化 | 减少布局的嵌套 | 测量布局绘制时间 | OnFrameMetricsAvailableListener | 布局渲染优化总结 )
文章目錄
- 一、 減少布局嵌套
- 二、 布局渲染時間測量
- 1. FrameMetrics 使用流程
- 2. FrameMetrics 參數(shù)解析
- 3. FrameMetrics 代碼示例
- 三、 布局渲染優(yōu)化總結(jié)
一、 減少布局嵌套
在 【Android 性能優(yōu)化】布局渲染優(yōu)化 ( GPU 過度繪制優(yōu)化總結(jié) | CPU 渲染過程 | Layout Inspector 工具 | View Tree 分析 | 布局組件層級分析 ) 博客中引入了 CPU 渲染優(yōu)化 , CPU 渲染優(yōu)化的核心就是減少布局嵌套 , 布局嵌套使用 Android Studio 中的 Layout Inspector 工具進行查看 ;
CPU 渲染的優(yōu)化的核心就是減少布局的嵌套 , 推薦使用約束布局進行開發(fā) , 只有一層嵌套的布局 ;
減少布局的嵌套 , 能極大減少 UI 組件測量 , 擺放 , 生成 UI 組件的時間 , 這樣就可以減少 CPU 渲染時間 , 使整個渲染過程時間降低 , 盡可能的壓縮在 16ms 以內(nèi) , 保證 Vsync 信號到來時 , 渲染已經(jīng)完畢 , 可以在屏幕中繪制這些布局 ;
能夠被優(yōu)化的布局 : 假如父布局中只有一個子布局 , 子布局中有若干組件 , 那么可以直接將子布局的組件放在父布局中 , 將子布局這個層級干掉 , 或者將父布局層級刪除 ;
一個父布局沒有分支的布局 , 只有一個子布局 , 那么大概率可以優(yōu)化刪除父布局或子布局中的一個 , 兩者保留一個 ;
強烈推薦使用 ConstraintLayout 約束布局 , 沒有以上布局嵌套問題 ;
二、 布局渲染時間測量
如果使用 可以直接在該工具中查看布局渲染時間 , 但是該工具停止維護 , 使用老版本的 Android Studio 可以使用該工具 ;
Google 官方推薦使用 OnFrameMetricsAvailableListener 測量布局渲染時間 ;
1. FrameMetrics 使用流程
FrameMetrics 使用流程 :
① 創(chuàng)建測量線程 : 測量的過程肯定是要放在線程中執(zhí)行 , 這里創(chuàng)建 HandlerThread 線程 ; 該線程創(chuàng)建后直接啟動即可 ;
HandlerThread handlerThread = new HandlerThread("FrameMetrics"); handlerThread.start();② 創(chuàng)建 Handler : 測量時 , 需要將 Handler 對象傳遞到 addOnFrameMetricsAvailableListener 方法中 , 與 Window.OnFrameMetricsAvailableListener 監(jiān)聽器一起設置 ;
Handler handler = new Handler(handlerThread.getLooper());③ 注冊監(jiān)聽器 : 向 Activity 界面的 Window 窗口對象 , 添加監(jiān)聽器 , 同時設置 Handler , 渲染測量過程在該 Handler 所在線程執(zhí)行 ;
getWindow().addOnFrameMetricsAvailableListener(new Window.OnFrameMetricsAvailableListener(){// 省略實現(xiàn)內(nèi)容 ... }, handler);④ 回調(diào)方法中獲取渲染時間 : FrameMetrics frameMetrics 參數(shù)中封裝了 121212 個渲染性能參數(shù)屬性 , 可以自行
2. FrameMetrics 參數(shù)解析
FrameMetrics 參數(shù)解析 :
| ANIMATION_DURATION | 動畫執(zhí)行回調(diào)時間, 單位納秒 |
| COMMAND_ISSUE_DURATION | 向 GPU 發(fā)送繪制命令花費的時間, 單位納秒 |
| DRAW_DURATION | 將組件樹 ( View Hierarchy ) 轉(zhuǎn)為顯示列表 ( DisplayLists ) , 計算過程所花費的時間, 單位納秒 |
| FIRST_DRAW_FRAME | 繪制的該幀是否是第一幀, 0 是, 1 不是 ; 第一幀渲染會慢一些 , 第一幀不會引發(fā)動畫中的跳幀問題, 這些問題都會被窗口動畫隱藏 , 不必進行顯示過程中的 jank 計算 |
| INPUT_HANDLING_DURATION | 處理輸入事件花費的時間, 單位納秒 |
| INTENDED_VSYNC_TIMESTAMP | 該值是個時間戳, 表示該幀的 vsync 信號發(fā)出時間 ; 這個時間是當前幀的預期開始時間 , 如果該時間與 VSYNC_TIMESTAMP 時間戳不同 , 那么說明 UI 線程被阻塞了, 沒有及時響應 vsync 信號 |
| LAYOUT_MEASURE_DURATION | 組件樹 ( view hierarchy ) 測量 ( measure ) 和擺放 ( layout ) 花費的時間 , 單位納秒 |
| SWAP_BUFFERS_DURATION | CPU 傳遞多維向量圖形數(shù)據(jù)給 GPU 花費的時間, 單位納秒 |
| SYNC_DURATION | 顯示列表 ( DisplayLists ) 與顯示線程同步花費的時間, 單位納秒 |
| TOTAL_DURATION | CPU 渲染到傳遞到 GPU 所用的總時間, 上述所花費的有意義的時間之和 , 單位納秒 |
| UNKNOWN_DELAY_DURATION | UI 線程響應并開始處理渲染的等待時間, 一般是 0, 如果大于 0 說明出問題了 |
| VSYNC_TIMESTAMP | vsync 信號發(fā)出的時間戳, 該時刻 GPU 應該進行繪制, 間隔 16ms , 同時 CPU 開始渲染 |
3. FrameMetrics 代碼示例
代碼示例 :
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {// 渲染性能測量renderingPerformanceMeasurement();super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}/*** 渲染性能測量*/public void renderingPerformanceMeasurement(){HandlerThread handlerThread = new HandlerThread("FrameMetrics");handlerThread.start();Handler handler = new Handler(handlerThread.getLooper());// 24 版本以后的 API 才能支持該選項if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {// 整個渲染過程測量都在 HandlerThread 線程中執(zhí)行getWindow().addOnFrameMetricsAvailableListener(new Window.OnFrameMetricsAvailableListener(){@Overridepublic void onFrameMetricsAvailable(Window window,FrameMetrics frameMetrics,int dropCountSinceLastInvocation) {// 幀渲染測量完畢后, 會回調(diào)該方法// 渲染性能參數(shù)都封裝在 FrameMetrics frameMetrics 參數(shù)中// 先拷貝一份, 之后從拷貝的數(shù)據(jù)中獲取對應渲染時間參數(shù)FrameMetrics fm = new FrameMetrics(frameMetrics);// 1. 動畫執(zhí)行回調(diào)時間, 單位納秒Log.i("FrameMetrics", "ANIMATION_DURATION : " +fm.getMetric(FrameMetrics.ANIMATION_DURATION));// 2. 向 GPU 發(fā)送繪制命令花費的時間, 單位納秒Log.i("FrameMetrics", "COMMAND_ISSUE_DURATION : " +fm.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION));// 3. 將組件樹 ( View Hierarchy ) 轉(zhuǎn)為顯示列表 ( DisplayLists )// 計算過程所花費的時間, 單位納秒Log.i("FrameMetrics", "DRAW_DURATION : " +fm.getMetric(FrameMetrics.DRAW_DURATION));// 4. 繪制的該幀是否是第一幀, 0 是, 1 不是// 第一幀渲染會慢一些// 第一幀不會引發(fā)動畫中的跳幀問題, 這些問題都會被窗口動畫隱藏// 不必進行顯示過程中的 jank 計算Log.i("FrameMetrics", "FIRST_DRAW_FRAME : " +fm.getMetric(FrameMetrics.FIRST_DRAW_FRAME));// 5. 處理輸入事件花費的時間, 單位納秒Log.i("FrameMetrics", "INPUT_HANDLING_DURATION : " +fm.getMetric(FrameMetrics.INPUT_HANDLING_DURATION));// 6. 該值是個時間戳, 表示該幀的 vsync 信號發(fā)出時間// 這個時間是當前幀的預期開始時間// 如果該時間與 VSYNC_TIMESTAMP 時間戳不同// 那么說明 UI 線程被阻塞了, 沒有及時響應 vsync 信號Log.i("FrameMetrics", "INTENDED_VSYNC_TIMESTAMP : " +fm.getMetric(FrameMetrics.INTENDED_VSYNC_TIMESTAMP));// 7. 組件樹 ( view hierarchy ) 測量 ( measure ) 和擺放 ( layout ) 花費的時間// 單位 納秒Log.i("FrameMetrics", "LAYOUT_MEASURE_DURATION : " +fm.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION));// 8. CPU 傳遞多維向量圖形數(shù)據(jù)給 GPU 花費的時間, 單位納秒Log.i("FrameMetrics", "SWAP_BUFFERS_DURATION : " +fm.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION));// 9. 顯示列表 ( DisplayLists ) 與顯示線程同步花費的時間, 單位納秒Log.i("FrameMetrics", "SYNC_DURATION : " +fm.getMetric(FrameMetrics.SYNC_DURATION));// 10. CPU 渲染到傳遞到 GPU 所用的總時間, 上述所花費的有意義的時間之和// 單位納秒Log.i("FrameMetrics", "TOTAL_DURATION : " +fm.getMetric(FrameMetrics.TOTAL_DURATION));// 11. UI 線程響應并開始處理渲染的等待時間, 一般是 0, 如果大于 0 說明出問題了Log.i("FrameMetrics", "UNKNOWN_DELAY_DURATION : " +fm.getMetric(FrameMetrics.UNKNOWN_DELAY_DURATION));// 12. vsync 信號發(fā)出的時間戳, 該時刻 GPU 應該進行繪制, 間隔 16ms// 同時 CPU 開始渲染Log.i("FrameMetrics", "VSYNC_TIMESTAMP : " +fm.getMetric(FrameMetrics.VSYNC_TIMESTAMP));}}, handler);}} }打印結(jié)果 :
2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: ANIMATION_DURATION : 3854 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: COMMAND_ISSUE_DURATION : 101644281 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: DRAW_DURATION : 9981616 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: FIRST_DRAW_FRAME : 1 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: INPUT_HANDLING_DURATION : 50625 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: INTENDED_VSYNC_TIMESTAMP : 164845526994092 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: LAYOUT_MEASURE_DURATION : 154012359 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: SWAP_BUFFERS_DURATION : 1569062 2020-06-25 13:19:05.339 14179-14229/kim.hsl.rtmp I/FrameMetrics: SYNC_DURATION : 405729 2020-06-25 13:19:05.340 14179-14229/kim.hsl.rtmp I/FrameMetrics: TOTAL_DURATION : 268872818 2020-06-25 13:19:05.340 14179-14229/kim.hsl.rtmp I/FrameMetrics: UNKNOWN_DELAY_DURATION : 639354 2020-06-25 13:19:05.340 14179-14229/kim.hsl.rtmp I/FrameMetrics: VSYNC_TIMESTAMP : 164845526994092 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: ANIMATION_DURATION : 281042 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: COMMAND_ISSUE_DURATION : 1590885 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: DRAW_DURATION : 520157 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: FIRST_DRAW_FRAME : 1 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: INPUT_HANDLING_DURATION : 38646 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: INTENDED_VSYNC_TIMESTAMP : 164845543729270 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: LAYOUT_MEASURE_DURATION : 6619844 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: SWAP_BUFFERS_DURATION : 908230 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: SYNC_DURATION : 51302 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: TOTAL_DURATION : 284281446 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: UNKNOWN_DELAY_DURATION : 274133736 2020-06-25 13:19:05.371 14179-14229/kim.hsl.rtmp I/FrameMetrics: VSYNC_TIMESTAMP : 164845810395926 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: ANIMATION_DURATION : 206980 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: COMMAND_ISSUE_DURATION : 2708386 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: DRAW_DURATION : 86823 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: FIRST_DRAW_FRAME : 0 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: INPUT_HANDLING_DURATION : 51302 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: INTENDED_VSYNC_TIMESTAMP : 164845828222860 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: LAYOUT_MEASURE_DURATION : 685260 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: SWAP_BUFFERS_DURATION : 859895 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: SYNC_DURATION : 537292 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: TOTAL_DURATION : 5902960 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: UNKNOWN_DELAY_DURATION : 545772 2020-06-25 13:19:05.377 14179-14229/kim.hsl.rtmp I/FrameMetrics: VSYNC_TIMESTAMP : 164845828222860三、 布局渲染優(yōu)化總結(jié)
1 . 背景問題 : 主題的背景 , 布局的背景 , 組件的背景 , 都需要特別主題 , 每次添加背景 , 都會增加一次繪制 ;
2 . 布局嵌套 : 推薦使用約束布局 ; 如果某個容器布局只有一個子容器 , 那么可以刪除一層嵌套 ;
3 . merger 使用 : 如果是 FrameLayout 布局 , 使用 merger 可以減少一層嵌套 ;
4 . 布局包含 : 布局文件中盡量使用 include 包含其它布局 , 如標題欄 Toolbar 組件 , 這樣 GPU 中緩存一次之后 , 之后的界面再加載該 Toolbar 組件時 , 直接復用 GPU 中緩存的紋理數(shù)據(jù) ;
5 . 自定義組件裁剪 : 在 Canvas 繪制重疊時 , 使用裁剪后的畫布繪制 ;
總結(jié)
以上是生活随笔為你收集整理的【Android 性能优化】布局渲染优化 ( CPU 渲染优化 | 减少布局的嵌套 | 测量布局绘制时间 | OnFrameMetricsAvailableListener | 布局渲染优化总结 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 性能优化】布局渲染优化
- 下一篇: 【Android 内存优化】Java 内