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看来,它只是个子线程。网络

dart异步编程

callback

对异步编程而言,客户端开发最熟悉的多是callback语法,固然不少时候也会使用delegate。dart的callback语法以下:app

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会触发进入以前的上下文继续执行。async

能够看到,这里的写法很像同步的写法,可是它是不会阻塞当前线程的,原理上面已经简单解释了。目前,async/await这种异步语法,是公认的异步语法的最佳方案。前端和安卓的kotlin已经比较普遍地使用了,而iOS还没跟得上时代。ide

单线程语言 & 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和线程的区别。因为isolate之间是不共享内存的,它们其实基本上是彻底隔离的。隔离就是这里的关键,从上层代码来看,是的,个人代码开了好几个线程,可是,从执行逻辑上,每一个isolate直接是相互隔离的,对每一个isolate内的逻辑来说,它就是单线程的。

声明式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编程范式,能够看到头部公司基本上达成了共识,目前阶段,这就是最佳实践。

相关文章
相关标签/搜索