研发效率:最大化代码复用,减少多端差异的适配工作量,降低开发成本,专注业务开发,实现“write once,run everywhere”的终极目标。效率提升是贯穿整个业务的生命周期线,即便业务上线后,可持续降低后续的维护成本,加快新feature的迭代速度,这是一个持续的效率收益。当然,这里不得不说,任何一门新技术在开发启动学习阶段会有一些成本,但上手后的收益是长期的。
动态化:突破渠道的更新频率,可快速迭代新功能,这一点不仅是跨平台技术的诉求,也是Native技术必备的杀手锏,这也是评估跨端技术的一个重要考核点。
多端一致性:好产品在多端UI设计上,往往是整体风格统一,所以业务方采用原生各自独立开发完成后,还需额外花不少时间来修改UI以保证多端一致性;可见,各端独立实现开发方式,带来的效率滞后,不仅仅是Android和iOS各开发一份代码的工作量,还有双端UI的一致性对齐的工作。
性能体验:一般地,跨端技术方案拥有以上多重优势,但在性能方面比原生流畅更差些。牺牲部分体验换来效率提升,这一点也是情理之中,试想一下,跨平台技术方案同时兼得这4点,那么原生技术恐怕已退出历史舞台,早已是跨平台技术的天下,所以往往跨平台技术的性能优劣便成为核心指标。
Web技术:主要依赖于WebView的技术,功能支持受限,性能体验很差,比如PhoneGap、Cordova、小程序。
原生渲染:使用JavaScript作为编程语言,通过中间层转化为原生控件来渲染UI界面,比如React Native、Weex。
自渲染技术:自行实现一套渲染框架,可通过调用skia等方式完成自渲染,而不依赖于原生控件,比如Flutter、Unity。
第一阶段,采用WebView技术绘制界面的Hybrid混合开发技术,通过JS Bridge 将系统部分能力暴露给 JS 调用,其缺点是性能较差,功能受限,扩展性差,不适合交互复杂的场景,比如Cordova。
第二阶段,针对WebView界面性能等问题,于是绘制交还原生渲染,仅仅通过JS调用原生控件,相比WebView技术性能体验更好,这是目前绝大部分跨平台框架的设计思路,比如React Native、Weex。另外,最近小程序也比较火,第一和第二阶段的融合,依然采用WebView作为渲染容器,通过限制Web技术栈的子集,规范化组件使用,并逐步引入原生控件代表WebView渲染,以提升性能。
第三阶段,虽然通过桥接技术使用原生控件解决了功能受限问题,提升性能体验,但相比原生体验差距还是比较大,以及处理平台差异性非常耗费人力。于是Flutter提出自带渲染引擎的解决方案,尽可能减少不同平台间的差异性, 同时媲美原生的高性能体验,因此业界对 Flutter有着极高的关注度。
面对现有的如此多跨平台方案,为何当下最火的跨平台技术是Flutter,有哪些优势呢?RN、Weex均使用JavaScript作为编程语言,JavaScript作为前端开发语言,在跨平台开发中可谓大放异彩,利用web技术不仅能开发出网站,也可以开发手机端web应用和移动端应用程序,似有一统三界(Android、iOS、Web)的趋势,这就是大家常说的“大前端”时代。这些技术方案流畅度不太好,平台一致性较差,至今还没能大面积取代原生开发。Flutter是以Dart语言编写,开发体验更接近客户端,从大家使用反馈来看也是如此,Flutter开发环境这一套的流程对于前端开发来说并不太友好。Flutter的定位同样是多端一体化,但是以客户端为首,先磨平Android和iOS双端开发体验,再逐步向Web端渗透,从Flutter规划的Roadmap也能看出,Flutter for web目前仍处于预览版,Flutter客户端方向都已经如火如荼上线了不少应用。在此之前,大家常说“大前端”,对于Flutter技术,在笔者看来称之为“大移动端”更贴切,Flutter的UI框架优先支持客户端(Android/iOS)应用的同时,然后再适配Web端。移动互联网时代,不少公司都制定“移动优先”的战略,甚至只开发移动端,没有Web端。移动互联网的时代造就“大移动端”,Flutter作为一款能做到媲美原生的高性能跨平台技术方案,或许一统天下。在跨平台技术领域,只要挑战在,技术就不会停滞,伴随着技术不断演进与革新,终将走向美好。6. Flutter技术优势Flutter是彻底的跨平台方案,既没有采用WebView,也没有采用JS桥接原生控件,而是自行实现一套UI框架,在引擎底层通过Skia渲染到屏幕。对于UI之外所需要使用的移动设备自身提供的服务,比如相机、定位、屏幕触摸等,则采用Platform Channels跟原生系统通信的方式来实现。对于Flutter优势,回到前面讲到移动端技术选型的4要素,研发效率、动态性、多端一致性、性能体验,分别对应下面这一组词语。
高效率:采用dart语言编写代码,虽然刚开始上手需要点时间,但熟练后效率比较高。一套代码适用多个平台(Android、iOS、Web),以及高效的Hot Reload能快速辅助调试;
动态化:2017年3月苹果下发警告邮件,禁止JSPatch等 iOS App热更新方案,从此iOS动态化成为一个不宜公开讨论的话题。同样地,Flutter引擎在某一个官方版本对动态化做过一些尝试,但后续基于风险考虑移除,当然并没有阻碍大家对技术的探索,这里不方便展开讨论;
高一致性:实现UI像素级的控制,Flutter渲染引擎依靠跨平台Skia图形库来实现,仅依赖系统图形绘制相关的接口,比如未来Android会支持vulkan,iOS会支持metal,这些都是通过skia封装调用。可最大程度上保证不同平台的体验一致性,见下图所示。
图解:
Android原生框架,通过调用Java Framework层,再调用到skia来渲染界面;
其他跨平台方案(如RN),通过JSBridge中间层来将JS写的APP转换成相应的原生渲染逻辑,可见比Native代码增加了更多逻辑,性能逊色差于原生框架;
Flutter框架,APP通过调用Dart Framework层,再直接调用到skia来渲染界面,并没有经过原生Framework过程,可见其渲染性能并不会弱于Native技术,这是一个性能上限很高的跨平台技术。
当然,不得不说目前的Flutter确实不够尽善尽美,会存在一些不够尽善尽美之处,比如生态不够健全,包体积问题,但其该方案的上限比较高,想象空间比较大,相信更多开发者参与进来,经过更多打磨,未来会做得更好。7. 业界发展近况2017年5月Google I/O大会正式对外公布Flutter,到2018年12月发布Flutter1.0,引发全球大量的开发者和企业开始研究Flutter。StackOverflow 2019年的全球开发者文件调查中,Flutter被评选为最受开发者欢迎的框架之一,超过了TensorFlow和Node.js。目前,全球越来越多的公司已经在大家耳熟能详的知名APP中使用Flutter技术并落地,尤其国内知名互联网公司对Flutter投入度很大,社区也是非常活跃。8. Flutter未来趋势目前Flutter主要在移动Android/iOS跨双端,Flutter 的愿景是成为一个多端运行的 UI 框架,能够支持不仅仅是移动端,还包括Web、桌面、甚至嵌入式设备。在2019 Google I/O 开发者大会上推出的使用 Flutter 开发 Web 应用的框架,同年9月发布Flutter 1.9,并将Flutter web合入Flutter主仓库。从架构图看,Flutter采用同一个Dart Framework层来统一Flutter C++引擎和Web引擎,最终可以运行在Android,iOS,Browser上,从Flutter引擎代码不难看出Flutter也是支持Fuchsia操作系统。《 Android技术架构演进与未来 》文中已介绍过,Fuchsia是Google内部正在开发的一款新的操作系统,采用Flutter作为系统默认的UI框架,也就是说Flutter天然支持Fuchsia,这无疑让Flutter在众多的跨平台方案更有优势。从Fuchsia技术架构来看,内核层zircon的基础LK是专为嵌入式应用中小型系统设计的内核,代码简洁,适合嵌入式设备和高性能设备,比如IOT、移动可穿戴设备等,目前这些领域还没有标准化级别的垄断者。以及在框架层中有着语音交互、云端以及智能化等模块,由此笔者揣测未来Fuchsia率先应用在音控等智能嵌入式设备。目前大家普遍比较看好的未来两个技术就是5G和IoT时代。对于5G的需求,很大程度上是因为移动互联网发展到“IoT时代”的阶段。这个发展阶段,全球上网设备的数量可能会达到500亿个。随着5G+IOT时代的到来,现在大家比较关注的Flutter包大小也同样不再是一个问题,或许Flutter技术的生命期比客户端更长,或许Fuchsia正在驰骋IOT疆场,你所掌握的Flutter技术栈可以无缝迁移,一次弯道超车的机会。到此,介绍完跨平台技术演进以及Flutter的优势。看到这,相信你可能对Flutter技术有一定兴趣,为了能让大家快速了解Flutter内部原理而不枯燥,本文不放任何的源码,通过一系列图来帮大家从整体架构来快速理解Flutter。
Flutter Framework层:用Dart编写,封装整个Flutter架构的核心功能,包括Widget、动画、绘制、手势等功能,有Material(Android风格UI)和Cupertino(iOS风格)的UI界面, 可构建Widget控件以及实现UI布局。
Flutter Engine层:用C++编写,用于高质量移动应用的轻量级运行时环境,实现了Flutter的核心库,包括Dart虚拟机、动画和图形、文字渲染、通信通道、事件通知、插件架构等。引擎渲染采用的是2D图形渲染库Skia,虚拟机采用的是面向对象语言Dart VM,并将它们托管到Flutter的嵌入层。shell实现了平台相关的代码,比如跟屏幕键盘IME和系统应用生命周期事件的交互。不同平台有不同的shell,比如Android和iOS的shell。
FlutterActivity.java的onCreate过程,通过FlutterJNI的AttachJNI()方法来初始化引擎Engine、Dart虚拟机、Isolate、taskRunner等对象。再经过层层处理最终调用main.dart中main()方法,执行runApp(Widget app)来处理整个Dart业务代码。
Flutter引擎启动中会创建有4个TaskRunner以及创建虚拟机,分别来看看它们的工作原理。
Step 1: 检查task,当task队列不为空,先执行一个task;
Step 2: 检查microTask,当microTask不为空,则执行microTask;不断循环Step 2 直到microTask队列为空,再回到执行Step 1。
可简单理解为先处理完所有的Microtask,然后再处理Task。因为scheduleMicrotask()方法的调用自身就处于一个Task,执行完当前的task,也就意味着马上执行该Microtask。了解了其工作机制,再来看看这4个Task Runner的具体工作内容。
Platform Task Runner:运行在Android或者iOS的主线程,尽管阻塞该线程并不会影响Flutter渲染管道,平台线程建议不要执行耗时操作;否则可能触发watchdog来结束该应用。比如Android、iOS都是使用平台线程来传递用户输入事件,一旦平台线程被阻塞则会引起手势事件丢失。
UI Task Runner: 运行在ui线程,比如1.ui,用于引擎执行root isolate中的所有Dart代码,执行渲染与处理Vsync信号,将widget转换生成Layer Tree。除了渲染之外,还有处理Native Plugins消息、Timers、Microtasks等工作;
GPU Task Runner:运行在gpu线程,比如1.gpu,用于将Layer Tree转换为具体GPU指令,执行设备GPU相关的skia调用,转换相应平台的绘制方式,比如OpenGL, vulkan, metal等。每一帧的绘制需要UI Runner和GPU Runner配合完成,任何一个环节延迟都可能导致掉帧;
IO Task Runner:运行在io线程,比如1.io,前3个Task Runner都不允许执行耗时操作,该Runner用于将图片从磁盘读取出来,解压转换为GPU可识别的格式后,再上传给GPU线程。为了能访问GPU,IO Runner跟GPU Runner的Context在同一个ShareGroup。比如ui.image通过异步调用让IO Runner来异步加载图片,该线程不能执行其他耗时操作,否则可能会影响图片加载的性能。
图解:
isolate堆是运该isolate中代码分配的所有对象的GC管理的内存存储;
vm isolate是一个伪isolate,里面包含不可变对象,比如null,true,false;
isolate堆能引用vm isolate堆中的对象,但vm isolate不能引用isolate堆;
isolate彼此之间不能相互引用;
每个isolate都有一个执行dart代码的Mutator thread,一个处理虚拟机内部任务(比如GC, JIT等)的helper thread;可见,isolate是拥有内存堆和控制线程,虚拟机中可以有很多isolate,但彼此之间内存不共享,无法直接访问,只能通过dart特有的Port端口通信;isolate除了拥有一个mutator控制线程,还有一些其他辅助线程,比如后台JIT编译线程、GC清理/并发标记线程。
StatelessWidget:内部没有保存状态,UI界面创建后不会发生改变;
StatefulWidget:内部有保存状态,当状态发生改变,调用setState()方法会触发StatefulWidget的UI发生更新,对于自定义继承自StatefulWidget的子类,必须要重写createState()方法。
三棵树图解:
Widget是为Element描述需要的配置, 负责创建Element,决定Element是否需要更新。Flutter Framework通过差分算法比对Widget树前后的变化,决定Element的State是否改变。当重建Widget树后并未发生改变, 则Element不会触发重绘,则就是Widget树的重建并不一定会触发Element树的重建。
Element表示Widget配置树的特定位置的一个实例,同时持有Widget和RenderObject,负责管理Widget配置和RenderObject渲染。Element状态由Flutter Framework管理, 开发人员只需更改Widget即可。
RenderObject表示渲染树的一个对象,负责真正的渲染工作,比如测量大小、位置、绘制等都由RenderObject完成。
可见,开发者通过Widget配置,Framework通过比对Widget配置来更新Element,最后调度RenderObject Tree完成布局排列和绘制。7. 渲染原理Dart的UI采用Widget来实现,最终转换为RenderObject,那界面又是如何渲染的呢?渲染过程,UI线程完成布局、绘制操作,生成Layer Tree;GPU线程执行合成并光栅化后交给GPU来处理,其中几个关键步骤:
Animate: 遍历_transientCallbacks,执行动画回调方法;
Build: 对于dirty的元素会执行build构造,没有dirty元素则不会执行,对应于buildScope()
Layout: 计算渲染对象大小和位置,对应于flushLayout(),这个过程可能会嵌套再调用build操作;
Compositing bits: 更新具有脏合成位的任何渲染对象, 对应于flushCompositingBits();
Paint: 将绘制命令记录到Layer, 对应于flushPaint();
Compositing: 将Compositing bits发送给GPU, 对应于compositeFrame()。
GPU线程通过skia向GPU硬件绘制一帧的数据,GPU将帧信息保存到FrameBuffer里面,然后视频控制器会根据VSync信号从FrameBuffer取帧数据传递给显示器,从而显示出最终的画面。8. Platform ChannelsFlutter框架提供了UI的控件支持,对于APP除了UI还有其他依赖于Native平台的支持,比如调用Camera的功能,该怎么办呢?为此,Flutter通过提供Platform Channel的功能,使得Dart代码具备与Native交互的能力。Platform Channel用于Flutter与Native之间的消息传递,整个过程的消息与响应是异步执行,不会阻塞用户界面。Flutter引擎框架已完成桥接的通道,这样开发者只需在Native层编写定制的Android/iOS代码,即可在Dart代码中直接调用,这也就是Flutter Plugin插件的一种形式。
科技不断在进步,技术不断发展,移动跨平台技术几乎从Android、iOS诞生不久便出现,已发展快10年。时至今日,兼具跨端高效率与高性能体验的Flutter力压群雄,崭露头角,已然成为当下最热门的移动端新技术,全球越来越多的公司在Flutter技术布局并落地产品应用,社区也非常活跃。笔者之前一直从事于Android操作系统底层研发工作,今年刚接触Flutter,Flutter作为一门全新的跨平台技术框架,不断深究会发现这是一个小型系统,涉及到的技术很广:
编译技术如何将dart代码转换为AST(抽象语法树),如何汇编转换为机器码,打包成产物是什么?
Flutter这台引擎如何发动的,怎么跟Native原生系统衔接运行,如何识别产物并加载到内存?
引擎启动后,TaskRunner如何分发任务,跟原生系统消息机制有什么关系?
Dart虚拟机如何管理内存,跟isolate又有什么关系?
开发者编写的Widget控件如何渲染到屏幕上?
Flutter如何通过plugin支持移动设备提供的服务?
以上问题本文都已逐一回答,如果仅仅是用Flutter做业务开发,并不需要掌握这么深度技术,不过,知其然知其所以然,能让你游刃有余。本文讲述跨平台技术的过去与未来,以及从宏观架构解读Flutter内部原理,后续有时间将更深入的技术细节以及实战经验角度来跟大家揭秘更多Flutter技术。随着5G+IOT时代的到来,Fuchsia系统或许发力IOT新战场,你所掌握的Flutter技术栈可以无缝迁移,这是一次弯道超车的机会。即便Fuchsia落败,相信只要深扎Flutter系统技术的精髓,其他任何的移动端新技术都可以轻松快速地掌握。最后,用一句话来结束本次分享,“有时候,你选择一个方向,不是因为它一定会成为未来,而是它有可能成为不一样的未来。”
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8