天天看點

flutter系列之:flutter架構什麼的,看完這篇文章就全懂了簡介Flutter的架構圖Widgets渲染和布局總結

簡介

Flutter是google開發的一個跨平台的UI建構工具,flutter目前最新的版本是3.0.1。使用flutter你可以使用一套代碼搭建android,IOS,web和desktop等不同平台的應用。做到一次編寫到處運作的目的。

說到一次編寫處處運作,大家可能會想到java。那麼flutter跟java是不是類似呢?

對于JAVA來說,在編寫完JAVA代碼之後,将其編譯成為class位元組碼,然後這個class位元組碼就可以不需要進行任何轉換的在任何平台上運作。其底層原理是JAVA開發了适配不同作業系統和平台的JVM,class實際運作在JVM中,是以對底層到底運作在哪個平台是無感的。一切的适配都是由JVM來執行的。

Flutter其實更像是C或者C++,雖然代碼是一樣的,但是需要根據不同的平台編譯成不同的二進制檔案。而Flutter也是一樣,雖然我們使用同一套dart代碼編寫了Flutter程式,但是需要不同的指令編譯成不同平台的指令和安裝包。

當然,在開發過程中,flutter提供了虛拟機,實作了hot reload的功能,在代碼進行修改之後,可以立刻重載,而不需要重新編譯整個代碼。

FLutter這麼神奇,那麼它到底是怎麼工作的呢?

Flutter的架構圖

我們先來看下Flutter的架構圖,當然這個架構圖是官方來的,官方的架構圖表示的是權威:

flutter系列之:flutter架構什麼的,看完這篇文章就全懂了簡介Flutter的架構圖Widgets渲染和布局總結

從上圖中,我們可以看到Flutter的架構可以分為三部分,從下到上分别是embedder,Engine和Framework。

embedder

embedder可以稱為嵌入器,這是和底層的作業系統進行互動的部分。因為flutter最終要将程式打包到對應的平台中,是以這個嵌入器需要和底層的平台接口進行互動。

具體而言,對于Android平台使用的是Java和C++,對于iOS和macOS平台,使用的是Objective-C/Objective-C++,對應Windows平台和Linux平台的是C++。

為什麼C++這麼強大? 這裡就可以看出來了,基本上所有底層的東西都是用C++寫的。

回到embedder,為什麼叫做嵌入器呢?這是因為Flutter打包的程式,可以作為整個應用程式,也可以作為現有程式的一部分被嵌入使用。

engine

engine也叫做flutter engine,它是flutter中最核心的部分。

Flutter engine基本上使用C++寫的。engine的存在是為了支援Dart Framework的運作。它提供了Flutter的核心API,包括作圖、檔案操作、網絡IO、dar運作時環境等核心功能。

engine主要是通過dart:ui暴露給Flutter framework層的。

Flutter framework

這一層是使用者程式設計的接口,我們的應用程式需要和Flutter framework進行互動,最終建構出一個應用程式。

Flutter framework主要是使用dart語言來編寫的。

framework從下到上,我們有最基礎的foundational包,和建構在其上的 animation, painting和 gestures 。

再上面就是rendering層,rendering為我們提供了動态建構可渲染對象樹的方法,通過這些方法,我們可以對布局進行處理。

接着是widgets layer,它是rendering層中對象的組合,表示一個小挂件。

最後是Material和Cupertino庫,這些庫使用widegts層中提供的小部件,組合成了不同風格的控件集。

Flutter framework就是這樣一層層的建構起來的。

當然,上面的embedder和engine屬于比較底層的東西,我們隻需要知道Flutter有這麼一個東西,是這麼使用的即可。

真正和我們程式員相關的,就是Flutter framework了。因為我們在編寫代碼的過程中,需要和Flutter framework打交道。

接下來,我們重點關注下Flutter framework中的幾個核心部分。

Widgets

Widgets翻譯成中文就是小插件的意思。Widgets是Flutter中使用者界面的基礎。你在flutter界面中能夠觀察到的使用者界面,都是Widgets。

當然這些大的Widgets又是由一個個的小的Widgets組成的,而這些小的Widgets又是由更小的Widgets組成的。

這樣就構成了Widgets的層次依賴結構,這些層次結構的關聯關系是通過Widget中的child Widget進行關聯的。

在這種層次結構中,子Widgets可以共享父Widgets的上下文環境。

Flutter中的Widgets跟其他語言中的類似的Widgets組合有什麼不同呢?

他們最大的不同是,Flutter中的Widgets更多,每個Widgets專注的功能更小。即便是一個很小很小功能,在Flutter中都可以找到與之對應的Widgets。

這樣做的好處就是,你可以使用不同的,非常基礎的Widgets任意組合,進而建構出非常複雜的,個性化的大的Widgets。

當然,它的缺點也非常明顯,就是代碼裡面的Widgets太多了,導緻代碼中的層級結構特别的多,可能會看的眼花缭亂。

舉個簡單的例子,Container是flutter提供的一個基本的容器Widget,我們通常這樣來使用它:

Container(
   constraints: BoxConstraints.expand(
     height: Theme.of(context).textTheme.headline4!.fontSize! * 1.1 + 200.0,
   ),
   padding: const EdgeInsets.all(8.0),
   color: Colors.blue[600],
   alignment: Alignment.center,
   child: Text('Hello World',
     style: Theme.of(context)
         .textTheme
         .headline4!
         .copyWith(color: Colors.white)),
   transform: Matrix4.rotationZ(0.1),
 )
           

我們向Container中傳入了constraints,padding,color,alignment,child,transform等資訊。

我們先來猜一下,這些資訊中,哪些是用來建構Widget的?

大家第一時間想到的應該是child,它本身就是一個Widget,用來表示Container中包含的子對象,這個很好了解。

但是,除了child這個Widget之外,其他的constraints,padding,color,alignment,transform等都是構成Widget的元素!

我們來看下Container的build方法:

Widget build(BuildContext context) {
    Widget? current = child;

    if (child == null && (constraints == null || !constraints!.isTight)) {
      current = LimitedBox(
        maxWidth: 0.0,
        maxHeight: 0.0,
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
      );
    }

    if (alignment != null)
      current = Align(alignment: alignment!, child: current);

    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
    if (effectivePadding != null)
      current = Padding(padding: effectivePadding, child: current);

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

    if (clipBehavior != Clip.none) {
      assert(decoration != null);
      current = ClipPath(
        clipper: _DecorationClipper(
          textDirection: Directionality.maybeOf(context),
          decoration: decoration!,
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

    if (decoration != null)
      current = DecoratedBox(decoration: decoration!, child: current);

    if (foregroundDecoration != null) {
      current = DecoratedBox(
        decoration: foregroundDecoration!,
        position: DecorationPosition.foreground,
        child: current,
      );
    }

    if (constraints != null)
      current = ConstrainedBox(constraints: constraints!, child: current);

    if (margin != null)
      current = Padding(padding: margin!, child: current);

    if (transform != null)
      current = Transform(transform: transform!, alignment: transformAlignment, child: current);

    return current!;
  }

           

從代碼中可以看到,Container先是建立了LimitedBox,然後将其嵌入到Align中,再依次嵌入到Padding,ColoredBox,ClipPath,DecoratedBox,ConstrainedBox,Padding和Transform中。這些所有的對象都是Widget。

這裡應該可以了解Flutter中Widget的設計思想了。在Flutter中一切皆可為Widget。

Widgets的可擴充性

和其他的編譯成原生語言特性的跨平台實作如React native相比,Flutter對于每個UI都有自己的實作,而不是依賴于作業系統提供的接口。

這樣做的好處就是一切都是由Flutter自己控制的,使用者可以在Flutter的基礎上進行無限擴充,而不用受限于系統底層的實作限制。

另一方面,這樣可以減少Flutter在呈現過程中在Flutter代碼和平台代碼之間來回轉換,減少了性能瓶頸,提升效率。

最後,因為UI的實作和底層的作業系統是分離的,是以Flutter的APP在不同的平台上面可以有統一的外觀和實作,可以保證風格的統一。

Widgets的狀态管理

Widgets表示的是不可變的使用者UI界面結構。雖然結構是不能夠變化的,但是Widgets裡面的狀态是可以動态變化的。

根據Widgets中是否包含狀态,Widgets可以分為stateful和stateless widget,對應的類是StatefulWidget和StatelessWidget。

對于有些Widgets來說,比如icon或者Label,它裡面本身就不需要狀态,這些Widgets就是StatelessWidget。

但是如果有些Widgets中的某些内容可能需要根據使用者或者其他原因來動态變化,則就需要使用StatefulWidget。

之前提到了Widgets是不可變的,StatefulWidget中的可變資料是存放在對應的State中的,是以StatefulWidgets本身并沒有build方法,所有使用者界面都是通過State對象來建構的。

當State發生變化的時候,需要調用setState() 方法來通知flutter架構來調用State的build方法,進而将變化回報到使用者界面中。

既然StatefulWidget是帶有狀态的,那麼這些狀态是怎麼進行管理和傳遞的呢?

State本身提供了一個build方法,用于建構初始的狀态:

Widget build(BuildContext context);
           

如果在一個StatefulWidget中需要嵌入另外一個StatefulWidget,那麼可以在其對應的State中調用另外一個StatefulWidget的構造函數,将要傳遞的資料,以構造函數參數的形式傳遞給子Widget。

當然這樣做是沒問題的。但是如果元件的嵌套層數過多的話,這種構造函數的傳遞方式,顯然不能滿足我們的需求。

于是Flutter提供了一個InheritedWidget類,如果我們自定義的類需要共享資料給子Widgets,則可以繼承InheritedWidget。

Inherited widgets有兩個作用: 第一,子Widget可以通過Inherited widgets提供的靜态of方法拿到離他最近的父Inherited widgets執行個體。

第二,當Inherited widgets改變state之後,會自動觸發state消費者的rebuild行為。

先來看一下inherited widgets類的定義:

abstract class InheritedWidget extends ProxyWidget {

  const InheritedWidget({ Key? key, required Widget child })
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
           

可以看到InheritedWidget是對實際Widget對象的代理,另外還将InheritedWidget封裝到了InheritedElement中。

這裡不多講解InheritedElement,InheritedElement是底層通知機制的實作。

我們看到InheritedWidget還添加了一個updateShouldNotify,這個方法可以提供給我們控制目前InheritedWidget rebuilt的時候,是否需要rebuilt繼承它的子Widget。

下面我們看一個InheritedWidget的具體實作:

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key? key,
    required this.color,
    required Widget child,
  }) : super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    final FrogColor? result = context.dependOnInheritedWidgetOfExactType<FrogColor>();
    assert(result != null, 'No FrogColor found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}
           

FrogColor中定義了一個Color屬性,當Color發生變化的時候,就會調用updateShouldNotify。

另外,FrogColor還提供了一個of方法,接受的參數是BuildContext,然後調用context.dependOnInheritedWidgetOfExactType去查找離該context最近的FrogColor。

為什麼要使用of方法對context.dependOnInheritedWidgetOfExactType進行封裝呢?這是因為,context.dependOnInheritedWidgetOfExactType方法不一定能夠找到要找的對象,是以我們需要進行一些異常值的處理。

另外,有可能of方法傳回的對象和context.dependOnInheritedWidgetOfExactType中查找的對象不一樣,這都是可以的。

我們看下of方法的具體使用:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FrogColor(
        color: Colors.green,
        child: Builder(
          builder: (BuildContext innerContext) {
            return Text(
              'Hello Frog',
              style: TextStyle(color: FrogColor.of(innerContext).color),
            );
          },
        ),
      ),
    );
  }
}
           

還有一個問題,of方法傳入的是BuildContext對象,注意,這裡的BuildContext必須是InheritedWidget對象本身的後輩,也就是說在對象樹中,必須是InheritedWidget的子樹。再看下面的例子:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FrogColor(
        color: Colors.green,
        child: Text(
          'Hello Frog',
          style: TextStyle(color: FrogColor.of(context).color),
        ),
      ),
    );
  }
}
           

這個例子中,FrogColor.of方法中的context是FrogColor的父context,是以是找不到FrogColor對象的,這樣的使用是錯誤的。

當然,除了InheritedWidget,Flutter還提供了很多狀态管理的工具,比如provider,bloc,flutter_hooks等,也是非常好用的。

渲染和布局

渲染就是将上面我們提到的widgets轉換成使用者肉眼可以感覺的像素的過程。

Flutter作為一種跨平台的架構,它和普通的跨平台的架構或者原生的架構有什麼差別呢?

首先來考慮一下原生架構。以android為例,首先調用的是andorid架構的java代碼,通過調用android系統庫提供的進行繪制的元件,最後調用底層的Skia來進行繪制。Skia 是一種用 C/C++ 編寫的圖形引擎,它調用 CPU 或 GPU 在裝置上完成繪制。

那麼常見的跨平台架構是怎麼運作的呢?它們實際上在原生的代碼架構上面又封裝了一層。通常使用javascript這樣的解釋性語言來進行編寫,然後編寫的代碼再和andorid的JAVA或者IOS的Objective-C系統庫進行互動。這樣的結果就是在UI互動或者調用之間會造成顯著的性能開銷。這也就是通用的跨平台語言不如原生的性能好的原因。

但是flutter不一樣,它并不是用系統自帶的UI控件,而是擁有自己的實作。Flutter代碼會直接被編譯成使用 Skia 進行渲染的原生代碼,進而提升渲染效率。

接下來,我們具體看一下flutter從代碼到渲染的整個流程。首先看一段代碼:

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

上面的代碼是建構一個Container widget。當flutter想要渲染這個widget的時候,會去調用build() 方法,然後生成一個widget集合.

為什麼是Widget集合呢?在上面我們也分析過,Container這個widget是由很多個其他的widget組成的,是以,上面的Container會生成下面的widget樹:

flutter系列之:flutter架構什麼的,看完這篇文章就全懂了簡介Flutter的架構圖Widgets渲染和布局總結

上面的就是代碼中生成的widget,這些widget在build的過程中,會被轉換為 element tree。一個element和一個widget對應。

element表示的widget的執行個體。flutter中有兩種類型的element,分别是:ComponentElement和RenderObjectElement.

ComponentElement是其他Element的容器,而RenderObjectElement是真正參與layout和渲染的element。

因為Widget本身是不可變的,是以任何對于Widget的修改都會傳回一個新的Widget。那麼是不是所有的變動,都會導緻整個element tree重新渲染呢?

答案是不會的,flutter僅會重新渲染需要被重新繪制的element。

接下來,我們看下渲染樹是怎麼建構的,渲染樹中的每個元素叫做RenderObject,它定義了布局和繪制的抽象模型。

上面我們提到的RenderObjectElement會在渲染的時候轉換成為RenderObject,如下所示:

flutter系列之:flutter架構什麼的,看完這篇文章就全懂了簡介Flutter的架構圖Widgets渲染和布局總結

當然,不同的Render element會轉換成為不同的Render對象。

總結

繼續閱讀