Flutter框架渲染流程與使用

2023-01-27 06:00:36
Flutter簡述
Flutter是一個UI SDK, 可以進行行動端(iOS, Android),Web端, 桌面,它是一個跨平臺解決方法。
Flutter的特點:美觀,快速,高效,開放。
美觀:Flutter內建了美麗的Material Design和 Cupertino widget, 方便開發出美麗的頁面。
快速:Flutter的UI渲染效能好,在生產環境下,Flutter將程式碼編譯成機器碼執行,並充分利用GPU的圖形加速能力,可以實現60FPS。Debug環境下則使用JIM,提升了開發效率。
Flutter引擎使用C++編寫,包含了高效的Skia 2D渲染引擎,Dart執行時和文字渲染庫。
高效:Hot reload, 在前端不是新鮮的東西,在行動端卻並不常見。
開放:Flutter是開放的,它是一個完全開源的專案。
Flutter有一統大前端的野心,並且它正在侵蝕iOS,Andriod這些原生開發。
 
跨平臺解決方案歷史
第一個階段:通過webView
iOS端有UIWebView, Android端WebView
最早出現的跨平臺框架是基於JavaScript和WebView的,代表框架有PhoneGap, Apache Cordova, Ionic等。
主要是通過Html, Css開發頁面。對於呼叫的一些本地服務如相機,藍芽等。需要通過JS進行橋接呼叫Native功能的一些功能程式碼,本身的效能和體驗並不理想。
0
 
第二階段:React Native
RN是FaceBook早先開源的JS框架React在原生行動平臺的衍生產物,目前支援iOS和安卓兩大平臺。
RN是使用JS語言,使用類似於HTML的JSX, 以及CSS來做移動開發。
使用原生自帶的UI元件實現核心的渲染引擎,從而保證了良好的渲染效能。
但是RN的本質是通過JavaScript VM呼叫原生介面,通訊相對比較低效,並且框架本身不負責渲染,而是間接通過原生進行渲染的。
 
0
第三個階段:Flutter
擁有自渲染閉環,是理想的跨平臺框架。
0
安卓原生渲染流程:
1.通過Java語言,呼叫Android框架提供的framework的API 寫出頁面佈局。
2.頁面佈局通過Android框架中framework進行翻譯,將翻譯的結果提交給Skia。
3.Skia給CPU/GPU 提供資料進行渲染。
Flutter渲染流程:
1.通過Dart語言,呼叫Flutter框架提供的framework的API 寫出頁面佈局。
2.頁面佈局通過Flutter框架中framework進行翻譯,將翻譯的結果提交給Skia。
3.Skia給CPU/GPU 提供資料進行渲染。
從上面可以發現,Flutter和安卓的渲染流程是一樣的。
0
RN的渲染流程
1.通過JS, JSX, CSS, Redux等呼叫React框架提供的API編寫頁面佈局。
2.React框架通過JavaScript VM, Bridge將JS佈局轉換成原生布局。
3.原生頁面佈局通過Android框架中framework進行翻譯,將翻譯的結果提交給Skia。
4.Skia給CPU/GPU 提供資料進行渲染。
RN渲染增加了1,2步驟的轉換,造成了效能消耗。所以RN的效能沒有Flutter的高。
Flutter不需要依賴原生控制元件,利用Skia繪圖引擎,直接通過CPU,GPU進行繪製。和安卓的原生繪製流程一樣。
而像RN框架,必須先通過橋接的方式轉成原生呼叫,然後再進行渲染。存在效能消耗。
 
0
Flutter繪製原理
1.GPU將VSync繪製訊號同步到UI執行緒。
2.UI執行緒用Dart將Flutter程式碼構建圖層樹Layer Tree。
3.圖層樹Layer Tree在GPU執行緒內的Compositor進行合成。
4.Compositor合成的結果交給Skia進行渲染。
5.Skia渲染的結果通過OpenGL或Vulkan交給GPU進行影象繪製。
6.GPU繪製完成後把影象放入到雙快取的Back Buffer,等下一個VSync來時,系統從Back Buffer複製到Frame Buffer併產生新一輪的CPU/GPU繪製過程。
上面是Flutter完整的渲染閉環,這是它和ReactNative的本質區別。
ReactNative是沒有自己的渲染閉環的,它是通過JS VM呼叫系統元件,使用的iOS、安卓的原生渲染。
 
影象的顯示原理
在螢幕上看到的任何東西都是影象,包括圖片,GIF影象,視訊。
當圖片連續播放的頻率超過16幀時,人眼就會感到非常流暢。當小於16幀時就會感到卡頓。
影格率(fps): Frames Per Second, 每秒生成多少幀影象。
重新整理率:顯示屏的頻率,比如iPhone的螢幕每秒重新整理60下,表示為60Hz。
 
影格率和重新整理率的關係
GPU/CPU生成影象(每秒生成多少張圖片是影格率)放入Buffer中,螢幕從Buffer中取影象,重新整理(每秒重新整理多少次是重新整理率)後顯示。這是一個生產者-消費者模型。
很容易產生的問題是:GPU在新的一幀圖片寫入一半時,螢幕從中取出影象展示。此時會取出一張上半部分和下半部分不一致的影象。
顯示出來的影象出現上半部分和下半部分明顯偏差的現象,我們稱為「tearing」(撕裂)。
 
0
 
雙重快取和VSync
為了解決「tearing」(撕裂)問題,就出現了雙重快取和VSync。
0
兩個快取分別為Back Buffer和Frame Buffer, GPU向Back Buffer寫資料,螢幕從Frame Buffer讀資料。VSync訊號負責從Back Buffer到Frame Buffer的複製操作。底層的複製是用「指標交換」的假複製,效率高。
工作流程:
某個時間點,一個螢幕重新整理週期完成,VSync訊號產生,先完成複製操作,然後通知CPU/GPU繪製一幀影象。
複製操作完成後,開始下一個重新整理週期,將剛複製到Frame Buffer的資料顯示到螢幕上。
在這種模型下,只有當VSync訊號產生時,CPU/GPU才開始繪製。
 
雙重快取存在的問題
雙重快取的缺陷:當CPU/GPU繪製一幀的時間過長(比如超過16ms一個重新整理週期時),會產生Jank(畫面停頓,甚至空白),VSync訊號是在一個重新整理週期結束後產生的。
0
三重快取
在每次VSync訊號來時,多快取一個Buffer作為備用。
 
渲染引擎skia
skia是Flutter向GPU提供資料的途徑。
skia是一個C++編寫的開源形庫。
目前skia是安卓的官方影象引擎,所以Flutter的安卓應用SDK無需內嵌Skia引擎。
而對於iOS來說,Skia引擎需要嵌入到iOS SDK中,替代了iOS必源的Core Graphics / Core Animation / Core Text, 所以Flutter iOS SDK打包的APP包體積比安卓的大。
因為底層渲染能力的統一,上層開發介面和功能也就是統一的。skia保證了同一套程式碼呼叫在iOS和Android上渲染效果是完全一致的。
0
 
建立一個Flutter專案
命令列建立flutter專案
專案名稱不支援中文,不支援大小,可以使用_進行連結
flutter create flutter_demo

VSCode環境安裝外掛

Flutter, 
Dart, 
Code Runner

Flutter專案啟動方式有三種:冷啟動,熱啟動(hotReload),熱過載(hotRestart)。

冷啟動:程式碼和Flutter框架都沒有載入和執行,需要從磁碟載入到記憶體進行執行,過程比較久,通常需要1min-5min。
熱啟動 hotReload:重新執行widget中的build方法。
熱過載 hotRestart:重新執行APP的main入口函數,重新執行一遍程式。
Material是什麼
Material是google推出的套設計風格,或者叫設計規範。裡面包含了很多設計規範,如:顏色,文字排版,響應動畫與過度等。
在Flutter中高度整合Material風格的Widget。
Widget是什麼
Flutter中萬物皆Widget。在iOS或安卓中,我們的頁面有很多種類:應用Application, 檢視控制器ViewController, 檢視View, Button按鈕等。
但是在Flutter中,這些東西都是不同的Widget。
而在我們的APP中,所有能看到的內容幾乎都是Widget, 甚至內邊距設定都是使用Padding的Widget來做的。
Flutter專案的入口
Flutter專案的入口是lib/main.dart下的main函數。
在main函數內部,需要執行函數runApp(widget app);, runApp函數是Flutter提供的一個全域性APP執行函數入口。
runApp(widget app);

最簡單的Flutter專案Demo

import 'package:flutter/material.dart';
void main(List<String> args) {
  runApp(Text('Hello Flutter'));
}

此時報錯:沒有找到排版方向

原因是Flutter是面向全世界很多國家的,有的國家排版是從左往右,有的是從右往左。所以需要使用者自己設定。
0
這裡需要明確指定排版方向:TextDirection.ltr。
在Flutter中萬物都是widget,所以如果要設定center, padding ,margin等都是寫對應的widget。
修改報錯後,如下:
void main(List<String> args) {
  runApp(
    Center(
        child:Text('Hello Flutter', 
        textDirection: TextDirection.ltr,
        style: TextStyle(
          color: Colors.red,
          fontSize: 30,
        ),
      )
    )
  );
}
MaterialApp:採用了Google的Material設計設計規範的Widget,裡面預設設定了文字排版方向等設定。
Scaffold:腳手架Widget, 用於快速搭建頁面機構,提供了不同位置的命名可選引數。
import 'package:flutter/material.dart';
/*
  MaterialApp:採用了Google的Material設計設計規範的Widget,裡面預設設定了文字排版方向等設定。
  Scaffold:腳手架Widget, 用於快速搭建頁面機構,提供了不同位置的命名可選引數。
  debugShowCheckedModeBanner: 去掉右上角的debug條
*/
void main(List<String> args) {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home:Scaffold(
          appBar: AppBar(title: Text('第一個Flutter程式'),),
          body: Center(
            child:Text('Hello Flutter', 
            style: TextStyle(
              color: Colors.blue,
              fontSize: 30,
            ),
          )
        ),
      )
    )
  );
}
StatelessWidget
StatelessWidget:在執行過程中,元件內容是固定的,沒有狀態修改的。
Widget中沒有資料改動,只使用固定的資料展示或者只使用從父Widget繼承過來的Widget時,使用StatelessWidget。
繼承StatelessWidget產生的子Widget需要重新build方法,並在build方法裡返回要展示的widget。
build方法是無法主動呼叫的。只能在資料改動時系統來呼叫。
build方法呼叫時機:
1.當StatelessWidget第一次插入到Widget樹時(第一次被建立時)
2.當父Widget發生改變時,子Widget需要被重新構建
3.當依賴的InheritedWidget的資料傳送改變時,被重新呼叫。
StatefulWidget
在執行過程中,狀態(data)會產生改變,導致頁面展示內容發生改變。
如果想使用StatefulWidget建立有狀態變化的Widget, 需要一個State物件一起來實現。
StatefulWidget內部無法寫var屬性, 因為它繼承自Widget,Widget是被@immutable修飾,不可改變。所以它的狀態改變要在別的類(State)中實現。
State:在建立的State子類中新增var屬性,並將其與Widget狀態繫結,當有新的狀態改變時,需要呼叫setState((){})進行更新狀態
Flutter的狀態更新和React的機制一樣,需要呼叫setState通知框架進行頁面更新。
與Vue不同的是Vue範例使用的是雙向繫結,內部對屬性做了監聽,無需手動呼叫setState進行通知更新。
/*
  StatefulWidget內部無法寫var屬性, 因為它繼承自Widget,Widget是被@immutable修飾,不可改變。所以它的狀態改變要在別的類(State)中實現。
  State:在建立的State子類中新增var屬性,並將其與Widget狀態繫結,當有新的狀態改變時,需要呼叫setState((){})進行更新狀態
  Flutter的狀態更新和React的機制一樣,需要呼叫setState通知框架進行頁面更新。
  與Vue不同的是Vue範例使用的是雙向繫結,內部對屬性做了監聽,無需手動呼叫setState進行通知更新。
*/
class PageContent extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return PageContentState();
  }
}
class PageContentState extends State<PageContent> {
  var flag = true;
  @override
  Widget build(BuildContext context) {
    return Center(
        child:Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children:[
            Checkbox(
              value: flag, 
              onChanged: (value) {
                setState(() {
                  flag = value;
                });
              },),
            Text('Hello World')
          ]
        )
    );
  }
  
}
宣告式程式設計和指令式程式設計
iOS,安卓使用的是指令式程式設計,平時涉及到的是屬性,成員變數。
vue, react, flutter是宣告式程式設計,平時涉及到的是State狀態,平時開發是隻需要管好狀態,顯示內容有框架自動幫忙更新上去。
Flutter修飾符
@immutable修飾符表示Widget類是不可改變的,裡面的屬性都是final型別的。
required可選變數 必傳修飾符, 用於表示命名可選變數為必傳引數。
@immutable
abstract class Widget extends DiagnosticableTree

  const Checkbox({
    Key? key,
    required this.value,
    this.tristate = false,
    required this.onChanged,