高效开发与高性能并存的UI框架——携程Flutter实践

Flutter已经开源了三年,但是最近两年才开始在开源社区活跃起来,尤其是最近还发布了Preview 1版本。作为可以实现一套代码同时在iOS、Android平台上运行的又一个新的UI框架,Flutter提供给开发者的不仅仅是高速实现,还有高质量、流畅的UI。免费开源的协议对于开发者来说也很友好。

 

本文将从Flutter架构理念与UI渲染逻辑,来解释为什么Flutter的渲染效率非常高,以及从Flutter开发实践的角度,介绍框架的特性及Flutter开发中所遇到的问题,希望给对Flutter感兴趣的小伙伴在选型时一些启发和思考,避免重复踩坑。

 

一、Flutter Layers

Flutter的主要设计人之一Ian Hickson,之前是HTML规范编写者,因此Flutter的设计理念也与HTML的实现方法有很多相似之处。

 

Flutter最初的理念是实现跨平台的Material Design的跨平台框架。平台框架大致可以分为四层:

 

  • dart:ui : 最底层的是UI层,由Flutter引擎所暴露的库,可以理解为一个布局层。

  • Rendering : 这一层是抽象的布局层,它依赖于UI层,可以构建一个UI树,通过更新UI树来更新UI。

  • Material与Widgets : 最后就是Material层使用Widget层来构建UI。

 

起初Flutter是没有Rendering层的,直接通过坐标计算每个像素点需要显示什么,这让框架的代码变得特别复杂,每当UI更新的时候需要重新计算这些坐标是否需要改变。后来增加Randering层来抽象UI显示的位置,通过抽象位置来判断像素点是否需要更新。
 

在Flutter项目的初期,Dart-lang也不是特别成熟。Dart虚拟机在垃圾回收的频率与回收机制表现当时并不是特别好,比如当时Flutter如果运行一个时间很长的动画,动画结束之后所占用的内存对于Flutter框架就是一个很大的垃圾。后来Dart团队在垃圾回收上进行了很多优化,使Flutter在UI显示更流畅。
 

如今,国内最大的使用厂商应该就是阿里闲鱼了,在Flutter发布Preview 1版本的时候,闲鱼App也一起协同展示了他们用Flutter编写的商品详情页面。我也在使用Flutter仿小米计算器开发后,体验到release版的流畅度确实堪比原生:

 

 

(已上架Google,可以通过包名搜索下载体验:top.basking.calculator)

 

二、Flutter的UI渲染

Flutter渲染效率堪比原生,快于RN。Flutter更新UI的时候,并不是更新整个UI,而是更新所需要更新的部分。比如从网络异步下载一个图片,设置到“Image”(ImageView)中,如果这个Image Widget大小并没有改变,只需要将图片对象传入Widget中,接着直接重新绘制这一个Widget就可以了。为了达到这样的UI渲染理念,Flutter是如何设计的呢?

 

FlutterUI渲染过程

 

Flutter 的UI渲染过程简单可以分为3个分支,Widget树、Element树、Rendering树。

 

当Widget改变的时候,只有将它添加到Element树上时,才会改变Rendering树,展示到UI界面上。将它添加到Element树的方法就是setState()方法,它会自动寻找改变了的Widget,然后添加到Element树,等待后续的操作。

 

可以看到,矩形的子Widget并没有改变,所以在Element树上也没有改变,到了Rendering树也没有重新渲染,这种设计理念对于刷新UI操作可以大大提高效率。

 

FlutterUI渲染 —— onDraw与onLayout

 

与其他的UI框架渲染逻辑不同的是,Widget的Draw与Layout的顺序不一定相同。比如在Android端onDraw与onLayout的顺序是相同的。关于Flutter框架的渲染顺序大家可以看以下的例子:

在Row Widget中有三个子Widget,其中中间的是固定宽度的Widget,还有两个是根据剩下宽度比例占用位置的Widget,其中绿色Widget是橙色的宽度的两倍。而他们的layout order与rendering order如下:

这么做是因为Flutter为了保证对于每个Widget的访问是单一线性的。所以在layout order中Flutter框架就会先layout固定宽度的Widget,然后再layout比例宽度的Widget。接着到了Rendering树再会根据Element树的顺序逐个对每个Widget进行渲染。

 

三、Flutter框架UI特性

 

Dart语言

 

Flutter的开发语言是由ChromeV8引擎团队的领导者Lars Bak主持开发的Dart。Dart语言语法类似于C。Dart语言为了更好的适应FlutterUI框架,在内存分配和垃圾回收做了很多优化。

 

因为Dart在连续分配多个对象的时候,所需消耗的资源非常少。Dart虚拟机可以快速分配内存给短期生存的对象,这样可以使很复杂的UI在60ms内完成一帧的渲染(实际感觉每一帧渲染时间更短),这样就保证了Flutter可以平滑的展示UI滑动及动画等效果。Flutter团队与Dart团队的密切合作让提升效率变得更加容易。

 

FlutterUI开发样式

 

Flutter在开发UI界面的时候,又比较像HTML的标签式语言,前文也提到,这是受Flutter创始人之一的Ian Hickson影响。其实很多UI布局都是类似标签的样式来编写的,比如Android的XML以及网页的HTML,所以Flutter会采用这样一个成熟的布局开发样式。

 

new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            new FlatButton(
              color: Colors.blue,
              )
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), 
    );

 

Flutter插件、依赖与包管理器

 

Flutter与RN一样,在原生开发中很依赖于插件来调用系统API,毕竟它是一个UI框架。但是现阶段的Flutter插件并不是像RN那么全,可以看到维护Flutter的开发者只有200多人,而维护react-native的开发者已经近1700人了,一个数量级之差的维护者肯定在插件数量与开发体验上差别很大。

 

在包管理上,flutter并不需要依赖第三方类似于RN的npm包管理器来添加依赖,flutter本身就自带了包管理器,只需要在pubspec.yaml文件中添加相关依赖即可。但是,因为Google的库在国不能访问,需要添加环境变量指定库镜像才可以使用。

 

  export PUB_HOSTED_URL=https://pub.flutter-io.cn
  export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

 

Flutter框架特性

 

在代码实现上,Flutter并没有Android的findViewById,页面布局是通过有状态Widget(StatefulWidget)和无状态Widget(StatelessWidget)实现的。顾名思义,无状态的Widget就是一些不可以改变的UI,而需要改变的UI则是通过有状态的Widget来实现,并且通过setStatus()来刷新UI的状态:

 

...
Text(
    '$_counter',
    style: Theme.of(context).textTheme.display1,
),
...
setState(() {
  _counter--;      
});

 

这种方法很简单的实现了动态化的UI及Android长久以来希望达到的目标 —— data binding。

 

四、Flutter待完善的方面及使用中遇到的问题

 

Flutter至今没有反射

 

Dart并不是没有反射,dart:mirrors就具有Mirror概念的反射。在安全、分发、部署方面,Mirror-Base具有很大优势。但是反射生成的代码冗长,会使Flutter编译过后的包很大。Flutter通过将Dart编译成原生代码本身就会增加包大小,再加上反射的话包大小更会进一步扩大。所以Flutter团队在现阶段并没有开放dart:mirrors的使用。

 

没有反射也就意味着Json String to Model 也没有办法完成,对于这一点,官方也比较无奈。至今Flutter中Dart只支持将JsonString 转化为Map,然后再由开发者手写代码将key值一一对应到相应的字段上。

 

/**
    "result": {
    "status": "ALREADY",
    "scur": "CNY",
    "tcur": "EUR",
    "ratenm": "人民币/欧元",
    "rate": "0.127839",
    "update": "2018-07-13 23:28:01"
    }
 */

///
class ExchangeResult {
  final String status;
  final String scur;
  final String tcur;
  final String ratem;
  final String rate;
  final String update;

  ExchangeResult(this.status, this.scur, this.tcur, this.ratem, this.rate,
      this.update,);

  ExchangeResult.fromJson(Map<String, dynamic> json)
      : status = json['status'],
        scur = json['scur'],
        tcur = json['tcur'],
        ratem = json['ratem'],
        rate = json['rate'],
        update = json['update'];

}

    Map exchangeMap = json.decode(Utf8Codec().decode(response.bodyBytes));
    var resultModel = new ExchangeResult.fromJson(exchangeMap);

 

Dart-langhttp请求response解码问题

 

Http请求返回的response中Header会包含编码格式charset=utf-8,官方给出的Demo如下:

 

 var  dataURL = "http://api.k780.com?app=finance.rate&scur=CNY&tcur=GBP&appkey=35134&sign=fb020c3129435bb5ff21b7113e9cb1c1&format=json";
    var response = await http.get(dataURL);
    print(response.body);

 

看起来是非常简单的实现了异步请求服务,但是如果返回的charset后面多加了一个";"的话 (charset=utf-8;),http client就不会自动根据header中的charset解析,会返回错误:

 

[ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
Error on line 1, column 33: Invalid media type: expected

 

所以,如果要解析返回的json string,必须要指定UTF8字符解析response才可以:

 

print(Utf8Codec().decode(response.bodyBytes));

 

Flutter并不能指定Dart lang version

 

安装Flutter的同时也会安装Dart lang SDK,集成在Flutter的SDK中的$FLUTTER_SDK/bin/cache/dart-sdk。假如你发现一个Dart lang bug,那就需要更改DartSDK的代码,但是这个修正并不能让你马上使用。因为Flutter与Dart lang SDK 的version是一一绑定好的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值