从架构到源码:一文了解Flutter渲染机制

简介:Flutter从本质上来讲还是一个UI框架,它解决的是一套代码在多端渲染的问题。在渲染管线的设计上更加精简,加上自建渲染引擎,相比ReactNative、Weex以及WebView等方案,具有更好的性能体验。本文将从架构和源码的角度详细分析Flutter渲染机制的设计与实现。较长,同学们可收藏后再看。

image.png

写在前面

跨平台技术由于其一码多端的生产力提升而表现出巨大的生命力,从早期的Hybrid App到ReactNative/Weex、小程序/快应用,再到现在的Flutter,跨平台技术一直在解决效率问题的基础上最大化的解决性能和体验问题。这也引出了任何跨平台技术都会面临的核心问题:

  • 效率:解决在多应用、多平台、多容器上开发效率的问题,一码多端,业务快跑。
  • 性能:解决的是业务的性能和体验问题。

效率作为跨平台技术的基本功能,大家都能做到。问题是谁能把性能和体验做得更好,在渲染技术这块一共有三种方案:

  • WebView渲染:依赖WebView进行渲染,在功能和性能上有妥协,例如PhoneGap、Cordova、小程序(有的小程序底层也采用了ReactNative等渲染方案)等。
  • 原生渲染:上层拥抱W3C,通过中间层把前端框架翻译为原生控件,例如ReactNative+React、Weex+Vue的组合,这种方案多了一层转译层,性能上有损耗。随着原生系统的升级,在兼容性上也会有问题。
  • 自建渲染:自建渲染框架,底层使用Skia等图形库进行渲染,例如Flutter、Unity。

Flutter由于其自建渲染引擎,贴近原生的实现方式,获得了优秀的渲染性能。

Flutter拥有自己的开发工具,开发语言、虚拟机,编译机制,线程模型和渲染管线,和Android相比,它也可以看做一个小型的OS了。

第一次接触Flutter,可以看看Flutter的创始人Eric之前的访谈《What is Flutter?》,Eric之前致力于Chromium渲染管线的设计与开发,因此Flutter的渲染与Chromium有一定的相似之处,后面我们会做下类比。

后面我们会从架构和源码的角度分析Flutter渲染机制的设计与实现,在此之前也可以先看看Flutter官方对于渲染机制的分享《How Flutter renders Widgets》。视频+图文的方式会更加直观,可以有一个大体的理解。

架构分析

image.png

架构设计

从结构上看,Flutter渲染由UI Thread与GPU Thread相互配合完成。

1)UI Thread

对应图中1-5,执行Dart VM中的Dart代码(包含应用程序和Flutter框架代码),主要负责Widget Tree、Element Tree、RenderObject Tree的构建,布局、以及绘制生成绘制指令,生成Layer Tree(保存绘制指令)等工作。

2)GPU Thread

对应图中6-7,执行Flutter引擎中图形相关代码(Skia),这个线程通过与GPU通信,获取Layer Tree并执行栅格化以及合成上屏等操作,将Layer Tree显示在屏幕上。

注:图层树(Layer Tree)是Flutter组织绘制指令的方式,类似于Android Rendering里的View DisplayList,都是组织绘制指令的一种方式。

UI Thread与GPU Thread属于生产者和消费者的角色。

流程设计

我们知道Android上的渲染都是在VSync信号驱动下进行的,Flutter在Android上的渲染也不例外,它会向Android系统注册并等待VSync信号,等到VSync信号到来以后,调用沿着C++ Engine->Java Engine,到达Dart Framework,开始执行Dart代码,经历Layout、Paint等过程,生成一棵Layer Tree,将绘制指令保存在Layer中,接着进行栅格化和合成上屏。

具体说来:
image.png

1)向Android系统注册并等待VSync信号

Flutter引擎启动时,会向Android系统的Choreographer(管理VSync信号的类)注册并接收VSync信号的回调。

2)接收到VSync信号,通过C++ Engine向Dart Framework发起渲染调用

当VSync信号产生以后,Flutter注册的回调被调用,VsyncWaiter::fireCallback() 方法被调用,接着会执行 Animator::BeiginFrame(),最终调用到 Window::BeginFrame() 方法,WIndow实例是连接底层Engine和Dart Framework的重要桥梁,基本上与平台相关的操作都会通过Window实例来连接,例如input事件、渲染、无障碍等。

3)Dart Framework开始在UI线程执行渲染逻辑,生成Layer Tree,并将栅格化任务post到GPU线程执行

Window::BeiginFrame() 接着调用,执行到 RenderBinding::drawFrame() 方法,这个方法会去驱动UI界面上的dirty节点(需要重绘的节点)进行重新布局和绘制,如果渲染过程中遇到图片,会先放到Worker Thead去加载和解码,然后再放到IO Thread生成图片纹理,由于IO Thread和GPI Thread共享EGL Context,因此IO Thread生成的图片纹理可以被GPU Thread直接访问。

4)GPU线程接收到Layer Tree,进行栅格化以及合成上屏的工作

Dart Framework绘制完成以后会生成绘制指令保存在Layer Tree中,通过 Animator::RenderFrame() 把Layer Tree提交给GPU Thread,GPU Thread接着执行栅格化和上屏显示。之后通过 Animator::RequestFrame() 请求接收系统的下一次VSync信号,如此循环往复,驱动UI界面不断更新。

逐个调用流程比较长,但是核心点没多少,不用纠结调用链,抓住关键实现即可,我们把里面涉及到的一些主要类用颜色分了个类,对着这个类图,基本可以摸清Flutter的脉络。

image.png
绿色:Widget 黄色:Element 红色:RenderObject

以上便是Flutter渲染的整体流程,会有多个线程配合,多个模块参与,抛开冗长的调用链,我们针对每一步来具体分析。我们在分析结构时把Flutter的渲染流程分为了7大步,Flutter的timeline也可以清晰地看到这些流程,如下所示:

image.png

UI Thread

1)Animate

由 handleBeiginFrame() 方法的transientCallbacks触发,如果没有动画,则该callback为空;如果有动画,则会回调 Ticker.tick() 触发动画Widget更新下一帧的值。

2)Build

由 BuildOwner.buildScope() 触发,主要用来构建或者更新三棵树,Widget Tree、Element Tree和RenderObject Tree。

3)Layout

由 PipelineOwner.flushLayout() 触发

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值