干货Flutter-原理与闲鱼深度实践

39次阅读

共计 5204 个字符,预计需要花费 14 分钟才能阅读完成。

王康(正物)—— Flutter 官方成员 阿里巴巴技术专家,之前主要负责 Flutter 在闲鱼中的混合开发体系,目前重点关注 Flutter 深入度以及生态相关的工作。本文将分享三方面内容,Flutter 的原理、Flutter 在闲鱼中的应用,最后介绍我们在深度方面的一些探索。

01、Flutter 原理

当我们谈到跨平台框架时,可能会想到很多备选方案。包括早期的 HTML 和 Cordova , 后来的 React Native,Weex,以及这两年很是流行的 Flutter,它们都在不同阶段不同程度上解决了我们对跨平台的诉求。如果我们从一些关键指标包括动态性、性能来观察,他们的区别还比较明显。

HTML 和 Cordova 具有最好的动态性,但他们的性能却是最差的,RN / Weex 具有良好的动态性。Flutter 则是一个纯原生的设计,其设计使它天生具有很好的性能与跨端一致性。

Flutter 是如何实现优秀的性能和跨端一致性的呢?从设计上可以看出 Flutter 在操作系统之上包含了三个层次。最下面是平台相关的嵌入层,其向上提供一个 Surface 用以绘制,建立了相关的线程模型和事件循环机制。在此之上则是一个平台无关的引擎,包括用于绘制的 Skia;Dart 的运行时,开发模式下包括一个解释器;还有一部分是文本绘制相关内容。最上面就是用 Dart 语言编写的 Flutter 框架,也是我们最常接触到的内容。Flutter 框架包含一个完整分层的 UI 框架,从基础的 Foundation 库,到动画手势,再到渲染,之上又提供了各种丰富的 Widget 库。为了方便开发者使用,Flutter 还提供了两套不同风格的组件库,针对安卓的 Material Design 的组件库和针对 iOS 的 Cupertino 风格的组件库。

从这个设计可以看出,Flutter 和平台相关的内容,其实只提供 Surface 和线程 / 事件循环模型的嵌入层部分。这种类似用游戏引擎的方式来开发应用的设计很好解释了为什么它具有优秀的跨端一致性。

我们常常说 Flutter 具有这样的几个特点:

  • 精美

丰富的 Widget 库、Material Design 和 Cupertino 风格的系统库、组合式的 API、像素级的控制力可以使开发者便捷地构建精美的应用。

  • 高效开发 & 执行快速

Dart 语言是为数不多同时支持 JIT 和 AOT 编译的语言。开发期使用 JIT 编译,支持了广受欢迎的热重载功能,开发者可以像 PS 图片一样来开发应用,开发效率高。发布后 Flutter 使用 AOT 编译,Dart 代码最终被编译成 ARM 汇编指令,运行快速。

  • 开放

Flutter 是开源项目,其整个的开发,工作流都是完全遵循开源项目的运作来完成的。

02、Flutter 在闲鱼中的应用

说完 Flutter 原理,我们来看 Flutter 在闲鱼中的应用。在我们的研发中有几个核心关注的问题。

  • 开发效率

闲鱼技术是一个相对较小的团队,但业务需求比较重,开发人员少需求多,这种情况下效率就非常重要。还有部分原因是 iOS 安卓两端开发资源不均衡的问题,

  • 用户体验

我们的设计要求我们具有很好的跨端一致性,其设计也是同一套风格。

  • 执行性能

不管是复杂的交互还是动画,都要求其具有不错的性能。

Flutter 的设计很好地满足了闲鱼业务研发关注的这些问题,这也是我们采用它的原因。

我们怎么样让 Flutter 从无到有地在闲鱼中落地与上线?这里面包括前期的调研,研发期的混合开发体系,以及如何保质保量在线上运行。

2017 年,我们去接触和调研 Flutter,其当时还处在 Alpha 阶段。

我们需要去了解它的原理,看它是如何具有所宣称的诸多优势;对其性能做测试,看能否满足要求;包大小的增加怎么样,这个量是否可承受;音视频调的出发点在于业务场景,我们的详情和发布页面包括了很多图片和视频;工具链上 Flutter 的开发体验如何;我们甚至做了一个 MVP Demo , 这个产品中我们实现了包括发布,详情、我的页面等主要设计与逻辑;此外我们还很关注其社区的成熟度,确保在遇到问题时,可以通过自己对原理的理解或者社区去解决问题。

要使用 Flutter 我们就会面临一个混合研发体系的问题。其最开始的设计是面向纯 Flutter 开发的应用。而我们的应用是在原生项目中嵌入 Flutter,也就是 Add2App。事实上在国内,即便是一个纯 Flutter 的应用,很多时候,因为二方、三方库的原因,也会成为一个混合工程。通过我们的实践与影响,Add2App 也变成了 Flutter 演进的重要方向。

工程层面上,我们的团队中存在两个视角。一个是传统的 Native 开发的视角,一个是 Flutter 视角。我们并没有直接使用 Github 上的 Flutter 项目,主要是因为可控性的问题。因为国内的安卓碎片化以及 ROM 中 opengl 的实现不规范,使我们不得不存在一些针对性的处理逻辑,也就是引擎定制。其产物通过定制的 Flutter 最终被 fwn 工程所使用。fwn 工程包含了实际 Flutter 业务,其 Flutter 代码通过产物集成的方式,通过 pod(iOS),和 gradle (Android), 最终集成到原生项目中。

混合开发不仅包括工程体系,也包括页面侧的逻辑。我们一开始就涉及到了混合页面体系,以 Android 为例简要介绍下原理。每个 Flutter 页面对应了一个原生的 BoostFlutterActivity,BoostFlutterActivity 通过各自的 BoostFlutterView 去绑定单例的 FlutterEngine。当 Flutter 页面在做切换时 BoostFlutterActivity 也会同步切换,将 FlutterEngine 动态地 Detach 和 Attach。Native 和 Flutter 之间可以互相关闭和打开页面,Native 的生命周期也会同步到 Flutter 侧。

Flutter 支持 UI 构建以及逻辑实现, 但可能我们还需要去获取 wifi 状态或电量等系统状态。

面对这样的场景,Flutter 提供了很多机制去扩展应用。以获取电量为例,Flutter 提供了 Channel 机制用于同 Native 之间的双向通信。Dart 代码在 Release 下会变成汇编代码,直接调用到 Engine (C++),再调用到 OC (直接)或 JAVA(通过 JNI 间接),这种设计下的性能是原生体验。

Flutter 不仅在逻辑侧提供了 Channel 机制去扩展应用,在渲染相关也提供了一些机制去做更多。我们写的 Flutter 视图布局最终会通过 DartRuntime 调用到 Engine 中的 LayerTree->Paint 方法。在这个渲染管线中,LayerTree 会调用到 SkCanva->Draw,最终通过 PresentRenderBuffer / SwapBuffer 将内容绘制到 GPU 上。LayerTree 中,包含很多种 Layer,其中有一个特殊的 TextureLayer 可用于扩展。

以 Android 为例,TextureLayer 可以在 SurfaceTexture 的帮助下,同一个 Surface 相关联。基于 Surface 可以将视频播放的内容传入并完成渲染,或者结合 VirtualDisplay 和 Presentation,完成 NativeView 的嵌入。需要注意的是,因为 VirtualDisplay API 的限制,此部分的逻辑需要 API Level 在 20 及其以上。

也就是说 Flutter 提供了 Channel 机制用来扩展系统特性相关的逻辑,通过 Texture 机制来支持视频播放器等场景和原生视图的嵌入。

在开发过程中我们其实也遇到很多问题,不管是混合栈,或者是视频嵌入,iOS 兼容等,很多今天已经不再是问题, 但我还是想和大家分享下其中的一些思路。首先了解各层面原理是很重要的,Flutter 本身是一整套庞大完整的内容,针对不同层次团队中都应该有相关的同学有一定理解;要有能力识别出关键问题;可以复现和定位问题,提供最小化的 Demo 用于复现它,在通过社区解决问题的场景下,最小的可复现的 Demo 是尤其重要的;还有就是要同社区有紧密的联系与合作。

我们不仅要关注技术本身,也一定要保证业务稳定。这里有一些手段来和大家分享。用灰度来发现那些容易发现的问题,用分桶和降级策略逐渐增加 Flutter 的业务比例,通过线上 APM 去监控质量,这些手段来保障质量。

总的来看,目前我们有 20 多个页面来使用 Flutter 构建,Crash 水平在万分之一的数量级,详情页等帧率在 52 帧以上,可交互的页面加载时长是 300 毫秒。

这些是我们目前使用 Flutter 开发的部分页面,包括详情发布、我的等。涉及到的设计元素较多,包括视频、图片、聊天、评论、拍摄等比较完备的内容。我们最早使用详情和发布来验证 Flutter,也是要看 Flutter 能否支撑业务场景最复杂的情况,此外也需要分桶与降级的机制来保障最坏情况下的业务可用性。背后的演进并不容易,也是个逐渐改善解决的过程,但这并不是原理上的大问题,很多是细节上的问题,典型如安卓上面的碎片化问题。

除了业务落地外,我们也做了一些体系化的建设,这里面很多内容都同 Native 侧。就 Flutter 而言,从下往上,包括一些针对 Flutter 的 SDK,像其特有的 APM 采集;大量 SDK 的桥接;在其上,构建用于 Flutter 开发的编程框架,如 Fish Redux,FlutterBoost 等,最终去支撑各个 Flutter 业务的研发。

03、Flutter 深度实践

分享完 Flutter 在闲鱼中的应用,接下来介绍我们的一些深度实践。

在 Engine 侧,主要解决 Android 碎片化所带来的问题以及 iOS 上的内存优化。

在 Dart 侧的工作主要围绕着 Dill 展开,我们开发了一个基于 Dill 编织的 AOP 框架,提供了一种新的方式来实现中间语言层面的代码编织。基于此,我们也正在做 JSON 转换,轻量级反射等部分的内容。

在 Flutter 侧,我们的工作包括 APM,FlutterBoost , FishRedux 等面向业务研发的开发框架。

在 UI 部分,我们也在做一些图片转代码部分的工作。

简单介绍下 AOP 框架和 UI To Code 两部分的工作。

如果想去解决 AOP For Flutter 的问题,有哪些问题需要解决呢?首先我们要描述清楚这段代码,是想对哪个库、哪个类的哪个方法去做怎么样的操作;要让 AOP 代码没有侵入性,使原生代码和 AOP 代码可以分开编写,并最终在合适层面进行编织;我们还需要一种机制,去提取散落各处的注解形式的切面逻辑,并将其应用到目标方法中去。

在 AspectD 的设计中,通过提供面向 Dart 的 Aspect 设计,我们解决了描述切面的问题;通过提供基于 Kernel to Kernel Transform 的 Transformer,我们解决了提取注解和编织的问题。

相对于传统 AOP 框架所提供的 Call 和 Execute 的语法,AspectD 还提供了 Inject 的语法,这主要是因为 Flutter 禁止反射造成的。

目前还有一个问题就是对于构建过程的侵入性。Flutter 的构建过程 (flutter tools) 和我们以前的习惯有区别,并没有提供太多的扩展点。目前 AspectD 本身有一点对于构建流程的修改,用于拦截原始的 Dill 构建,并用操作过的 Dill 文件替换原始 Dill 文件,这一侵入性同 AOP 本身没有什么关系,我们正在和 Flutter 团队去解决这一问题。

还有就是我们在做的 UI To Code。即通过 UI 去分析版面,识别组件属性和布局,生成中间的 DSL 描述。后端基于此,完成针对 Flutter 的布局推导,树的构建优化与最终代码转化。

04、总结 & 展望

回顾一下,本文我们分享了跨平台方案与 Flutter 的原理。在 Flutter 的业务落地中如何去调研问题,如何完成混合开发体系和能力扩展,如何去解决关键问题和保证线上质量;也展开介绍了我们在 AOP 和 UI To Code 等领域做得一些深入性工作。

展望未来,我们谈谈 Flutter 的一些未来的趋势。

首先 Flutter 原理很自然地支持了 Mobile 和 Desktop,以及神秘的 Fuchsia 系统。针对 Flutter For Web,我最近也写了一点分析,这是一个实验性的项目,从原理上来说可以支持 Flutter 代码无成本地运行在 Web 上,但可能存在性能的损失。当然如果业务不是很复杂,或不是很高的性能要求的话,可以考虑尝试下。

除了我们自身的实践外,我们也希望一些大的应用,可以更多地进来。更复杂的应用场景和生态链的支持可以让 Flutter 的社群更加完善。

目前我们现在也在做一些 Flutter China 的工作,核心目标就是完善国内的生态降低大家的开发门槛,让更多的团队能够受益。最后,大家如果有什么问题可以在下方评论区进行交流。

精彩回顾


本文作者:王康(正物)

阅读原文

本文为云栖社区原创内容,未经允许不得转载。

正文完
 0