赞
踩
9 月 9 日,第 17 期 Agora Talk 邀请到了《Flutter开发实战详解》的作者郭树煜(Github ID:CarGuo) 。熟人都会叫他大喵。他在直播中与大家分享了一些 Flutter 上有意思的话题,包括 Flutter 的现状、Widget、Element、混合开发等。本文由他整理自本次的演讲。大家也可以点击「阅读原文」观看视频回放。
一、Flutter 的现状和钱途
之所以聊这个话题,因为比起后面的部分可能大家会更关心这个,Flutter 自出道以来可以说是突飞猛进,因为它独特的底层设计和开发语言,但是作为一个跨平台框架它也是备受追捧和争议。
问题回答放到了最后面
欲扬先抑,在说好处之前先聊下它的争议,作为新的东西出来总是有人安利有人抵触,这是正常的现象,特别是这里面涉及到学习成本的问题,所以 “Flutter 垃圾,不用学” 的言论也遇到过不少,不过抛开带节奏的,这里面吐槽最多的应该就是 :
Flutter 嵌套恶心;
使用 dart 不使用 js ;
这两个确实是最多的争议,无可厚非对比 js 帝国 dart 确实显得“弟弟”,而且 Flutter 的嵌套写法确实不讨喜。但是这个其实可以解释过去,我们后面会解释为什么 Flutter 可以这样嵌套,要怎么去理解和使用这个嵌套。
另外 Flutter 在混合开发上的难度确实会比 RN 更麻烦一些,这是它天生的跨平台设计造成的,Flutter 最核心的优势就是在于它不走寻常路的跨平台模式,简单来说对于平台而言,只需要平台提供画板(Surface
)就可以了,剩下都是由 Flutter Engine 来完成绘制,这让 Flutter 的 UI 和平台的关联性很低,但是也造成了 Flutter 在混合开发时需要接入原生控件的难度。
另外 Flutter 在第三方支持和内存占用上的优势目前也不够明显,当然目前 pub 上的第三方插件其实已经比较丰富了,大部分时候能满足开发需求。另外就是内存占用上会多一点,当然这个内存占用属于 Native 的,也就是不会因为超过 JVM 的内存限制而导致 OOM 的问题,不过会比平时的应用起步占用的内存更多一点。
最后就是热更新,Flutter 的热更新就不要想了,至少是官方的热更新或者是直接下发二进制代码的支持就不用想了,因为无论是从谷歌或者苹果的角度出发,这都是禁止的底线。当然现在有各种通过下发文本或者模版去实现类似 code-push 的热更新,Android 平台也可以用动态化的方式下发,因为 Android 上 Flutter 在 release 模式是打包成 so 动态库的,当然这就是另外一个话题了,它究竟好不好我就不评价了~
说完争议我们聊聊优势,现实中没有什么框架是完美的,大部分时候主要在于它能不能解决你的需求,从我这两年多使用 Flutter 的感觉上来说,Flutter 给我最大的感觉就是舒服!
过去 5 年里在跨平台相关领域我使用过 Cordova、 RN、Weex 等框架,他们无疑是提高了我在某些场景下代码的复用,但是在 UI 的兼容和性能上总是需要耗费额外的精力来处理一些琐碎的问题。
对比以前在 RN 和 Weex 上,时不时遇到:“在 Android 端调整完样式后,在 iOS 端不生效或者异常的情况”,这是因为 RN 等框架需要依赖原生控件,而原生的控件在不同版本和平台上都存在一定的差异化。
Flutter 得益于它特有的跨平台渲染机制,在 UI 兼容性和代码复用上 Flutter 让我感觉很舒服,因为框架自带的 Widget 渲染是平台无关的,基本上调样式在一个平台上适配完了,另外的平台也不会有什么问题。
这也进一步提高了代码的复用率。
Flutter 在 release 模式下的性能体验还很不错,记住这里重点记得是 release 而不是 debug,并且不是在虚拟机上的性能,这个官方也提示过。
关于性能对比的部分可以在我的掘金或者公众号也有相关的文章。
说起这个聊一个题外话,关于跨平台框架存在的意义,我以前在面试的时候就遇到过面试官这样的问题:我本身就会 Java
和 Objective-C
, 直接写各平台的代码不是更好吗?当然好,这样的性能和兼容性肯定最有保证,但是逻辑上是冗余的,跨平台的主要优势在于代码逻辑的复用,减少各平台同一逻辑,因人或者因时而异的开发成本,也可以不同平台开发人员的扯皮时间。
当然我相信每一个流行框架在性能上都不会有太大问题,更多的是使用框架的场景和方式对不对,另外 Flutter 其他的优势在于官方对于 Web 、 Linux 、Win、MacOS 平台的支持和推进速度也很快。
至于 Flutter 现在在国内投入使用的现状,大家可以看看我在6月份时发过的一篇文章: 《国内大厂在移动端跨平台的框架接入分析》,文章分析了 53 个大厂的应用样本,其中发现有 Flutter 使用的有近 19 个,这里面还不包括使用了动态化下发的。
全球范围内的话,谷歌年初统计 Flutter 使用者数量排名前五的地区是印度、中国、美国、欧盟和巴西,所以本质上在争议中 Flutter 的普及还是很快的。
另外目前各大平台的内容产出,相信大家也可以关注到字节跳动、阿里、美团等平台都在 Flutter 上都有对应的投入,同时 Flutter 如今除了 Android
和 iOS
平台之外,各大厂对于 Web
、MacOS
、Linux
、Win
平台的支持也在推动,因为 Flutter 在类似小程序、2B的商业应用场景上它很实用,之前在各种渠道上和钉钉、字节等相关的技术人员有过接触,他们都在 Flutter 跨多平台上有着各种各样的需求,大家可以相信 Flutter 目前的钱途还是可以放心的。
这里要补充一点:纯 Flutter 的岗位还是比较少的,它更多是作为一项加分或者竞争项的能力存在,因为跨平台的应用解决的大部分是 UI 上的问题,剩余的很多涉及平台的逻辑还是需要所在平台的能力去解决。妄想一劳永逸的跨平台现阶段是看不到的,各个平台也不会允许一个第三方的框架独霸,就比如目前微软在 Flutter 平台的支持是就不是很积极,所以如果看到什么 “Flutter 一统天下,xxx要凉” 的文章,大家看个乐就好了。
那接下里就开始介绍 Flutter 本身的一些有意思的设定。
二、Flutter 的表世界:Widget
相信了解过 Flutter 的这张图大家一定很熟悉,可以看到作为 UI 框架 Flutter 抽象出了很多层来实现从平台到开发过程中的解耦。当然日常普通开发我们只会关心 Widget
,而 Widget
上的 Material
和 Cupertino
是官方为我们封装的不同风格的 Widget
,这些控件基本上都是和平台无关的。
那要理解 Flutter 就要理解 Flutter 中最灵魂的设定:
Flutter 内一切皆
Widget
,Widget
是不可变的(immutable),每个Widget
状态都代表了一帧。
理解这段话是非常重要的。
这句话也是很多一开始接触 Flutter 的开发者比较迷惑的地方,因为 Flutter 中所有界面的展示效果,在代码层面都是通过 Widget
作为入口开始。 Widget
是不可变的,说明页面发生变化时 Widget
一定是被重新构建, Widget
的固定状态代表了一帧静止的画面,当画面发生改变时,对应的 Widget
一定会变化。
是不是有点绕了?
怎么理解呢,举个简单例子,如下代码所示定义了一个 TestWidget
,TestWidget
接受传入的 title
和 count
参数显示到 Text
上,同时如果 count
大于 99,则只显示 99。
- /// Warnning
- /// This class is marked as '@immutable'
- /// but one or more of its instance fields are not final
- class TestWidget extends StatelessWidget {
-
-
- final String title;
-
-
- int count;
-
-
- TestWidget({this.title, this.count});
-
-
- @override
- Widget build(BuildContext context) {
- this.count = (count > 99) ? 99 : count;
- return Container(
- child: new Text("$title $count"),
- );
- }
- }
这段代码看起来没有什么问题,也可以正常运行,但是在编译器上会有 “This class is marked as '@immutable',but one or more of its instance fields are not final”
的提示警告,这是因为 TestWidget
内的 count
成员变量没有加上 final
声明,从而在代码层面容易产生歧义。
因为前面说过
Widget
是immutable
,所以它的每次变化都会导致自身被重新构建,也就是TestWidget
内的count
成员变量其实是不会被保存且二次使用。
如上所示代码中 count
成员没有 final
声明,所以理论是可以对 count
进行二次修改赋值,造成 count
成员好像被保存在 TestWidget
中被二次使用的错觉,容易产生歧义,比如某种情况下的widget.count
,所以这个 final
就可以看出来 Widget
的不可变逻辑。
那这时候可能有人会问:每次 Widget
都重构,那怎么保存状态?这样 Widget
的设计会不会性能很差?
我们先讲状态保存,把 StatelessWidget
换成 StatefulWidget
,然后把 build
方法放到 State
里,State
里的 count
就可以就可以实现跨帧保存。
- class TestWidgetWithState extends StatefulWidget {
- final String title;
-
-
- TestWidgetWithState({this.title});
-
-
- @override
- _TestWidgetState createState() => _TestWidgetState();
- }
-
-
- class _TestWidgetState extends State<TestWidgetWithState> {
- int count;
-
-
- @override
- Widget build(BuildContext context) {
- this.count = (count > 99) ? 99 : count;
- return InkWell(
- onTap: () {
- setState(() {
- count++;
- });
- },
- child: Container(
- child: new Text("${widget.title} $count"),
- ),
- );
- }
- }
到这里我们知道了 Widget
在 Flutter 是不可变的, Widget
内的变量也不会被保存,并且需要用 final
声明避免歧义。而用了 State
之后就支持跨帧保存数据,那为什么 State
就可以跨帧保存呢? createState
得到的 State
为什么就可以跨帧?
其实看到前面代码里的 context
了吗?和它有密不可分的关系。
三、Flutter 的里世界:Element
事实上如果查看 Flutter 对于 Widget
的定义,可以看到官方对于 Widget
的定义是 Describes the configuration for an [Element].
,也就是 Element
才是 Widget
真正的体现,Widget
更像是配置文件的逻辑,也就是我们通过嵌套 Widget
去配置想要显示的逻辑,然后 Flutter 根据配置文件去进行渲染,通过配置文件变了,所以配置文件可以经常改变,只要真正工作的节点不要频繁重建就好了。
这时候你再想想 Widget
的嵌套,em·····所以 Flutter 上大部分时候我们写的是都是配置代码?其实看 Flutter 里的 Widget
默认都是很简单的功能,就像是一个个简单的配置单元,它并不是什么真实的 View
,真正干活的是从 Element
开始。
而 Element
大家可能会比较陌生,因为 Flutter 开发中比较少直接使用 Element
。但是如果说到 BuildContext
相信大家就不会陌生,因为 BuildContext
在开发中经常被使用到,而 Element
恰好就是 BuildContext
的实现类,你使用的 BuildContext
其实就是 Element
,所以日常开发中其实大家一直都会使用到 Element
。
就比如正常我们使用 StatelessWidget
和 StatefulWidget
都有它对应的 Element
,而 StatelessElement
和 StatefulElement
都属于 ComponentElement
, 也就是容器,这种 Element
的特点是没有 RenderObject
,就是它们没有绘制功能,所以我们开发中一种用它们去作为承载容器,然后在 build
方法里去组织我们的 UI ,其实可以看到 build
方法里的 context
就是 this
,也就是 Element
自己。
所以为什么State
可以跨帧保存数据,就是因为它和 Element
的关系!
一个 Widget
被首次加载时,会先创建出它对应的 Element
,就比如上面介绍的 StatefulWidget
,会对应先创建出 StatefulElement
;而 StatefulElement
被调用的时候,会先通过 widget.createState()
创建出 State
,之后 _state
就被保存在 Element
,所以 _state
可以跨 Widget
保存,保存的位置就是在 Element
里面。
所以 StatfulWidget
的 build
方法不在 Widget
里而在 State
里面。
在简化的源码里可以看到, _state
就被保存在 Element
中,然后 Widget
是在创建和更新时被赋予到 state 里的,所以 _state
可以跨帧保存,而 Widget 可以不断创建。所以 State 具备声明周期,而 Widget 不具备。
理解了吗?Element
才是一个真正的工作节点,而 Widget
仅仅是一个“配置信息”。而 State
是跟着 Element
走,保存在 Element
中,所以它可以用于保存和恢复状态。
因为 Widget
仅仅是“配置信息” ,所以它可以每次改变时都被重构,而 Element
才是真正的节点,它每次读取 Widget
的状态去配置和绘制,但是 Element
并不是每次都重新创建,而是在 Widget
首次被加载时通过 createElement
创建出来。
什么是首次加载呢?通俗点来说就是这个 Element
在 mount
的时候。
举一个例子,如下面代码所示:
在 DemoPage
中通过 Scaffold
的 body
嵌套了 StatePage
;
给 StatePage
传递了 data
为 “init”
的字符串,然后通过 _StatePageState
的构造方法传递 data
,此时页面正常显示;
当用户点击 floatingActionButton
时,会执行 setState
触发页面更新,并且把 _DemoPageState内
的 data
改变为 “Change By setState”
;
但是最终结果 StatePage
界面依然显示 “init”
。
- class DemoPage extends StatefulWidget {
- @override
- _DemoPageState createState() => _DemoPageState();
- }
-
-
- class _DemoPageState extends State<DemoPage> {
-
-
- String data = "init";
-
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- /// 将data数据传递给StatePage
- body: StatePage(data),
- floatingActionButton: FloatingActionButton(
- onPressed: (){
- /// 改变data数据
- setState(() {
- data = "Change By setState";
- });
- },
- ),
- );
- }
- }
-
-
- class StatePage extends StatefulWidget {
-
-
- final String data;
-
-
- StatePage(this.data);
-
-
- @override
- _StatePageState createState() => _StatePageState(data);
- }
-
-
- class _StatePageState extends State<StatePage> {
-
-
- String data;
- data是从StatePageState的构造方法传入
- _StatePageState(this.data);
-
-
- @override
- Widget build(BuildContext context) {
- return Container(
- child: new Center(
- child: new Text(data ?? "")
- ),
- );
- }
- }
-
-
为什么 StatePage
界面依然显示 “init”
?因为虽然 StatePage
这个 Widget
的 data
发生了改变,但是 StatePage
的 createState()
方法只在第一次被加载时调用,对应创建出来的 state
被保存在 Element
中,所以 _StatePageState
中的 data
只在第一次时被传递进来。
关键就在于
_StatePageState
创建时data
只被赋数一次。
如果这时候要让 StatePage
的 data
正常改变,就需要使用 widget.data
。看出来了没,如果把 State
放到 Element
的级别上来看,通过 widget.data
去更新的逻辑,是不是 Widget
看起来就很像是一个配置文件。
所以对应前面的源码,是不是对应上了,State
保存在 Element
中,而 Widget
被不断更新到 State
里,所以 State
里用 widget.xxxx
可以获取到每次更新的状态。
也就是在写 Flutter 时, 把 Widget
当作配置文件写就对了。
介绍了 Widget
和 Element
,那最后在问一个问题:Widget
和 Element
的对应关系是怎么样?
四、Flutter 的里表关系
从官方的注释里可以看出,Widget
做为配置文件,和 Element
是应该是一对多的关系,因为真实工作的其实是 Element
树,而 Widget
作为配置文件,可能会在多个地方被复用。这个理解是不是感觉很奇怪?换个角度解释下:一个配置文件被多个运行的实体引用。
举个例子,如下代码所示,通过运行后可以看到 textUseAll
在多个地方被 inflated
并且正常渲染出 3333333
的效果。
这是因为 textUseAll
仅仅是作为配置文件,所以被多个 Element
加载并不会出现问题,因为这种情况并不是一个真正的 "View" 被多个地方添加。
final textUseAll = new Text( "3333333", style: new TextStyle(fontSize: 18, color: Colors.red), ); class MyHomePage extends StatelessWidget { void goNext(context) { Navigator.of(context).push(MaterialPageRoute(builder: (context) { return ShowPage(); })); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Container( color: Colors.blue, height: 80, child: Stack( children: <Widget>[ new Center( child: Row( crossAxisAlignment: CrossAxisAlignment.center, textBaseline: TextBaseline.alphabetic, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ textUseAll, Text( ' GSY ', style: TextStyle(fontSize: 36, fontFamily: "Heiti"), ), textUseAll, ], ), ), ], ), )), floatingActionButton: FloatingActionButton( onPressed: () { goNext(context); }, tooltip: 'Next', child: Icon(Icons.add), ), ); } } class ShowPage extends StatelessWidget { void goNext(context) { Navigator.of(context).push(MaterialPageRoute(builder: (context) { return ShowPage(); })); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Container( child: new Center( child: textUseAll, ), ), floatingActionButton: FloatingActionButton( onPressed: () { goNext(context); }, tooltip: 'goNext', child: Icon(Icons.add), ), ); } }
当然,当你给上面的 textUseAll
的 Text
配置上 GlobalKey
就是会是另外一个故事,因为 GlobalKey
会让一个 Widget
变成“全局单例” ,具体解释这里就不展开,感兴趣的可以自己尝试下。
最后补充一点: Flutter 中除了 Widget
、Element
之外,还有 RenderObject
、Layer
等才能组成 Dart 层的渲染闭环,这里主要介绍了 Widget
和 Element
的关系,是因为 Widget
和 Element
的理解是 Flutter 中入门时最容易忽略的,Flutter 对于 Widget
的设计让我们写代码时更像是在写配置文件。
不是所以
Element
都有RenderObject
,比如前面说过的CompmentElement
。
其中最具备代表性的就是 Container
,官方给我们做了一个很通用的配置模板,通过配置通用模版和业务模板,是我们减少嵌套的写法,这可能有点傻,但是这就是 Flutter 的组织方式之一。另一方面讲因为 Widget
本身很轻,作为配置文件前期也不要太担心嵌套性能,因为 Flutter 里的 Widget
很多时候工作逻辑很单一,比如一个 Aligin
到绘制层也就是改变一下绘制时的位置而已。
所以在使用 Flutter 时,对于Widget
的理解其实不应该把它看成 View
,而是应该把它当作配置文件看待;而理解一个 Widget
的实现逻辑,则是应该看它的 Element
和 RenderObejct
。
当然,虽然通过前面的介绍我们知道 build
方法很轻和 Widget
很轻,但是没必要的调用肯定是能省则省!那么怎么省呢?那就是通过 BuildContext
,因为 BuildContext
就是 Element
的抽象,说白了就是利用 ComponentElement
这种容器来隔离,在更新时减少节点的判断逻辑,从具体上说也可以使用 Builder
等简单的控件来实现,通过 BuildContext
就可以更新的颗粒度,比如状态管理框架 provider
中的 Consumer
。
五、Flutter 中混合开发的变迁史
介绍完 Flutter 基本的设定理念,接下来结果一个 Flutter 中的混合开发的问题,也就是 PlatformView
,之所以介绍这个,是因为如果想要在 Flutter 上接入原生平台的界面能力,比如 WebView
,MapView
等等就会需要使用上它,因为 Flutter 本身是通过 Flutter Engine 去绘制控件的,也就是它不像 RN 一样可以快速接入原生平台的控件能力。
如果想要把原生控件直接干涉到 Flutter 的渲染树里是不可能的,因为它们已经是两个完全不同的东西,Flutter 对于原生而已就像是一个单页面应用,原生只提供了一个 Surface
,剩下的由 Flutter 自己去绘制。
起初在实现时 Flutter 在 Android 和 iOS 平台采用了两种不同的实现模式,得益于 iOS 平台本身的优势, PlatformView
在 iOS 上的问题一直不是特别多,但是在 Android 平台上,因为使用的是 VirtualDisplay
这种实现方式:简单来说,就是原生控件的内容被绘制到内存里,然后 Flutter Engine 通过相对应的 textureId
就可以获取到控件的渲染数据并显示出来。
通过从 VirtualDisplay
输出中获取纹理,并将其和 Flutter 原有的 UI 渲染树混合,使得 Flutter 可以在自己的 Flutter Widget tree 中以图形方式插入 Android 原生控件。
从而引发的就是性能、触摸事件还有最最最多问题的键盘输入,特别是 WebView
自己内部还有创建和设置输入连接的逻辑,所以 WebView
和键盘的相关逻辑就显得十分脆弱,经常闹幺蛾子,如果对这部分感兴趣的可以看我写过的《Android PlatformView 和键盘问题》。
而在 1.20 开始,Android 平台的 PlatformView
多了 Hybrid Composition
模式,这个模式就和 iOS 本身的实现很相似了,他更好的解决了键盘和触摸等玄学问题。
在 Hybrid Composition
上新增了一个对象:FlutterImageView
,Hybrid Composition
上混合原生控件所需的图层合成就是通过 FlutterImageView
来实现。FlutterImageView
本身是一个普通的原生 View, 它通过实现了 RenderSurface
接口从而实现如 FlutterSurfaceView
的部分能力。
默认情况下添加的 PlatformView
就是把控件直接添加到现在 Flutter 的上面,其实这并没有什么高大上的,就是在原生的 FlutterView
上面 addView 而已。但是当你在 PlatformView
的上面继续覆盖一个 Flutter 自己渲染的控件的话,那么 FlutterImageView
就出场了。
通过 FlutterImageView
实现了 Surface
和 Surface
之间的堆叠,其中 FlutterImageView
的 Surface
来自于ImageReader
,ImageReader
可以简单理解为就是能够存储 Image
数据的对象。
而如果他们不在一个区域内,那么就会各自使用自己的 FlutterImageView
,如果在一个区域内,就会合并渲染逻辑,其实效果上就是不同的 Surface
嵌套了堆叠了。
感兴趣的可以看《Flutter 1.20 下的 Hybrid Composition 深度解析》
六、最后
最后再聊聊开源和学习,要如何去学习 Flutter 。
因为我也有几个维护了好些年的开源项目,我发现很多问题 60% 都可以通过文档解决,剩下 30% 能通过源码解决,在播放器项目上我就遇到过好多有拿着 html 链接问我为什么播放器不能播放的问题,或者基本是支不支持某个 API,这类问题其实是最多的。
所以说阅读源码很重要,源码才是开源项目的灵魂,如果只停留在 API 使用阶段,那么就会发现怎么用都不顺手,这也是开发者会感觉觉一字型螺丝刀拧十字螺丝时好时坏的逻辑。
学 Flutter 也是,其实 Flutter 本身并不复杂,理解了它的 Widget
和 Element
之间的关系,甚至后面 RenderObject
和 Layer
的关系,之后通过源码你就会发现很多需求和问题很好解决了。虽然面向单纯的 API 编程会比较舒服,当时阅读源码这个习惯也会成为一个良好的财富,是如何把别人的东西变成你的能力,而不是拿着别人的螺丝刀无从下手的虚无感。
问题
有的,Hot UI
就是这样的功能,从1.12开始 Flutter 官方就开始开发这样的工具,在官方的 HotUI-Getting-Started-instructions 中可以看到相关的描述:This feature is currently experimental. To enable, go to Preferences > Languages & Frameworks > Flutter Then check "Enable Hot UI" under "Experiments". 目前该功能还处于实验阶段,在 Android Studio 的设置中,如图所示底部勾选启动这个功能,但是对于稳定版目前预览的 Screen mirror
处于 coming soon
的状态。
Flutter 性能好,是因为它的跨平台实现方法不需要桥接原生控件,就如同游戏引擎,Flutter 界面都是由 Engine 绘制,而 Engine 绘制使用的 Skia 工具, Android 原生也是使用 SKia 绘制,所以它们的性能会很接近。
Flutter 其实在 Web 实现上是存在两种 Canvas 实现 :DomCanvas
和 CanvasKi
,
DomCanvas
用 Dom
来模拟 OpenGL
的绘制,而 CanvasKit
是使用 WebAssembly
和 WebGL
在浏览器中渲染 Skia 绘制命令,后者性能更好但是兼容性还有待商榷,所以目前 Web 可能还是会 Dart2JS 的实现比较多。
另外 dart:js library 是一套底层支持 Dart 和 JS 交互的官方库,支持 Web 上 Dart 和 JavaScript 的交互处理。
不是这么说,只是说 Flutter 主要是解决了 UI 的跨平台,但是不能说 Flutter 只能写 UI ,很多逻辑代码也可以用 Dart 写在 Flutter 内,只是 Dart 和 JS 一样都是单线程的,如果太过复杂的操作,建议用isolate
去处理。
另外涉及平台的硬件或者特性的某些功能,只能通过插件去解决了。
其实 Fish Redux 是在 Redux 的基础上进一步增加了细致化和可配置化的能力,它相对会复杂一点,但是提高了服用率和控制的细腻度,但是问题也在于它的入侵性比较强,接入后基本上后期你想更换状态管理框架会比较麻烦,所以看个人取舍了。
最主要原因是 Flutter 的不需要桥接原生控件去渲染,它直接使用把绘制信息提交给 Skia 绘制出来,而原生 Android 也是通过 Skia 绘制,所以本质上大家的流程是一致的,所以性能上会更加接近原生。另外在 iOS 上 Flutter 在 iOS 上对于支持 Metal
的设备将使用 Metal
进行渲染s。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。