移动开发新利器|一文深入了解Flutter界面开发

  阿里妹导读:谈到移动端开发,大家心中肯定会涌现出一系列名词:iOS、Android、Weex,H5... 那为何还使用 Flutter?其实,Flutter 通过自建绘制引擎,具备与 Native 媲美的性能指数,且有很好的两端一致性,因此 Flutter 提供了一种新的可选项。闲鱼宝贝详情页实践上线也证明了这点,可以在性能无损前提下降低 iOS&Android 开发成本。

为都江堰等地区用户提供了全套网页设计制作服务,及都江堰网站建设行业解决方案。主营业务为成都网站制作、成都网站设计、外贸营销网站建设、都江堰网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!

  本文由闲鱼技术团队出品。它将为你深入介绍 Flutter framework 关于视图树的创建与管理机制、布局、渲染的原理,以及 Flutter 布局与渲染相关性能优化的设计思路的文章。同时介绍在使用 Flutter 开发过程中,遇到的一些坑和相应的解决方案。

  Flutter 框架简介

  1. 跨平台应用的框架,没有使用 WebView 或者系统平台自带的控件,使用自身的高性能渲染引擎(Skia)自绘。

  2. 界面开发语言使用 dart,底层渲染引擎使用C, C++。

  3. 组合大于继承,控件本身通常由许多小型、单用途的控件组成,结合起来产生强大的效果,类的层次结构是扁平的,以***化可能的组合数量。

  Rendering Pipeline

  本文主要介绍 build、layout、paint 的三个阶段。

  视图树

  Widget&Element&RenderObject

  Flutter 视图树包含了三种树,上图只是介绍了三颗树的基础 class 的对应关系和功能介绍。

  创建树

  1. 创建 widget 树

  2. 调用 runApp (rootWidget),将 rootWidget 传给 rootElement,做为 rootElement 的子节点,生成 Element 树,由 Element 树生成 Render 树

  • Widget:存放渲染内容、视图布局信息,widget 的属性***都是 immutable (如何更新数据呢?查看后续内容)

  • Element:存放上下文,通过 Element 遍历视图树,Element 同时持有 Widget 和 RenderObject

  • RenderObject:根据 Widget 的布局属性进行 layout,paint Widget 传人的内容

  更新树

  为什么 widget 都是 immutable?

  Flutter 界面开发是一种响应式编程,主张 simple is fast,Flutter 设计的初衷希望数据变更时发送通知到对应的可变更节点(可能是一个 StatefullWidget 子节点,也可以是 rootWidget),由上到下重新 create widget 树进行刷新,这种思路比较简单,不用关心数据变更会影响到哪些节点。

  widget 重新创建,element 树和 renderObject 树是否也重新创建?

  widget 只是一个配置数据结构,创建是非常轻量的,加上 Flutter 团队对 widget 的创建/销毁做了优化,不用担心整个 widget 树重新创建所带来的性能问题,但是 renderobject 就不一样了,renderobject 涉及到 layout、paint 等复杂操作,是一个真正渲染的 view,整个 view 树重新创建开销就比较大,所以答案是否定的。  

  树的更新规则

  1. 找到 widget 对应的 element 节点,设置 element 为 dirty,触发 drawframe, drawframe 会调用 element 的 performRebuild ()进行树重建

  2. widget.build () == null, deactive element.child,删除子树,流程结束

  3. element.child.widget == NULL, mount 的新子树,流程结束

  4. element.child.widget == widget.build () 无需重建,否则进入流程5

  5. Widget.canUpdate (element.child.widget, newWidget) == true,更新 child 的 slot,element.child.update (newWidget)(如果 child 还有子节点,则递归上面的流程进行子树更新),流程结束,否则转6

  6. Widget.canUpdate (element.child.widget, newWidget) != true(widget 的 classtype 或者 key 不相等),deactivew element.child,mount 新子树

  注意事项:

  1. element.child.widget == widget.build (),不会触发子树的 update,当触发 update 的时候,如果没有生效,要注意 widget 是否使用旧 widget,没有 new widget,导致 update 流程走到该 widget 就停止了。

  2. 子树的深度变化,会引起子树重建,如果子树是一个复杂度很高的树,可以使用 GlobalKey 做为子树 widget 的 key。GlobalKey 具有缓存功能。

  如何触发树更新

  1. 全局更新:调用 runApp (rootWidget),一般 flutter 启动时调用后不再会调用。

  2. 局部子树更新, 将该子树做 StatefullWidget 的一个子 widget,并创建对应的 State 类实例,通过调用 state.setState () 触发该子树的刷新。

  Widget

  StatefullWidget vs StatelessWidget

  1. StatelessWidget:无中间状态变化的 widget,需要更新展示内容就得通过重新 new,Flutter 推荐尽量使用 StatelessWidget。

  2. StatefullWidget:存在中间状态变化,那么问题来了,widget 不是都 immutable 的,状态变化存储在哪里?Flutter 引入 state 的类用于存放中间态,通过调用 state.setState ()进行此节点及以下的整个子树更新。

  State 生命周期  

  1. initState (): state create 之后被 insert 到 tree 时调用的

  2. didUpdateWidget (newWidget):祖先节点 rebuild widget 时调用

  3. deactivate ():widget 被 remove 的时候调用,一个 widget 从 tree 中 remove 掉,可以在 dispose 接口被调用前,重新 instert 到一个新 tree 中

  4. didChangeDependencies ():

  5. 初始化时,在 initState ()之后立刻调用

  6. 当依赖的 InheritedWidget rebuild,会触发此接口被调用

  7. build ():

  8. After calling [initState].

  9. After calling [didUpdateWidget].

  10. After receiving a call to [setState].

  11. After a dependency of this [State] object changes (e.g., an[InheritedWidget] referenced by the previous [build] changes).

  12. After calling [deactivate] and then reinserting the [State] object into the tree at another location.

  13. dispose ():Widget 彻底销毁时调用

  14. reassemble (): hot reload 调用

  注意事项:

  1. A页面 push 一个新的页面B,A页面的 widget 树中的所有 state 会依次调用 deactivate (), didUpdateWidget (newWidget)、build ()(这里怀疑是 bug,A页面 push 一个新页面,理论上并没有将A页面进行 remove 操作),当然从功能上,没有看出来有什么异常。

  2. 当 ListView 中的 item 滚动出可显示区域的时候,item 会被从树中 remove 掉,此 item 子树中所有的 state 都会被 dispose,state 记录的数据都会销毁,item 滚动回可显示区域时,会重新创建全新的 state、element、renderobject。

  3. 使用 hot reload 功能时,要特别注意 state 实例是没有重新创建的,如果该 state 中存在一下复杂的资源更新需要重新加载才能生效,那么需要在 reassemble ()添加处理,不然当你使用 hot reload 时候可能会出现一些意想不到的结果,例如,要将显示本地文件的内容到屏幕上,当你开发过程中,替换了文件中的内容,但是 hot reload 没有触发重新读取文件内容,页面显示还是原来的旧内容。

  数据流转

  从上往下

  数据从根往下传数据,常规做法是一层层往下,当深度变大,数据的传输变的困难,Flutter 提供 InheritedWidget 用于子节点向祖先节点获取数据的机制,如下例子:

  child 及其以下的节点可以通过调用下面的接口读取 color 数据:

  说明:BuildContext 就是 Element 的一个接口类

  context.inheritFromWidgetOfExactType (FrogColor)其实是通过 context/element 往上遍历树,查找到***个 FrogColor 的祖先节点,取该节点的 widget 对象。

  从下往上

  子节点状态变更,向上上报通过发送通知的方式

  • 定义通知类,继承至 Notification

  • 父节点使用 NotificationListener 进行监听捕获通知

  • 子节点有数据变更调用下面接口进行数据上报

  闲鱼 Flutter 的界面框架设计

  

  

  Layout  

  Size 计算

  parent 传入约束条件,在 dramframe 的 layout 阶段,child 根据自身的渲染内容返回 size。

  问题:在 build ()阶段获取不到 size,很多时候需要提前知道部分 widget size 来进行布局,解决方案当 widget 在对应 renderobject 的 layout 阶段之后,发送一个 LayoutChangeNotification,参考 SizeChangedLayoutNotifier class,但是 SizeChangedLayoutNotifier 没有上报 init layout size,可以自己参考这个实现封装一个 Notifier。

  Offset 计算

  1. renderObject 拿到计算好的 size,再加上一些布局属性(align、paddig)等,计算 child 相对 parent 的 offset。

  2. offset 存放在每个 child renderObject 的 BoxParentData 中。

  3. 当 parent 拥有 mutil children 时,BoxParentData 还用来存 children 兄弟节点之间的遍历顺序。  

  Relayout boundary

  renderObject 在 layout 阶段做了 Relayout boundary 的优化,当子树进行 relayout 时,满足下面三种中的一种:

  • parentUsesSize == false

  • sizedByParent == true

  • constraints.isTight

  那么该 renderObject 设置为 Relayout boundary,也就是该 renderObject 的重新 layout 不触发 parent 的 layout,一般情况下开发人员不需要关心 Relayout boundary,除非是使用 CustomMultiChildLayout。

  Paint

  Layer

  iOS 的每一个 UIView 都有一个 layer,Flutter 的 render object 不一定存在 layer,一般情况下一个 renderObject 子树都渲染在一个 layer 上,那么什么 renderObject 具有 layer,子 renderObject 怎么渲染到这个 layer?

  1. 当一个 renderObject 的
或者

,renderOject 会有对应的 compositing layer。

  2. 子 renderObject 会对目标 layer 返回对应的 offsetLayer, 目标 compositing layer 再根据 offset 合成一个渲染的纹理 buffer。

  Repaint Boundary

  类似 Relayout boundary,Paint 阶段也有 Repaint Boundary,目的和 layout 一样,就是对应子树的 paint 不会导致外部的 repaint,但是 Relayout boundary 需要开发人员自己设置,使用 RepaintBoundary widget 进行设置,ListView 在渲染的 item 默认都是使用了 RepaintBoundary,显而易见 ListView 的 children 之间都是相互独立的。Flutter 建议复杂的 image 渲染使用 RepaintBoundary,image 的渲染需要 io 操作,然后解码,***渲染,使用 RepaintBoundary 可以进行 gpu 的缓存,但是不一定就会缓存,engine 会判断这个 image 是否足够复杂,毕竟 gpu 缓存还是非常珍贵的,同时 RepaintBoundary 还会对一些反复渲染的 layer 进行缓存处理(反复渲染 3 次及以上,这个是 Flutter 的视频中提到的)。

  结语

  Flutter 还处于 Beta 阶段,有些界面编程的接口设计还不够成熟,相比 iOS 和安卓生态还很不成熟,需要我们共同的创建,Flutter 提供的调试工具相比一开始接触的时候,已经完善很多,让我们给 Flutter 更多的耐心和包容,期待 Flutter 越来越完善。

  参考资料

  • https://github.com/flutter/flutter

  • https://github.com/flutter/engine

  • https://flutter.io/

当前题目:移动开发新利器|一文深入了解Flutter界面开发
文章链接:http://www.shufengxianlan.com/qtweb/news48/551898.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联