flutter入門:線程,異步,聲明式UI

關於flutter的背景、體系結構、橫向對比等,建議閱讀淘寶的一篇文章,大比拼|下一代高性能跨平臺UI渲染引擎,人家是真的厲害。前端

這裏就很少貼這些宏觀的簡介了。本文主要從客戶端開發的角度看三個小點:線程、異步、聲明式UI,都是Flutter跟正常的客戶端開發有必定區別的地方。react

線程模型

線程模型
線程模型這塊,大多數文章對初學者來說都有點不清不楚的,這裏詳細總結一下。android

首先,在上層flutter APP裏,咱們用dart語言開發,這個層面,是沒有線程的概念的,取而代之的是dart提供的相似線程的isolate。isolate簡單來說就是個受限制的線程,isolate之間只能經過一種叫port的消息機制通訊,不能共享內存。除此以外跟線程是同樣的。
dart vm默認提供了一個root isolate,有耗時操做須要執行時,能夠new出新的isolate執行。
Flutter engine這個層面,有四個Runner各司其職,這裏的Runner其實就是線程,不過這四個Runner是由Engine和Native之間的那個嵌入層去賦值的,engine層只會使用這四個Runner,不會建立新的線程。默認地,Platform Runner和Native的主線程是同一個線程。
回頭看dart的root isolate,它跟engine層的UI Runner是綁定的,即,它們兩個是同一個線程。編程

總體看一下,會發現一些特別的東西。對Flutter App來說,root isolate基本上能夠理解爲主線程,同時它也是UI線程。可是,它不是Native層面的主線程,在Native看來,它只是個子線程。bash

dart異步編程

callback

對異步編程而言,客戶端開發最熟悉的多是callback語法,固然不少時候也會使用delegate。dart的callback語法以下:網絡

Timer.run(() => print('hi!'));
複製代碼

不過雖然dart也能夠用callback,可是更多的時候,會使用Future/async/await這套語法來執行異步任務。多線程

Future/async/await

Future<Response> dateRequest() async {
  String url = 'https://www.baidu.com';
  Client client = Client();
  Future<Response> response = client.get(requestURL);
  return response;
}

Future<String> loadData() async {
  Response response = await dataRequest();
  return response.body;
}
複製代碼

簡單看一下這個小例子,client.get()是個異步的網絡請求,它能夠直接返回一個Future<Response>的對象,這個名字頗有意思,它的意思是,我之後會給你個Response類型的對象的,可是如今,只是個空頭支票(Future)。併發

以後,可使用await關鍵字加上這個Future,當前調用就會停在這裏,直到這個Future對象返回纔會繼續向下執行。基本原理是,把當前上下文存到堆內存;當Future返回時,會產生一個event進入eventloop(基本上是個語言都有這麼個玩意兒,能夠參考Dart與消息循環機制),這個event會觸發進入以前的上下文繼續執行。app

能夠看到,這裏的寫法很像同步的寫法,可是它是不會阻塞當前線程的,原理上面已經簡單解釋了。目前,async/await這種異步語法,是公認的異步語法的最佳方案。前端和安卓的kotlin已經比較普遍地使用了,而iOS還沒跟得上時代。框架

單線程語言 & isolate

前面講Flutter線程模型時,已經提到了isolate。它在底層其實就是個線程,可是dart vm限制了isolate的能力,使得isolate之間不能直接共享內存,只能經過Port機制收發消息。
看一下代碼

void main() async{
  runApp(MyApp());
  
  //asyncFibonacci函數裏會建立一個isolate,並返回運行結果
  print(await asyncFibonacci(20));
}

//這裏以計算斐波那契數列爲例,返回的值是Future,由於是異步的
Future<dynamic> asyncFibonacci(int n) async{
  final response = new ReceivePort();
  await Isolate.spawn(isolateTask,response.sendPort);
  final sendPort = await response.first as SendPort;
  final answer = new ReceivePort();
  sendPort.send([n,answer.sendPort]);
  return answer.first;
}
//建立isolate必需要的參數
void isolateTask(SendPort initialReplyTo){
  final port = new ReceivePort();
  //綁定
  initialReplyTo.send(port.sendPort);
  //監聽
  port.listen((message){
    //獲取數據並解析
    final data = message[0] as int;
    final send = message[1] as SendPort;
    //返回結果
    send.send(syncFibonacci(data));
  });
}

int syncFibonacci(int n){
  return n < 2 ? n : syncFibonacci(n-2) + syncFibonacci(n-1);
}
複製代碼

Port分爲ReceivePort和SendPort,這二者是成對出現的,在新建一個isolate的時候,能夠傳入一個sendPort用於isolate向主線程發消息,若是主線程想往子線程發消息呢...就只能讓子線程new出一對port把sendport發過來才能用...

語法上是很囉嗦了,所幸Flutter給咱們封裝了便捷的compute函數,能夠參考深刻了解Flutter的isolate(4) --- 使用Compute寫isolates,因爲只是上層封裝,這裏就不具體展開了。

到這裏咱們基本上明白了,isolate就是個削弱版的線程,用起來麻煩一點,另外就是因爲不共享內存,port發送數據時是copy的,若是有大塊內存真的要copy多份,可能會有比較大的內存問題。

可是,官方明確說明,dart是個單線程語言。這很容易讓人困惑,由於從體系結構上,isolate實際上是內核級線程的封裝,在系統內核層面就是多線程的。
我以爲這句話可能從理論模式上理解會比較好。
併發編程長期以來有兩種範式,一種是基於共享內存的,主要是多線程編程;一種是基於消息的,如Actor、CSP模型。從這個角度看,isolate實際上是消息驅動的併發編程,算是CSP模型的簡化,跟多線程編程是徹底不一樣的併發編程範式。
所以說dart是個單線程語言也是說得通的。

聲明式UI

聲明式UI與響應式UI是對應的概念,考慮一下iOS/android的UI實現。
iOS是很純粹的命令式,new view,addsubview,new view,addsubview,這樣搞。
安卓呢,算是半命令式吧,xml聲明瞭UI,這是聲明式的部分;但程序運行時若是要修改某個view,還是取到這個view,再去命令式地修改。

下面來看看flutter的框架

Declarative UI

flutter的UI框架吸收了react的理念,即 UI是關於狀態的函數。
具體看一下demo

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        leading: IconButton(icon:Icon(Icons.arrow_back),
          onPressed:() => SystemNavigator.pop(),
        )
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
複製代碼

這個是官方的helloworld demo。每一個組件,會有個build函數,這裏會返回一個可以完整描述UI的對象結構。每當數據改變時,就從新調用build函數,返回新的結構。如何高效渲染,就是框架去作的事情了。

經過這種方式,不論是UI的初始佈局結構,仍是後面的修改,都是build函數返回的對象結構去聲明的,完整的聲明式UI由此而來。

UI開發的最佳實踐是怎麼樣的,一直以來都充滿爭議。但近幾年,React -> Flutter -> SwiftUI,都使用了聲明式的UI編程範式,能夠看到頭部公司基本上達成了共識,目前階段,這就是最佳實踐。

相關文章
相關標籤/搜索