一文了解Flutter架構(gòu)

前言

本文總結(jié)Flutter架構(gòu)概覽,包含其設(shè)計(jì)層面的核心原則以及概念。

Flutter是一個(gè)跨平臺(tái)的UI工具集,它允許在各種操作系統(tǒng)上復(fù)用相同的代碼,同時(shí)應(yīng)用程序直接與底層平臺(tái)交互,避免了不同平臺(tái)視圖的差異,同時(shí)也讓開(kāi)發(fā)者能夠在不同平臺(tái)上都能交付擁有原生體驗(yàn)的高性能應(yīng)用。

開(kāi)發(fā)階段,F(xiàn)Lutter應(yīng)用會(huì)在一個(gè)VM(程序虛擬機(jī))中運(yùn)行,從而可以保留狀態(tài)且無(wú)需重新編譯的情況下,熱重載相關(guān)的更新。對(duì)于發(fā)行版(release),F(xiàn)lutter程序會(huì)直接編譯錯(cuò)機(jī)器碼,或者針對(duì)Web平臺(tái)的JavaScript。

概覽分為以下幾個(gè)部分:

  1. 分層模型:Flutter的構(gòu)成要素
  2. 響應(yīng)式用戶(hù)界面:Flutter用戶(hù)界面開(kāi)發(fā)的核心概念
  3. widgets介紹:構(gòu)建Flutter用戶(hù)界面的基石
  4. 渲染過(guò)程:Flutter如何將界面布局轉(zhuǎn)化為像素
  5. 平臺(tái)嵌入層的概念:讓Flutter應(yīng)用可以再移動(dòng)端以及桌面端操作系統(tǒng)執(zhí)行的代碼

架構(gòu)層

Flutter被設(shè)計(jì)為一個(gè)可擴(kuò)展的分層系統(tǒng)。它可以被看做是各個(gè)獨(dú)立的組件系列合集,上層的組件各自依賴(lài)下層的組件。組件無(wú)法越權(quán)訪(fǎng)問(wèn)底層的內(nèi)容,并且框架層的各個(gè)部分都是可選且可替代。

image

對(duì)于底層操作系統(tǒng)而言,F(xiàn)lutter應(yīng)用程序的包裝方式與其他原生應(yīng)用相同。在每一個(gè)平臺(tái)上,都回去包含一個(gè)特定的嵌入層,從而提供一個(gè)程序入口,程序由此可以與底層操作系統(tǒng)進(jìn)行協(xié)調(diào),訪(fǎng)問(wèn)諸如Surface渲染,輔助功能和輸入等等服務(wù),并且管理時(shí)間循環(huán)隊(duì)列。該嵌入層采用了適合當(dāng)前平臺(tái)語(yǔ)言編寫(xiě),例如Android使用的是Java/C++,IOS和MacOSSierra使用的是OC和OC++,Windows和Linux使用的是C++,F(xiàn)lutter代碼可以通過(guò)嵌入層,以模塊方式集成到現(xiàn)有的應(yīng)用中,也可以作為應(yīng)用的主體。Flutter本身包含了各個(gè)常見(jiàn)平臺(tái)的嵌入層,同時(shí)也存在一些其他的嵌入層。

Flutter引擎毫無(wú)疑問(wèn)是Flutter的核心,它主要是C++編寫(xiě),并提供了Flutter應(yīng)用所需要的原語(yǔ)。當(dāng)需要繪制新的一幀的內(nèi)容時(shí),引擎將負(fù)責(zé)對(duì)需要合成的場(chǎng)景進(jìn)行柵格化。它提供了Flutter核心API的底層實(shí)現(xiàn),包括圖形(通過(guò)Skia)、文本布局、文件以及網(wǎng)絡(luò)IO、輔助功能支持、插件架構(gòu)和Dart運(yùn)行環(huán)境以及編譯環(huán)境的工具鏈。

引擎將C++ 代碼包裝成Dart代碼,通過(guò)dart:ui暴露給Flutter框架層。該庫(kù)暴露了最底層的原語(yǔ),包括用于驅(qū)動(dòng)圖形輸入、圖形、和文本渲染的子系統(tǒng)的類(lèi)。

通常,開(kāi)發(fā)者可以通過(guò)Flutter Framework與Flutter進(jìn)行交互,該Framework提供了以Dart語(yǔ)音編寫(xiě)的現(xiàn)代響應(yīng)式框架。它包括由一系列層組成的一組豐富的平臺(tái),布局和基礎(chǔ)庫(kù)。從下層到上層,依次有:

  • 基礎(chǔ)的 foundational 類(lèi)及一些基層之上的構(gòu)建塊服務(wù),如 animation、 paintinggestures,它們可以提供上層常用的抽象。
  • 渲染層 用于提供操作布局的抽象。有了渲染層,你可以構(gòu)建一棵可渲染對(duì)象的樹(shù)。在你動(dòng)態(tài)更新這些對(duì)象時(shí),渲染樹(shù)也會(huì)自動(dòng)根據(jù)你的變更來(lái)更新布局。
  • widget 層 是一種組合的抽象。每一個(gè)渲染層中的渲染對(duì)象,都在 widgets 層中有一個(gè)對(duì)應(yīng)的類(lèi)。此外,widgets 層讓你可以自由組合你需要復(fù)用的各種類(lèi)。響應(yīng)式編程模型就在該層級(jí)中被引入。
  • MaterialCupertino 庫(kù)提供了全面的 widgets 層的原語(yǔ)組合,這套組合分別實(shí)現(xiàn)了 Material 和 iOS 設(shè)計(jì)規(guī)范。

Flutter 框架相對(duì)較小,因?yàn)橐恍╅_(kāi)發(fā)者可能會(huì)使用到的更高層級(jí)的功能已經(jīng)被拆分到不同的軟件包中,使用 Dart 和 Flutter 的核心庫(kù)實(shí)現(xiàn),其中包括平臺(tái)插件,例如 camerawebview;與平臺(tái)無(wú)關(guān)的功能,例如 charactershttpanimations。還有一些軟件包來(lái)自于更為寬泛的生態(tài)系統(tǒng)中,例如 應(yīng)用內(nèi)支付、 Apple 認(rèn)證Lottie 動(dòng)畫(huà)。

該概覽的其余部分將從 UI 開(kāi)發(fā)的響應(yīng)式范例開(kāi)始,瀏覽各個(gè)構(gòu)建層。而后,我們會(huì)講述 widgets 如何被組織,并轉(zhuǎn)換成應(yīng)用程序的渲染對(duì)象。同時(shí)我們也會(huì)講述 Flutter 如何在平臺(tái)層面與其他代碼進(jìn)行交互,最終,我們會(huì)對(duì)目前 Flutter 對(duì)于 Web 平臺(tái)的支持與其他平臺(tái)的異同做一個(gè)總結(jié)。

響應(yīng)式用戶(hù)界面

Flutter 是一個(gè)響應(yīng)式的且偽聲明式的 UI 框架,開(kāi)發(fā)者負(fù)責(zé)提供應(yīng)用狀態(tài)與界面狀態(tài)之間的映射,框架則在運(yùn)行時(shí)將應(yīng)用狀態(tài)的更改更新到界面上。在大部分傳統(tǒng)的 UI 框架中,界面的初始狀態(tài)通常會(huì)被一次性定義,然后,在運(yùn)行時(shí)根據(jù)用戶(hù)代碼分別響應(yīng)事件進(jìn)行更新。

Flutter 與其他響應(yīng)式框架類(lèi)似,采用了顯式剝離基礎(chǔ)狀態(tài)和用戶(hù)界面的方式,來(lái)解決這一問(wèn)題。你可以通過(guò) React 風(fēng)格的 API,創(chuàng)建 UI 的描述,讓框架負(fù)責(zé)通過(guò)配置優(yōu)雅地創(chuàng)建和更新用戶(hù)界面。

在 Flutter 里,widgets(類(lèi)似于 React 中的組件)是用來(lái)配置對(duì)象樹(shù)的不可變類(lèi)。這些 widgets 會(huì)管理單獨(dú)的布局對(duì)象樹(shù),接著參與管理合成的布局對(duì)象樹(shù)。 Flutter 的核心就是一套高效的遍歷樹(shù)的變動(dòng)的機(jī)制,它會(huì)將對(duì)象樹(shù)轉(zhuǎn)換為更底層的對(duì)象樹(shù),并在樹(shù)與樹(shù)之間傳遞更改。

build() 是將狀態(tài)轉(zhuǎn)化為 UI 的方法,widget 通過(guò)重寫(xiě)該方法來(lái)聲明 UI 的構(gòu)造。build() 方法在框架需要時(shí)都可以被調(diào)用(每個(gè)渲染幀可能會(huì)調(diào)用一次),從設(shè)計(jì)角度來(lái)看,它應(yīng)當(dāng)能夠快速執(zhí)行且沒(méi)有額外影響的。這樣的實(shí)現(xiàn)設(shè)計(jì)依賴(lài)于語(yǔ)言的運(yùn)行時(shí)特征(特別是對(duì)象的快速實(shí)例化和清除)。幸運(yùn)的是,Dart 非常適合這份工作。

Widgets

應(yīng)用額如前所述,F(xiàn)Lutter強(qiáng)調(diào)以widgets作為組成單位。Widgets是構(gòu)建Flutter應(yīng)用界面的基礎(chǔ)塊,每個(gè)widget都是一部分不可變的UI聲明。

Widgets通過(guò)布局組合形成一種層次結(jié)構(gòu)關(guān)系。每個(gè)Widget都是嵌套在其父級(jí)的內(nèi)部,并可以通過(guò)父級(jí)接收上下文。從根布局(托管Flutter應(yīng)用的容器,通常是MaterialApp或者CupertinoApp)開(kāi)始,自上而下就是這樣的結(jié)構(gòu),如下面實(shí)例;

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('My Home Page'),
        ),
        body: Center(
          child: Builder(
            builder: (BuildContext context) {
              return Column(
                children: [
                  const Text('Hello World'),
                  const SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: () {
                      print('Click!');
                    },
                    child: const Text('A button'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

在上面的代碼中,所有的實(shí)例化的類(lèi)都是widgets。

應(yīng)用會(huì)根據(jù)事件交互,通知框架替換層級(jí)中的舊的widget為新的widget,最后框架會(huì)比較新舊widgets,高效的更新用戶(hù)界面。

Flutter擁有其自己的UI控制實(shí)現(xiàn),而不是由系統(tǒng)自帶的方法進(jìn)行托管:例如,IOS的Switch控件和Android的選擇控件都有一個(gè)Dart實(shí)現(xiàn)。

這樣的實(shí)現(xiàn)有幾個(gè)優(yōu)勢(shì):

  • 提供了誣陷的擴(kuò)展性。
  • Flutter可以直接合成所有的場(chǎng)景,而無(wú)需在Flutter與原生平臺(tái)之間來(lái)回的切換,從而避免了明顯的性能瓶頸。
  • 將應(yīng)用的行為與操作系統(tǒng)的依賴(lài)解耦。

組成

Widget通常由更小的且用途單一的widgets組合而成,提供更強(qiáng)大的功能。

在設(shè)計(jì)的時(shí)候,相關(guān)的概念設(shè)計(jì)已盡可能地少量存在,而通過(guò)大量的內(nèi)容進(jìn)行填充。eg,F(xiàn)lutter在widgets層中使用了相同的概念(一個(gè)Widget)來(lái)表示屏幕上的繪制、布局(位置和大?。⒂脩?hù)交互、狀態(tài)管理、主題、動(dòng)畫(huà)以及導(dǎo)航。在動(dòng)畫(huà)層,Animation和Tween這對(duì)概念組合,涵蓋了大部分的設(shè)計(jì)空間。在渲染層,RenderObject用來(lái)描述布局、繪制、觸摸判斷以及可訪(fǎng)問(wèn)性。在這些場(chǎng)景中,最終對(duì)于包含的內(nèi)容都很多:有數(shù)百個(gè)widgets和Render objects,以及數(shù)十種的動(dòng)畫(huà)和補(bǔ)間類(lèi)型。

類(lèi)的層次結(jié)構(gòu)是有意的淺而廣,以最大限度的增加可能的組合數(shù)量,重點(diǎn)放在小的,可組合的widget上,確保每個(gè)widget都能橫好的完成一件事情。核心功能均被抽象,甚至像編劇和對(duì)齊這樣的基礎(chǔ)功能,都被實(shí)現(xiàn)為單獨(dú)的組件,而不是內(nèi)置于核心中。(這樣的實(shí)現(xiàn)也與傳統(tǒng)的API形成了對(duì)比,類(lèi)似于邊距這樣的功能通常都內(nèi)置在了每個(gè)組件的公共核心內(nèi),F(xiàn)lutter中的widget則不同。)因此,如果你需要講一個(gè)widget居中,預(yù)期調(diào)整Align這樣的屬性,不如將他包裹在一個(gè)Center widget內(nèi)。

Flutter中包含了邊距,對(duì)齊,行,列和網(wǎng)格系列的widgets。這些布局類(lèi)型的widgets自身沒(méi)有視覺(jué)內(nèi)容,而只用于控制其他的widgets的部分布局條件。Flutter也包含了以這種組合方法組成的實(shí)用性widgets。

例如,一個(gè)常用的widget Container,是由幾個(gè)widget組合而成,包含了布局、繪制、定位和大小的功能。更具體地說(shuō),Container是由LimitedBox、ConstrainedBox、Align、Padding、DecoratedBox和Transform組合而成的,你也可以通過(guò)查看源碼看到這些組合。Flutter 有一個(gè)典型的特征,即你可以深入到任意一個(gè) widget,查看其源碼。因此,你可以通過(guò)同樣的方式組合其他的 widgets,也可以參考 Container 來(lái)創(chuàng)建其他的 widget,而不需要繼承 Container 來(lái)實(shí)現(xiàn)自定義的效果。

構(gòu)建widgets

先前提到,可以通過(guò)重寫(xiě)build()方法,返回一個(gè)新的元素樹(shù),來(lái)定義視覺(jué)展示。這棵樹(shù)用更為具體的術(shù)語(yǔ)表示了widget在UI中的部分。例如,工具欄widget的build方法可能會(huì)返回水平布局,其中可能包含了一些文字,各種各樣的按鈕。根據(jù)需要,框架會(huì)遞歸請(qǐng)求每個(gè)widget進(jìn)行構(gòu)建,直到整棵樹(shù)都被具體的可渲染的對(duì)象描述為止。然后框架會(huì)將可渲染的對(duì)象縫合在一起,組成可渲染的對(duì)象樹(shù)。

Widget的build方法應(yīng)該是沒(méi)有副作用的。每當(dāng)一個(gè)方法要求構(gòu)建市,widget都應(yīng)當(dāng)能返回一個(gè)widget的元素樹(shù),與先簽返回的widget也沒(méi)有關(guān)聯(lián)??蚣軙?huì)根據(jù)渲染對(duì)象樹(shù)來(lái)確定哪些構(gòu)建方法需要被調(diào)用,這是一響略顯繁重的工作。

每個(gè)渲染幀,F(xiàn)lutter都可以根據(jù)變換的狀態(tài),調(diào)用build()方法重建部分UI。因此,保證build方法輕量且能夠快速返回widget是非常關(guān)鍵的,繁重的計(jì)算工作應(yīng)該通過(guò)一些異步的方法來(lái)完成,然后作為構(gòu)建方法build的一部分存儲(chǔ)。

盡管這樣的實(shí)現(xiàn)看起來(lái)不夠成熟,但是這樣的自動(dòng)對(duì)比方法非常有效,可以實(shí)現(xiàn)高性能的交互應(yīng)用。同時(shí),以這種方式設(shè)計(jì)的build方法,將重點(diǎn)放在widget組合的聲明上,從而簡(jiǎn)化了代碼,而不是以一種狀態(tài)去更新另一種狀態(tài)這樣的復(fù)雜過(guò)程。

狀態(tài)管理

那么,在眾多的widget都持有狀態(tài)的情況下,系統(tǒng)中的狀態(tài)是如何被傳遞和管理的呢?

與其他類(lèi)相同,你可以通過(guò)widget的構(gòu)造函數(shù)來(lái)初始化數(shù)據(jù),如此一來(lái)build()方法可以確保子widget使用其所需要的數(shù)據(jù)進(jìn)行實(shí)例化:

@override
Widget build(BuildContext context) {
   return ContentWidget(importantState);
}

然而,隨著widget樹(shù)層級(jí)的逐漸增加加深,依賴(lài)樹(shù)結(jié)構(gòu)上下傳遞狀態(tài)信息會(huì)變得十分麻煩。這時(shí),第三張類(lèi)型的widget——InheritedWidget,提供了一種從共享的祖先節(jié)點(diǎn)獲取數(shù)據(jù)的簡(jiǎn)易辦法。你可以使用InheritedWidget創(chuàng)建包含狀態(tài)的widget,該widget會(huì)將一個(gè)共同的祖先節(jié)點(diǎn)包裹在widget樹(shù)中,如下:

image

現(xiàn)在,當(dāng)ExamWidget或者GradeWIdget對(duì)象需要獲取StudentState的數(shù)據(jù)時(shí),可以直接使用以下方式:

final studentState = StudentState.of(context);

調(diào)用of(context)會(huì)根據(jù)當(dāng)前構(gòu)建的上下文(即當(dāng)前的widge位置的句柄),并返回類(lèi)型為StudentState的在樹(shù)中距離最近的祖先節(jié)點(diǎn)。InheritedWidget同時(shí)也包含了updateShouldNotify()方法,F(xiàn)lutter會(huì)調(diào)用它來(lái)判斷依賴(lài)了某個(gè)狀態(tài)的widget是否需要更新重建。

InheritedWidget在Flutter中被大量用于共享狀態(tài),例如應(yīng)用的視覺(jué)主題,包含了應(yīng)用于整個(gè)應(yīng)用的顏色和字體樣式等屬性。MaterialApp的build()方法會(huì)在構(gòu)建市在樹(shù)中插入一個(gè)主題,更生層次的widget便可以使用.of()方法來(lái)查找相關(guān)的主題數(shù)據(jù),例如:

Container(
  color: Theme.of(context).secondaryHeaderColor,
  child: Text(
    'Text with a background color',
    style: Theme.of(context).textTheme.headline6,
  ),
);

類(lèi)似的,以該方法實(shí)現(xiàn)的還有提供了路由頁(yè)面的Navigator,提供了屏幕信息指標(biāo),包括方向,尺寸和高度的MediaQuery等等。

隨著應(yīng)用程序的不斷迭代,更高級(jí)的狀態(tài)管理方法變得更加有吸引力,它們可以減少有狀態(tài)的widget的創(chuàng)建。許多Flutter應(yīng)用使用了provider用于狀態(tài)管理,它對(duì)InheritedWidget進(jìn)行了進(jìn)一步的包裝。FLutter的分層架構(gòu)也允許使用其他的實(shí)現(xiàn)來(lái)替換狀態(tài)管理只UI的方案,例如flutter_hooks。

渲染和布局

本節(jié)介紹Flutter的渲染機(jī)制,包括將widget層級(jí)結(jié)構(gòu)轉(zhuǎn)換成屏幕上繪制的實(shí)際像素的一系列步驟。

Flutter的渲染模型

你可能思考過(guò):既然Flutter是一個(gè)跨平臺(tái)的框架,那么它又是如何提供與原生平臺(tái)框架相當(dāng)?shù)男阅艿哪兀?/p>

讓我們從Android原生應(yīng)用的角度開(kāi)始思考。當(dāng)你在編寫(xiě)繪制的內(nèi)容的時(shí)候,你需要調(diào)用Android框架的Java代碼。Android的系統(tǒng)庫(kù)提供了可以將自身繪制到Canvas對(duì)象的組件,接下來(lái)Android就可以使用由C/C++編寫(xiě)的Skia圖形引擎,調(diào)用CPU和GPU完成在設(shè)備上的繪制。

跨平臺(tái)框架都會(huì)在Android和IOS的UI底層庫(kù)上創(chuàng)建一層抽象,該抽象層嘗試抹平各個(gè)系統(tǒng)之間的差異。這時(shí),應(yīng)用程序的代碼通常使用JavaScript等解釋型語(yǔ)言來(lái)進(jìn)行編寫(xiě),這些代碼會(huì)與基于Java的Android和基于OC的IOS進(jìn)行交互,最終展示UI界面。所有流程都增加了顯著的開(kāi)銷(xiāo),在UI和應(yīng)用邏輯有凡在的交互時(shí)更為如此。

相比之下,F(xiàn)lutter通過(guò)染過(guò)系統(tǒng)UI組件庫(kù),使用自己的widget內(nèi)容集,消減了抽象層的開(kāi)銷(xiāo)。用于繪制Flutter圖像內(nèi)容的Dart代碼被編譯成機(jī)器碼,并使用Skia進(jìn)行渲染。Flutter同時(shí)也嵌入了自己的Skia副本作文引擎的一部分,讓開(kāi)發(fā)者能再設(shè)備未更新到最新系統(tǒng)時(shí),也能跟進(jìn)升級(jí)自己的應(yīng)用,保證穩(wěn)定性并提升性能。

從用戶(hù)操作到GPU

對(duì)于Flutter渲染機(jī)制而言,首要原則就是簡(jiǎn)單快捷。Flutter為數(shù)據(jù)流向系統(tǒng)提供了直通的通道,如以下的流程圖所示:

Render pipeline sequencing diagram

接下來(lái)讓我們更加深入了解其中的一些階段。

構(gòu)建:從Widget到Element

首先觀察以下的代碼片段,它代表了一個(gè)簡(jiǎn)單的widget結(jié)構(gòu):

Container(
  color: Colors.blue,
  child: Row(
    children: [
      Image.network('https://www.example.com/1.png'),
      const Text('A'),
    ],
  ),
);

當(dāng)Flutter需要繪制這段代碼時(shí),框架會(huì)調(diào)用build()方法,返回一顆基于當(dāng)前應(yīng)用狀態(tài)來(lái)繪制UI的widget子樹(shù)。在這個(gè)過(guò)程中,build()方法可能會(huì)在必要時(shí),根據(jù)狀態(tài)引入新的widget。在上面的例子中,Container的color和child就是電信的例子。我們可以查看Container的源碼,會(huì)發(fā)現(xiàn)當(dāng)color屬性不為空時(shí),ColoredBox會(huì)被加入用于顏色布局。

if (color != null)
  current = ColoredBox(color: color!, child: current);

與之對(duì)應(yīng)的,Image和Text在構(gòu)建過(guò)程中也會(huì)引入RawImage和RichText。如此一來(lái),最終生成的widget結(jié)構(gòu)比代碼表示的層級(jí)更深,在該場(chǎng)景中如下圖:

Render pipeline sequencing diagram

這就是為什么你在使用Dart DevTools的Flutter inspector調(diào)試widget樹(shù)結(jié)構(gòu)時(shí),會(huì)發(fā)下實(shí)際的結(jié)構(gòu)比你原本代碼中的結(jié)構(gòu)更深。

在構(gòu)建階段,F(xiàn)lutter會(huì)將代碼中描述的widgets轉(zhuǎn)化成對(duì)應(yīng)的Element樹(shù),每一個(gè)Widget都有一個(gè)對(duì)應(yīng)的Element。每一個(gè)Element代表了梳妝層次結(jié)構(gòu)中特定位置的widget實(shí)例。目前有兩種Element的基本類(lèi)型:

  • ComponentElement,其他Element的宿主。
  • RenderObjectElement,參與布局或繪制階段的Element。
Render pipeline sequencing diagram

RenderObjectElement是底層RenderObject與對(duì)應(yīng)的widget之間的橋梁,我們晚點(diǎn)會(huì)介紹。

任何widget都可以通過(guò)其BuildContext引用到Element,它是該widget在樹(shù)中的位置的句柄。類(lèi)似于Theme.of(context)方法調(diào)用中的context,它作為build()方法的參數(shù)被傳遞。

由于widgets以及它上下節(jié)點(diǎn)的關(guān)系都是不可變的,因此,對(duì)widget樹(shù)做任何操作(例如將Text('A')修改成Text('B'))都會(huì)返回一個(gè)新的widget對(duì)象集合。但是這并不是意味著底層呈現(xiàn)的內(nèi)容必須要重新構(gòu)建。Element樹(shù)每一幀之間都是持久化的,因此起著至關(guān)重要的性能作用,F(xiàn)lutter依靠該優(yōu)勢(shì),實(shí)現(xiàn)類(lèi)一種好似widget樹(shù)被完全拋棄,而緩存了底層表示的機(jī)制。Flutter可以根據(jù)發(fā)生變化的widget,來(lái)重建需要重新配置的Element樹(shù)的部分。

布局和渲染

很少有應(yīng)用只繪制單個(gè)widget。因此,有效的排布widget的結(jié)構(gòu)以及在渲染完成前決定每個(gè)Element的大小和位置,是所有UI框架的重點(diǎn)之一。

在渲染樹(shù)中,每個(gè)節(jié)點(diǎn)的基類(lèi)都是RenderObject,該基類(lèi)為布局和繪制定義了一個(gè)抽象的模型。這是再平凡不過(guò)的事情:它并不總是一個(gè)固定大小,甚至不尊徐笛卡爾坐標(biāo)系規(guī)律。每一個(gè)RenderObjectElement都了解其父節(jié)點(diǎn)的信息,對(duì)于其子節(jié)點(diǎn),除了如何訪(fǎng)問(wèn)和獲得他們的布局約束,并沒(méi)有更多的信息。這樣設(shè)計(jì)讓RenderObject擁有高效的抽象能力,能夠處理各種各樣的使用場(chǎng)景。

在構(gòu)建階段,F(xiàn)lutter會(huì)為Element樹(shù)中的每個(gè)RenderObjectElement創(chuàng)建或更新其對(duì)于的一個(gè)從RenderObject繼承的對(duì)象。RenderObject實(shí)際上是原語(yǔ):渲染文字的RenderParagraph、渲染圖片的RenderImage以及在繪制子節(jié)點(diǎn)內(nèi)容前應(yīng)用變換的RenderTransform是更為上層的實(shí)現(xiàn)。

Differences between the widgets hierarchy and the element and render trees

大部分的Flutter widget是由一個(gè)繼承了RenderBox的子類(lèi)對(duì)象渲染的,他們呈現(xiàn)出的RenderObject會(huì)在二維迪卡空間中擁有固定的大小。RenderBox提供了盒子模型限制,為每個(gè)widget關(guān)聯(lián)了渲染的最小和最大的寬度和高度。

在進(jìn)行布局的時(shí)候,F(xiàn)lutter會(huì)議DFS(深度優(yōu)先遍歷)方式遍歷渲染書(shū),并將限制以自上而下的方式從父節(jié)點(diǎn)傳遞給子節(jié)點(diǎn)。子節(jié)點(diǎn)若要確定自己的大小,則必須遵循父節(jié)點(diǎn)傳遞的限制。子節(jié)點(diǎn)的響應(yīng)方式是在父節(jié)點(diǎn)簡(jiǎn)歷的約束內(nèi)將大效益自下而上的方式傳遞給父節(jié)點(diǎn)。

Constraints go down, sizes go up

在遍歷完成一次樹(shù)之后,每個(gè)對(duì)象都通過(guò)父級(jí)約束而擁有了明確的大小,隨時(shí)可以通過(guò)調(diào)用paint()進(jìn)行渲染。

盒子限制模型十分強(qiáng)大,它的對(duì)象布局的時(shí)間復(fù)雜度是O(n):

  • 父節(jié)點(diǎn)可以通過(guò)設(shè)定最大和最小的尺寸限制,決定其子節(jié)點(diǎn)對(duì)象的大小。例如:在一個(gè)手機(jī)應(yīng)用中,最高層級(jí)的渲染對(duì)象將會(huì)限制其子節(jié)點(diǎn)的大小為屏幕的尺寸。(子節(jié)點(diǎn)可以選擇如何占用空間。例如,它們可能在設(shè)定的限制中以居中的方式布局。)
  • 父節(jié)點(diǎn)可以決定子節(jié)點(diǎn)的寬度,而讓子節(jié)點(diǎn)靈活地自適應(yīng)布局高度(或決定高度而自適應(yīng)寬度)?,F(xiàn)實(shí)中有一種例子就是流式布局的文本,它們常常會(huì)填充橫向限制,再根據(jù)文字內(nèi)容的多少?zèng)Q定高度。

這樣的盒子約束模型,同樣也適用于子節(jié)點(diǎn)對(duì)象需要知道有多少可用空間渲染其內(nèi)容的場(chǎng)景,通過(guò)使用 LayoutBuilder widget,子節(jié)點(diǎn)可以得到從上層傳遞下來(lái)的約束,并合理利用該約束對(duì)象,使用方法如下:

Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (context, constraints) {
      if (constraints.maxWidth < 600) {
        return const OneColumnLayout();
      } else {
        return const TwoColumnLayout();
      }
    },
  );
}

所有的RenderObject的根節(jié)點(diǎn)是RenderView,代表了渲染樹(shù)的總體輸出。當(dāng)平臺(tái)需要渲染新的一幀內(nèi)容時(shí)(例如一個(gè)vsync型號(hào)或者一個(gè)紋理的更新完成),會(huì)調(diào)用一次compositeFrame()方法,它是RenderView的一部分。該方法會(huì)創(chuàng)建一個(gè)SceneBuilder來(lái)觸發(fā)當(dāng)前畫(huà)面的更新。當(dāng)畫(huà)面更新完畢,RenderView會(huì)將合成的畫(huà)面?zhèn)鬟f給dart:ui中的Window.render()方法,控制GPU進(jìn)行渲染。

Platform embedding

我們都知道,F(xiàn)lutter 的界面構(gòu)建、布局、合成和繪制全都由 Flutter 自己完成,而不是轉(zhuǎn)換為對(duì)應(yīng)平臺(tái)系統(tǒng)的原生組件。獲取紋理和聯(lián)動(dòng)應(yīng)用底層的生命周期的方法,不可避免地會(huì)根據(jù)平臺(tái)特性而改變。 Flutter 引擎本身是與平臺(tái)無(wú)關(guān)的,它提供了一個(gè)穩(wěn)定的 ABI(應(yīng)用二進(jìn)制接口),包含一個(gè) 平臺(tái)嵌入層,可以通過(guò)其方法設(shè)置并使用 Flutter。

平臺(tái)嵌入層是用于呈現(xiàn)所有 Flutter 內(nèi)容的原生系統(tǒng)應(yīng)用,它充當(dāng)著宿主操作系統(tǒng)和 Flutter 之間的粘合劑的角色。當(dāng)你啟動(dòng)一個(gè) Flutter 應(yīng)用時(shí),嵌入層會(huì)提供一個(gè)入口,初始化 Flutter 引擎,獲取 UI 和柵格化線(xiàn)程,創(chuàng)建 Flutter 可以寫(xiě)入的紋理。嵌入層同時(shí)負(fù)責(zé)管理應(yīng)用的生命周期,包括輸入的操作(例如鼠標(biāo)、鍵盤(pán)和觸控)、窗口大小的變化、線(xiàn)程管理和平臺(tái)消息的傳遞。 Flutter 擁有 Android、iOS、Windows、macOS 和 Linux 的平臺(tái)嵌入層,當(dāng)然,開(kāi)發(fā)者可以創(chuàng)建自定義的嵌入層,正如這個(gè) 可用的例子 以 VNC 風(fēng)格的幀緩沖區(qū)支持了遠(yuǎn)程 Flutter,還有 [支持樹(shù)莓派運(yùn)行的例子]https://github.com/ardera/flutter-pi)。

每一個(gè)平臺(tái)都有各自的一套 API 和限制。以下是一些關(guān)于平臺(tái)簡(jiǎn)短的說(shuō)明:

  • 在 iOS 和 macOS 上, Flutter 分別通過(guò) UIViewControllerNSViewController 載入到嵌入層。這些嵌入層會(huì)創(chuàng)建一個(gè) FlutterEngine,作為 Dart VM 和您的 Flutter 運(yùn)行時(shí)的宿主,還有一個(gè) FlutterViewController,關(guān)聯(lián)對(duì)應(yīng)的 FlutterEngine,傳遞 UIKit 或者 Cocoa 的輸入事件到 Flutter,并將 FlutterEngine 渲染的幀內(nèi)容通過(guò) Metal 或 OpenGL 進(jìn)行展示。

  • 在 Android 上,F(xiàn)lutter 默認(rèn)作為一個(gè) Activity 加載到嵌入層中。此時(shí)視圖是通過(guò)一個(gè) FlutterView 進(jìn)行控制的,基于 Flutter 內(nèi)容的合成和 z 排列 (z-ordering) 的要求,將 Flutter 的內(nèi)容以視圖模式或紋理模式進(jìn)行呈現(xiàn)。

  • 在 Windows 上,F(xiàn)lutter 的宿主是一個(gè)傳統(tǒng)的 Win32 應(yīng)用,內(nèi)容是通過(guò)一個(gè)將 OpenGL API 調(diào)用轉(zhuǎn)換成 DirectX 11 的等價(jià)調(diào)用的庫(kù) ANGLE 進(jìn)行渲染的。目前正在嘗試將 UWP 應(yīng)用作為 Windows 的一種嵌入層,并將 ANGLE 替換為通過(guò) DirectX 12 直接調(diào)用 GPU 的方式。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容