最近我們新增了一個顯示球員詳細資訊的頁面,當我們完成第一版程式碼,開始調整細節時,我們發現了畫面滑動似乎不太順暢。
每個球員訊息彈跳視窗是靜態,外層使用了 PageView 加上一些的特效,使得畫面在滑動時會頻繁的 setState,造成了整個彈跳視窗卡頓,使用者體驗不佳,就像遊戲效能不好一樣會掉幀,嚴重一點可能會讓人不舒服。今天就來分享如何快速有效的解決這個問題吧。
首先,想要有效的解決問題,釐清問題是第一步。為了要了解我們畫面卡頓的問題根源,我們使用 Flutter 提供的 DevTools 並在實體手機上運行 Profile 模式。Profile 模式是讓 App 運行效能接近 Release 模式同時又能搜集運行資訊的一種模式。當我們執行程式並開起 DevTools 之後,可以觀察到 App 運行時,每一個 Frame 效能到底如何。
在 Dev Tools 面板中,可以發現在大多數 Frame 中,UI phase 與 Raster phase 都花了很多時間,這通常表示問題可能有很多個,但是我們今天先研究 UI phase 花過久時間的問題吧。
在面板中清楚看到了 Build 和 Layout 畫面的操作各花了多久時間,我們可以初步了解問題可能發生在哪邊,但是只有這些資訊,顯然不夠我們解決問題。
為了更深入了解問題出在哪邊,我們可以在 Dev Tools 的右上角打開 Enhance Tracing,並且勾選 Track Widget Builds,這個功能可以提供更詳細的時間,提供每一個 Widget 在每一個 Frame 中所花費的時間。
當我們啟用 Track Widget Build 之後,我們再繼續操作一下手機,讓手機繼續執行幾個 Frame,我們就能這些新產生的報告找到 Timeline Events,在 Timeline Events 中, DevTools 顯示在這個 Frame 中 Build Widget 所花費的時間,能更直觀地看到時間到底花在哪個 Widget 上了。
從火焰圖中我們可以得知,PlayerInfoGameLogView 花費的時間佔了很大一部份比例,而且每一個 Frame 都是這種狀況。這表示畫面滑動的時候,每一個 Frame 都在重新建立這個 Widget。
雖然我們滑動需要頻繁的 setState,畫面其實是不變的,數值並不會在滑動過程中有變化,應該要可以使用重複使用之前已經 Build 好的 Widget,但是顯然 Flutter 不這麼認為,而是辛苦的每一個 Frame 都Rebuild 新的 Widget。
在分析問題中,我們發現問題的癥結點,當我們滑動 PageView 時,Flutter 會重複且完整的建立每一個 Page,花費許多時間去 Build Widget,在下一篇文章中,我們會利用 dartpad 寫一個有問題的範例,並在這個範例中探討解決方案,有興趣的朋友可以先嘗試看看。