<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                在上一篇文章中,我與你分享了調試 Flutter 代碼的 3 種基本方式,即輸出日志、斷點調試與布局調試。 通過可定制打印行為的 debugPrint 函數,我們可以實現生產環境與開發環境不同的日志輸出行為,從而保證在開發期打印的調試信息不會被發布至線上;借助于 IDE(Android Studio)所提供的斷點調試選項,我們可以不斷調整代碼執行步長和代碼暫停條件,收斂問題發生范圍,直至找到問題根源;而如果我們想找出代碼中的布局渲染類 Bug,則可以通過 Debug Painting 和 Flutter Inspector 提供的輔助線和視圖可視化信息,來更為精準地定位視覺問題。 除了代碼邏輯 Bug 和視覺異常這些功能層面的問題之外,移動應用另一類常見的問題是性能問題,比如滑動操作不流暢、頁面出現卡頓丟幀現象等。這些問題雖然不至于讓移動應用完全不可用,但也很容易引起用戶反感,從而對應用質量產生質疑,甚至失去耐心。 那么,如果應用渲染并不流暢,出現了性能問題,我們該如何檢測,又該從哪里著手處理呢? 在 Flutter 中,性能問題可以分為 GPU 線程問題和 UI 線程(CPU)問題兩類。這些問題的確認都需要先通過性能圖層進行初步分析,而一旦確認問題存在,接下來就需要利用 Flutter 提供的各類分析工具來定位問題了。 所以在今天這篇文章中,我會與你一起學習分析 Flutter 應用性能問題的基本思路和工具,以及常見的優化辦法。 ## 如何使用性能圖層? 要解決問題,我們首先得了解如何去度量問題,性能分析也不例外。Flutter 提供了度量性能問題的工具和手段,來幫助我們快速定位代碼中的性能問題,而性能圖層就是幫助我們確認問題影響范圍的利器。 **為了使用性能圖層,我們首先需要以分析(Profile)模式啟動應用。**與調試代碼可以通過模擬器在調試模式下找到代碼邏輯 Bug 不同,性能問題需要在發布模式下使用真機進行檢測。 這是因為,相比發布模式而言,調試模式增加了很多額外的檢查(比如斷言),這些檢查可能會耗費很多資源;更重要的是,調試模式使用 JIT 模式運行應用,代碼執行效率較低。這就使得調試模式運行的應用,無法真實反映出它的性能問題。 而另一方面,模擬器使用的指令集為 x86,而真機使用的指令集是 ARM。這兩種方式的二進制代碼執行行為完全不同,因此模擬器與真機的性能差異較大:一些 x86 指令集擅長的操作模擬器會比真機快,而另一些操作則會比真機慢。這也使得我們無法使用模擬器來評估真機才能出現的性能問題。 **為了調試性能問題,我們需要在發布模式的基礎之上,為分析工具提供少量必要的應用追蹤信息,這就是分析模式**。除了一些調試性能問題必須的追蹤方法之外,Flutter 應用的分析模式和發布模式的編譯和運行是類似的,只是啟動參數變成了 profile 而已:我們既可以在 Android Studio 中通過菜單欄點擊 Run->Profile ‘main.dart’ 選項啟動應用,也可以通過命令行參數 flutter run --profile 運行 Flutter 應用。 ## 分析渲染問題 在完成了應用啟動之后,接下來我們就可以利用 Flutter 提供的渲染問題分析工具,即性能圖層(Performance Overlay),來分析渲染問題了。 性能圖層會在當前應用的最上層,以 Flutter 引擎自繪的方式展示 GPU 與 UI 線程的執行圖表,而其中每一張圖表都代表當前線程最近 300 幀的表現,如果 UI 產生了卡頓(跳幀),這些圖表可以幫助我們分析并找到原因。 下圖演示了性能圖層的展現樣式。其中,GPU 線程的性能情況在上面,UI 線程的情況顯示在下面,藍色垂直的線條表示已執行的正常幀,綠色的線條代表的是當前幀: :-: ![](https://img.kancloud.cn/91/eb/91eb7eff1c5c2326f2904044b950fe8e_1125x522.png) 圖 1 性能圖層 為了保持 60Hz 的刷新頻率,GPU 線程與 UI 線程中執行每一幀耗費的時間都應該小于 16ms(1/60 秒)。在這其中有一幀處理時間過長,就會導致界面卡頓,圖表中就會展示出一個紅色豎條。下圖演示了應用出現渲染和繪制耗時的情況下,性能圖層的展示樣式: :-: ![](https://img.kancloud.cn/bf/b5/bfb5ec2b5dcf7a2c20b7ec3b946854c1_1125x561.jpg) 圖 2 渲染和繪制耗時異常 如果紅色豎條出現在 GPU 線程圖表,意味著渲染的圖形太復雜,導致無法快速渲染;而如果是出現在了 UI 線程圖表,則表示 Dart 代碼消耗了大量資源,需要優化代碼執行時間。 接下來,我們就先看看 GPU 問題定位吧。 ## GPU 問題定位 GPU 問題主要集中在底層渲染耗時上。有時候 Widget 樹雖然構造起來容易,但在 GPU 線程下的渲染卻很耗時。涉及 Widget 裁剪、蒙層這類多視圖疊加渲染,或是由于缺少緩存導致靜態圖像的反復繪制,都會明顯拖慢 GPU 的渲染速度。 我們可以使用性能圖層提供的兩項參數,即檢查多視圖疊加的視圖渲染開關 checkerboardOffscreenLayers,和檢查緩存的圖像開關 checkerboardRasterCacheImages,來檢查這兩種情況。 ### checkerboardOffscreenLayers 多視圖疊加通常會用到 Canvas 里的 savaLayer 方法,這個方法在實現一些特定的效果(比如半透明)時非常有用,但由于其底層實現會在 GPU 渲染上涉及多圖層的反復繪制,因此會帶來較大的性能問題。 對于 saveLayer 方法使用情況的檢查,我們只要在 MaterialApp 的初始化方法中,將 checkerboardOffscreenLayers 開關設置為 true,分析工具就會自動幫我們檢測多視圖疊加的情況了:使用了 saveLayer 的 Widget 會自動顯示為棋盤格式,并隨著頁面刷新而閃爍。 不過,saveLayer 是一個較為底層的繪制方法,因此我們一般不會直接使用它,而是會通過一些功能性 Widget,在涉及需要剪切或半透明蒙層的場景中間接地使用。所以一旦遇到這種情況,我們需要思考一下是否一定要這么做,能不能通過其他方式來實現呢。 比如下面的例子中,我們使用 CupertinoPageScaffold 與 CupertinoNavigationBar 實現了一個動態模糊的效果。 ~~~ CupertinoPageScaffold( navigationBar: CupertinoNavigationBar(),// 動態模糊導航欄 child: ListView.builder( itemCount: 100, // 為列表創建 100 個不同顏色的 RowItem itemBuilder: (context, index)=>TabRowItem( index: index, lastItem: index == 100 - 1, color: colorItems[index],// 設置不同的顏色 colorName: colorNameItems[index], ) ) ); ~~~ :-: ![](https://img.kancloud.cn/19/88/1988216282ce60059462da7b9a2eda11_300x650.gif) 圖 3 動態模糊效果 由于視圖滾動過程中頻繁涉及視圖蒙層效果的更新,因此 checkerboardOffscreenLayers 檢測圖層也感受到了對 GPU 的渲染壓力,頻繁的刷新閃爍。 :-: ![](https://img.kancloud.cn/55/85/5585780bfd5dcae6a3498b7119a558db_300x650.gif) 圖 4 檢測 saveLayer 使用 如果我們沒有對動態模糊效果的特殊需求,則可以使用不帶模糊效果的 Scaffold 和白色的 AppBar 實現同樣的產品功能,來解決這個性能問題: ~~~ Scaffold( // 使用普通的白色 AppBar appBar: AppBar(title: Text('Home', style: TextStyle(color:Colors.black),),backgroundColor: Colors.white), body: ListView.builder( itemCount: 100, // 為列表創建 100 個不同顏色的 RowItem itemBuilder: (context, index)=>TabRowItem( index: index, lastItem: index == 100 - 1, color: colorItems[index],// 設置不同的顏色 colorName: colorNameItems[index], ) ), ); ~~~ 運行一下代碼,可以看到,在去掉了動態模糊效果之后,GPU 的渲染壓力得到了緩解,checkerboardOffscreenLayers 檢測圖層也不再頻繁閃爍了。 :-: ![](https://img.kancloud.cn/8c/93/8c937383845cb306dade4ab9c374ed46_300x650.gif) 圖 5 去掉動態模糊效果 ### checkerboardRasterCacheImages 從資源的角度看,另一類非常消耗性能的操作是,渲染圖像。這是因為圖像的渲染涉及 I/O、GPU 存儲,以及不同通道的數據格式轉換,因此渲染過程的構建需要消耗大量資源。為了緩解 GPU 的壓力,Flutter 提供了多層次的緩存快照,這樣 Widget 重建時就無需重新繪制靜態圖像了。 與檢查多視圖疊加渲染的 checkerboardOffscreenLayers 參數類似,Flutter 也提供了檢查緩存圖像的開關 checkerboardRasterCacheImages,來檢測在界面重繪時頻繁閃爍的圖像(即沒有靜態緩存)。 我們可以把需要靜態緩存的圖像加到 RepaintBoundary 中,RepaintBoundary 可以確定 Widget 樹的重繪邊界,如果圖像足夠復雜,Flutter 引擎會自動將其緩存,避免重復刷新。當然,因為緩存資源有限,如果引擎認為圖像不夠復雜,也可能會忽略 RepaintBoundary。 如下代碼展示了通過 RepaintBoundary,將一個靜態復合 Widget 加入緩存的具體用法。可以看到,RepaintBoundary 在使用上與普通 Widget 并無區別: ~~~ RepaintBoundary(// 設置靜態緩存圖像 child: Center( child: Container( color: Colors.black, height: 10.0, width: 10.0, ), )); ~~~ ## UI 線程問題定位 如果說 GPU 線程問題定位的是渲染引擎底層渲染異常,那么 UI 線程問題發現的則是應用的性能瓶頸。比如在視圖構建時,在 build 方法中使用了一些復雜的運算,或是在主 Isolate 中進行了同步的 I/O 操作。這些問題,都會明顯增加 CPU 的處理時間,拖慢應用的響應速度。 這時,我們可以使用 Flutter 提供的 Performance 工具,來記錄應用的執行軌跡。Performance 是一個強大的性能分析工具,能夠以時間軸的方式展示 CPU 的調用棧和執行時間,去檢查代碼中可疑的方法調用。 在點擊了 Android Studio 底部工具欄中的“Open DevTools”按鈕之后,系統會自動打開 Dart DevTools 的網頁,將頂部的 tab 切換到 Performance 后,我們就可以開始分析代碼中的性能問題了。 :-: ![](https://img.kancloud.cn/11/d8/11d8392713ed0ce8615eeb360662653a_478x156.png) 圖 6 打開 Performance 工具 :-: ![](https://img.kancloud.cn/86/7c/867cbb2e87f5f18df0e1ac1a114bf687_1978x1560.png) 圖 7 Performance 主界面 接下來,我們通過一個 ListView 中計算 MD5 的例子,來演示 Performance 的具體分析過程。 考慮到在 build 函數中進行渲染信息的組裝是一個常見的操作,為了演示這個知識點,我們故意放大了計算 MD5 的耗時,循環迭代計算了 1 萬次: ~~~ class MyHomePage extends StatelessWidget { MyHomePage({Key key}) : super(key: key); String generateMd5(String data) { //MD5 固定算法 var content = new Utf8Encoder().convert(data); var digest = md5.convert(content); return hex.encode(digest.bytes); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('demo')), body: ListView.builder( itemCount: 30,// 列表元素個數 itemBuilder: (context, index) { // 反復迭代計算 MD5 String str = '1234567890abcdefghijklmnopqrstuvwxyz'; for(int i = 0;i<10000;i++) { str = generateMd5(str); } return ListTile(title: Text("Index : $index"), subtitle: Text(str)); }// 列表項創建方法 ), ); } } ~~~ 與性能圖層能夠自動記錄應用執行情況不同,使用 Performance 來分析代碼執行軌跡,我們需要手動點擊“Record”按鈕去主動觸發,在完成信息的抽樣采集后,點擊“Stop”按鈕結束錄制。這時,我們就可以得到在這期間應用的執行情況了。 Performance 記錄的應用執行情況叫做 CPU 幀圖,又被稱為火焰圖。火焰圖是基于記錄代碼執行結果所產生的圖片,用來展示 CPU 的調用棧,表示的是 CPU 的繁忙程度。 其中,y 軸表示調用棧,其每一層都是一個函數。調用棧越深,火焰就越高,底部就是正在執行的函數,上方都是它的父函數;x 軸表示單位時間,一個函數在 x 軸占據的寬度越寬,就表示它被采樣到的次數越多,即執行時間越長。 所以,我們要檢測 CPU 耗時問題,皆可以查看火焰圖底部的哪個函數占據的寬度最大。只要有“平頂”,就表示該函數可能存在性能問題。比如,我們這個案例的火焰圖如下所示: :-: ![](https://img.kancloud.cn/12/62/1262ed91986a5e09646d56e5a2db3302_2252x1218.png) 圖 8 CPU 幀圖 / 火焰圖 可以看到,\_MyHomePage.generateMd5 函數的執行時間最長,幾乎占滿了整個火焰圖的寬,而這也與代碼中存在的問題是一致的。 在找到了問題之后,我們就可以使用 Isolate(或 compute)將這些耗時的操作挪到并發主 Isolate 之外去完成了。 ## 總結 好了,今天的分享就到這里。我們總結一下今天的主要內容吧。 在 Flutter 中,性能分析過程可以分為 GPU 線程問題定位和 UI 線程(CPU)問題定位,而它們都需要在真機上以分析模式(Profile)啟動應用,并通過性能圖層分析大致的渲染問題范圍。一旦確認問題存在,接下來就需要利用 Flutter 所提供的分析工具來定位問題原因了。 關于 GPU 線程渲染問題,我們可以重點檢查應用中是否存在多視圖疊加渲染,或是靜態圖像反復刷新的現象。而 UI 線程渲染問題,我們則是通過 Performance 工具記錄的火焰圖(CPU 幀圖),分析代碼耗時,找出應用執行瓶頸。 通常來說,由于 Flutter 采用基于聲明式的 UI 設計理念,以數據驅動渲染,并采用 Widget->Element->RenderObject 三層結構,屏蔽了無謂的界面刷新,能夠保證絕大多數情況下我們構建的應用都是高性能的,所以在使用分析工具檢測出性能問題之后,通常我們并不需要做太多的細節優化工作,只需要在改造過程中避開一些常見的坑,就可以獲得優異的性能。如: * 控制 build 方法耗時,將 Widget 拆小,避免直接返回一個巨大的 Widget,這樣 Widget 會享有更細粒度的重建和復用; * 盡量不要為 Widget 設置半透明效果,而是考慮用圖片的形式代替,這樣被遮擋的 Widget 部分區域就不需要繪制了; * 對列表采用懶加載而不是直接一次性創建所有的子 Widget,這樣視圖的初始化時間就減少了。 ## 思考題 最后,我給你留下一道思考題吧。 請你改造 ListView 計算 MD5 的示例,在保證原有功能的情況下,使用并發 Isolate(或 compute)完成 MD5 的計算。提示:計算過程可以使用 CircularProgressIndicator 來展示加載動畫。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看