关于flutter:钉钉-Flutter-跨四端方案设计与技术实践-Dutter

5次阅读

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

《Dutter 系列文章》将论述钉钉基于 Flutter 构建的跨四端利用框架(代号 Dutter)的技术实际与踩坑教训,共分为上、下两篇,本文为上篇,本周四将上线系列文章下篇《前事不忘; 后事之师:聊聊钉钉 Flutter 落地桌面端踩过的“坑”》,欢送追更 & 浏览。

作者:刘太举 (驽良)

本文次要介绍钉钉基于 Flutter 构建的跨四端利用框架(代号 Dutter),内容次要蕴含方案设计、最佳实际以及局部 FlutterEngine 层面的问题定位等。心愿能通过本文的分享,为有相似诉求的团队提供肯定参考。

我的项目概述

1.1 何为 Dutter

Dutter 即 DingTalk Flutter,是钉钉内基于 Flutter 构建的跨四端研发框架。

Dutter 我的项目「起于 Flutter 但不止于 Flutter」。我的项目的次要指标是心愿可能借助 Flutter 跨平台能力,在不升高用户体验的前提下,晋升钉钉端侧研发效率,缓解钉钉端侧研发资源有余、各端人力不均衡的问题。

1.2 目前停顿

目前 Dutter 运行框架已实现钉钉四端集成,并实现了一系列共创业务的灰度和试点。现阶段钉钉内基于 Flutter 研发业务有「日程签到」「+ 面板」以及局部外部灰度业务:

我的项目背景

咱们抉择 Flutter 并启动 Dutter 我的项目次要有两方面的思考:

  1. 端侧研发提效;
  2. 跟进 Flutter 技术。

上面咱们针对这简略开展做一下阐明。

2.1 端侧研发提效

随着钉钉倒退到第 7 个年头,客户端侧「业务需要」「研发资源」和「技术演进」这三者之间的矛盾愈发强烈:

  • 业务产品同学有很多优良的想法,为将想法落地须要去寻找各端 TL 争取资源,且因为研发资源缓和需重复沟通其需要的业务价值;
  • 研发同学经常处于「1 vs N」的状态,业务需要、稳定性保障、技术支持、BugFix 等,日常工作工夫根本趋于饱和;
  • 技术团队不只是要满足于当初,更要面向未来。在满足日常业务迭代同时,咱们还须要安顿局部资源投入到满足将来 3~5 年倒退的技术我的项目上。

以上各点可汇总到一个问题:咱们技术研发资源有余。为解决上述问题,有两个路径:1. 持续扩充技术团队规模;2. 晋升团队研发效率。

以目前钉钉端侧将近 150 人的团队规模来看,总体量并不算小,持续扩招存在肯定难度。既然团队规模无奈有限扩张,咱们就须要在研发效率上开掘晋升空间:

  • 端侧技术同学被宰割到 5 个平台,宰割之后每个平台上上人力并不算短缺;
  • 不同平台下的同学技术栈上根本处于「断绝」状态,不同平台下的同学无奈互相补位;
  • 任何业务需要须要须要 4+ 端以上的研发资源投入,任何一端人力欠缺都可能造成无奈落地;
  • 一份逻辑多份实现,很难导致完全一致,时常会呈现不同平台业务体现不统一的状况,返工对焦进一步影响效率;
  • 业务上线之后不同平台别离保护,在日常技术支持、BugFix 等场景下须要多份投入。

由此可见,如果咱们能借助于跨平台技术,使技术同学能够通过「一份代码实现笼罩所有端」,将原来一个需要须要多个平台、多个同学别离做的事件收敛到 1~2 个同学上,即可极大的进步咱们的研发效率。

2.2 跟进 Flutter 技术

钉钉内曾经有「小程序」「H5」等跨端技术,咱们须要提效是否能够间接应用现有技术栈来达成指标?对于钉钉端侧团队来说,选基于 Web 的计划来做跨平台实践上可行,然而理论很难达到预期成果。次要起因在于两方面:

  1. 「小程序技术」是目前较为热门跨端技术,其设计定位要满足三方生态多样性场景,其架构设计偏重「大而全」,而非在单点上的重复打磨。这与钉钉一方业务强调的「专而精」「谋求极致」有出入;
  2. 对端侧同学来说,前端开发模式上手门槛高、研发模式差异性较大。须要须要有肯定的应用以及开发教训积攒能力具备较高的开发程度,也就是说后期须要有肯定的「试错空间」。以钉钉目前对线上品质的要求,这一点也是很难满足的。

Flutter 作为最近几年倒退起来跨平台技术,不同于 Web 生态,其基于类 Native 的架构设计,选择性放弃动态化、更关注于跨平台。在保障具备相似 Native 性能和体验根底之上,赋予开发者「一次开发多端构建运行」的能力。因而相比小程序技术,Flutter 更适宜用于解决咱们端侧技术团队的痛点。

除此以外,咱们对国内跨平台技术进行摸底调研之后发现基于 Flutter 的跨平台我的项目后发优势显著,下限高、发展潜力大,更具长期投入价值。

在对业界跨平台计划的长期跟踪中咱们发现,「自绘引擎」是现阶段一大热点,而大多「自绘引擎」计划,是在 Flutter 我的项目开源并热度回升之后开始启动。这个工夫点上的偶合并非偶尔,咱们通过上面这种图来阐明支流跨平台计划在技术实现上区别:

从下面这张图咱们能够看到,对于跨平台计划设计者来说,Flutter 我的项目最大的价值是:为生态提供了一个开源的、设计优良的、兼容性低劣的、性能优异的、边界清晰的 自绘引擎。

基于这套开源的自绘引擎,具备技能能力的团队只有稍加批改即可将其利用到本人的跨平台计划中以替换掉 Native 组件,复用 Flutter 具备的跨平台一致性能力,晋升计划业务与技术价值。

对于钉钉来说,思考到现阶段咱们在跨平台的投入和指标,还不是相似其它计划一样推出本人的跨平台自绘引擎。然而从技术方向来看,抉择基于 Flutter 来做跨平台计划,一方面咱们能够疾速享受 Flutter 的技术红利,在交付产物性能和品质上与其它支流计划保持一致;另外一方面咱们也能够在这个过程中造就相干技术团队,为后续更深层次的定制和革新做技术储备。

方案设计

本章节会概要介绍钉钉 Dutter 跨端框架设计状况,并针对其中具备代表性的问题做一些补充阐明。

3.1 总体设计

Dutter 外围模块蕴含三大套件:

  1. Dutter Runtime;
  2. Dutter Dev Kit;
  3. Dutter OPS Kit。

整体如下简图所示:

  • Dutter Runtime: 基于 Flutter 构建的 Dutter 运行时环境,是 Dutter 最外围的局部。除去 Flutter 提供根底性能以外,咱们还提供了 容器化组件、API 插件、业务模块化框架等性能。并且在于团体 AliFlutter 我的项目根底上,进一步扩大了 Aion 动态化等性能。Dutter Runtime 也是咱们我的项目运行到当初全力投入的局部;
  • Dutter Dev Kit:即研发套件,次要目标是解决不同技术栈同学在 跨 4 + 端 研发时的撑持和效率问题。目前投入绝对无限,后续可与 钉钉研发平台 单干整合;
  • Dutter OPS Kit: 即运维套件,次要承载是 Dutter 产物公布和运维相干性能,如大盘监控等。目前投入绝对无限,后续可与 钉钉研发平台 单干整合。

把上述简图开展,即可失去框架整体模块图,大抵如下:

从下向上以此为:

  • 左下角局部为「Dutter Runtime」相干模块;
  • 右下角为「Dutter OPS Kit」相干模块;
  • 右上角为「Dutter Dev Kit」相干模块;
  • 左上角为业务局部。

3.2 数据通信

数据通信这块次要就是指 Flutter 与平台侧两种次要通信形式:Channel 与 FFI。Channel 在 Flutter 利用中绝对比拟宽泛,绝大部分设计到 Flutter 与平台通信都是基于此模式开展,其劣势在于集成度高、封装好应用简略;劣势次要在于通信效率问题;FFI 在 Flutter 2.0 中曾经作为正式个性推出,其最大个性在于同步调用、内存共享、执行效率高,然而在易用性、扩展性等方面还有肯定晋升空间。

Channel

对于 Channel,钉钉侧应用相比官网文档并无实质差异,想分享的教训在于 Channel 数量治理上。官网原生材料并未太多波及 Channel 治理相干内容,以钉钉理论应用教训来看,咱们还是举荐大家在一方业务中,尽量将 Channel 收敛到 1~2 个做共享,并在共享 Chennel 根底之上封装供业务应用的「响应」和「散发」接口。

这样做次要有以下益处:

  1. 有利于性能稳定性,无限的的 Channel 能够升高通信异样概率、晋升通信性能;
  2. 有利于治理,尤其是在「单引擎 / 多引擎」共存模式下,能够通过正当的封装抹平底层差别。

上述两点,尤其是第 2 点对钉钉做挪动端与桌面端兼容有着微小的意义。在「钉钉 Flutter 桌面端利用计划」中有阐明,咱们当初在挪动端应用的是单引擎架构、然而桌面端局部采纳的是多引擎架构。如果没有对 Channel 做正当的封装、让业务同学间接面向 FlutterEngine 来做注册与调用,则会极大的减少多引擎模式下的代码治理老本,并且会造成挪动端和桌面端实现不统一。

咱们当初的做法是将 FlutterEngine 与 Channel 封装到 Dutter 框架外部,对下层接口裸露对立封装之后的实例:DutterMethodChannel。对于业务层代码,曾经无需感知底层架构是单引擎模式或者多引擎模式,仅需依照对立的规定和模式来注册或者调用相干服务。通过此模式,在升高了业务应用复杂度的同时,也为底层框架设计带来了极大的灵活性,为后续挪动端切换多引擎计划提供了无力撑持。

FFI

FFI 曾经在 Flutter 2.0 版本正式公布,其相比 Channel 最大的劣势在于执行效率更高,更适宜于对性能要求较高的场景。此章节不波及具体 FFI 的应用办法,而是想为大家简略分享在应用 FFI 时内存治理上所需注意的事项。

咱们都晓得,目前挪动端开发(Java、OC、Swift)都有主动治理内存的的机制;Flutter 所应用的 dart 语言也有基于垃圾回收主动内存治理。各种语言在本人作用域中都能够依照各自规定来正当治理内存,保障内存空间正当稳固的利用。

然而 FFI 作为一种跨作用直调的办法,尽管基于内存共享的机制下简化调用链路,然而对内存治理也提出了更高的要求。在这种模式下,如果不能很好的治理(开拓 & 开释)内存空间,则有很大概率导致野指针或者内存透露问题。

在官网文档 Flutter FFI 与 Dart FFI 章节的介绍中,对内存治理上的阐明较为无限。通过查阅相干接口材料可知,在 dart:ffi 中提供了手动治理内存的形式:

在此基础之上咱们即可定义 Dutter FFI 内存管理策略。首先咱们须要咱们须要精确定义外围准则:

  1. 调配与开释同源:必须应用一套 alloc 与 free 算法,防止因为实现差别,导致内存调配开释异样;
  2. 必须满足「谁 alloc 谁 free」的准则。

在 1 和 2 的根底上,咱们把 FFI 操作相干接口以及数据结构进行封装,对立到「Dutter FFI Bridge」模块。

在对覆盖面和复杂度充分考虑之后,Dutter FFI 接口中除默认根底类型外,咱们仅减少对 String 类型的反对。对于其它数据类型,业务方能够通过将其序列化的形式来进行传递。在传递过程中,对定长字符串,能够间接通过「UTF-8 编码的 char * 数组」传递;如果是不定长字符串(如调用返回值),则须要应用应用自定义数据结构 DTFUInt8String 传递。具体到实现:

1、为满足「调配与开释同源」准则,在 Dutter 中,咱们抉择 dart:ffi 中的 allocate 和 free 办法作为统一分配和开释实现。Dutter 框架会在启动过程中做一次接口绑定,将咱们自定义数据结构相干办法传递到 Native 侧,Native 侧所有 FFI 接口内存调配场景均通过绑定接口实现:

2、为满足「谁 alloc 谁 free」准则,在 Dutter FFI 接口中,咱们默认约定以下 3 准则。在此基础上可能保障堆内存的调配都在 DTFUInt8String 管制范畴内,只有解决好 DTFUInt8String 对象的生命周期,即可保障传递过程中内存治理的安全性:

a. 接口设计时,对于须要不定长返回值的场景,应用 DTFUInt8String 来传递数据;

b. 为晋升传递效率,尽量以指针形式传递 DTFUInt8String;

c. 调用方负责创立以及开释 DTFUInt8String。

3.3 音讯总线

「音讯总线」是一个钉钉特色模块,咱们次要是是为解决钉钉端侧基于不同技术栈实现的业务通信问题:比方一个基于 Flutter 实现的业务,心愿告诉一个基于小程序实现的页面刷新 UI,即可通过音讯总线来实现此性能:

音讯总线定位是一个轻量级「端」到「端」的超级通道,指标是让业务具备跨运行环境无缝通信的能力。在逻辑上蕴含「总线」「控制器」「注册发送」三大模块;在实现上通过「可长久化音讯」「管道分级」「权限管控」等形式保障整体运行牢靠、高效和平安。

3.4 模块化

因为钉钉端侧业务特点,咱们十分重视模块化建设。Flutter 业务采纳的模块化计划倒退自钉钉 Native 侧模块化框架,咱们在最后即保持杜绝 Flutter 业务层间接耦合:

模块化之后并不仅仅只是对咱们研发效力有晋升,同时也带来了显著的业务和技术价值。比方:

  1. 为钉钉多版本提供了无力撑持,满足「规范钉」「大客户钉」「专有钉」等多个版本共享代码的诉求;
  2. 提供了良好的兼容性,通过对根底模块的灵便插拔,满足 Dutter 框架在挪动端和桌面端同架构的诉求;
  3. 提供了丰盛的扩展性,例如咱们在做 Flutter 动态化尝试时,基于模块化可能以较低成本对现有模块做动态化革新而不影响其它模块的稳定性。

3.5 容器化

容器化是撑持 Flutter 在钉钉内疾速落地的无力保障。通过钉钉在 H5 和 小程序我的项目中积淀的容器根底,在 Flutter 场景咱们持续参考容器化思维,在设计和能力上疾速对接。一方面得以疾速复用现有积淀的基础设施;另外一方面升高业务开发上手复杂度,保障原容器罕用能力在 Flutter 场景能够持续应用,技术栈得以连续。

从倒退时间轴来看,钉钉端侧容器大抵经验过 3 个版本:

  • v1.0 版本次要解决「有无」问题,定义容器相干外围概念;
  • v2.0 版本在原根底上形象出「能力包」的概念,保障业务根底能力可跨运行环境复用;
  • v3.0 版本在 v2.0 根底上进一步形象出「运行时」和「扩大」,将外围实现上层为「容器底座」,三者之间弱耦合。

在目前容器架构根底上,咱们能够保障对将来新技术良好的兼容性。在后续倒退中如再次须要对接相似 Flutter 新技术栈时,能够依照现有规范疾速买通,并在概念、能力、基础设施上保障最大化复用。

3.6 组件库

钉钉 Flutter 目前应用的组件库有两套:dingui_flutter 以及 dingtalk_uikit,其中 dingui_flutter 是咱们现阶段重点建设的局部,dingui_flutter 是依照钉钉视觉团队提出的 DingUI 视觉标准实现的一套 Flutter 版本组件,目前外围组件能够做到四端兼容:

dingui_flutter 指标是可奉献给社区,但现阶段因为稳定性、欠缺度等问题,临时还在钉钉外部应用,后续倒退成熟之后咱们会将其尽早开源。

Flutter 桌面端

目前在钉钉桌面端中 Flutter 应用模式根本与挪动端雷同:Flutter 作为钉钉内的一个功能模块,客户端主体仍以原 Native 实现为主。对于局部基于 Flutter 实现的业务,在启动时通过 Dutter 框架封装的接口转场,依据特定转场模式执行转场动作。

为了达到上述成果,咱们在桌面端利用中次要解决了以下三问题:

  1. 桌面端集成模式问题;
  2. Widows 32 位问题;
  3. 引擎架构兼容问题。

前面咱们就针对上述问题别离做一下阐明。

4.1 桌面端集成模式问题

Flutter 在桌面端目前还仅反对以 FlutterApp 的模式来应用,挪动端宽泛应用的 FlutterModule 模式临时还不反对。但冀望通过 FlutterApp 来对现有客户端做大范畴的革新,这既不合理也不事实。因而咱们在桌面端落地 Flutter 遇到的第一个问题,即如何把 Flutter 作为一个模块集成到钉钉现有客户端。

咱们在对 Flutter 构建产物做剖析的时候发现,其实无论是 FlutterApp 还是 FlutterModule,其外围产物差异并不大。以 iOS 端 FlutterModule 和 macOS 下 FlutterApp 来举例,如下图所示:

咱们能够看到,对于 App.framework, Flutter.framework, Plugins.framework 这些外围模块,无论是 FlutterApp 还是 FlutterModule,其产物中都是蕴含的。次要差异在于 FlutterModule 中多了一个用于辅助插件注册的 FlutterPluginRegistrant.framework。侥幸的是这部分实现并不简单,咱们能够很轻易的通过自定义工具链的形式来生成。

沿着这个思路,咱们就能够梳理出 Flutter 桌面端集成计划:

通过 FlutterApp 来组织桌面端 Flutter 相干模块,在官网工具链根底上做适当扩大。从原有构建产物中摘取作为模块化应用所需的局部,最初再补全局部用于插件注册所需的模板代码。最终产物集成到钉钉现有客户端之后,应用上与其它二方库并无实质差异,可参考现有 FlutterModule 的办法来应用。

最终流程如下:

Mac 和 Windows 端产物集成示意图:

4.2 Widows 32 位问题

Flutter 不反对 Windows 32 位零碎,应该是现阶段妨碍 Flutter 在国内桌面端生态铺开的外围妨碍之一。钉钉在解决此问题时,基本上尝试了咱们能想到的所有计划:从最后的双过程,到两头的的整体降级 64 位,以及前面的 FFW,但上述计划最终还是因为各种各样的问题无奈落地。

尽管最终未能落地,然而在上述尝试的过程中,咱们理解到两个十分重要的信息:

  1. DartVM 是能够运行在 Windows 32 位设施上,然而仅反对以 JIT 模式加载 dart 代码;
  2. Skia 能够编译 Windows 32 位产物。

在以上两点的反对下,由钉钉 周镛 同学最终摸索出了编译 Windows 32 位 FlutterEngine 的计划,并通过 JIT 模式加载 Flutter 编译产物,最终满足在 Windows 端应用的诉求。

为了可能在 Windows 平台应用 Flutter,剥离细节之后咱们大抵做了以下几件事 (详细资料前面会有文章做专门分享):

  1. 批改 FlutterEngine 的构建脚本,使其可能构建出 32 位 的 flutter_windows.dll;
  2. 批改 flutter_tool 中 FlutterPlugin 编译 gn 参数,使其构建 32 位 的产检产物;
  3. 将相干产物做平安混同之后之后集成到钉钉客户端。

通过以上步骤咱们即实现了在 Windows 32 位钉钉集成 Flutter 的次要工作,其后应用无论是 JIT 还是 AOT 在性能上并无本质区别,然而在性能上的差别较大。目前咱们灰度过程中发现的次要问题有:

  1. 启动速度慢:首页加载工夫在 2s 以上;
  2. 内存暂用高:每开拓一个 FlutterEngine 对象,大略须要耗费 70MB 左右的内存;
  3. 代码运行效率低:此问题尽管绝大部分场景并不显著,然而极其场景下还是会呈现性能问题。

因而现阶段咱们采纳的仅能算作一个刊用计划,后续咱们仍需在此局部加大投入,争取尽早让一个齐全的 Flutter 集成到钉钉 Windows 端。

4.3 引擎架构兼容问题

这个是咱们在桌面端落地过程中遇到的第三个问题。因为在挪动端咱们应用的是基于 FlutterBoost 构建的单引擎架构,而桌面端则因为其非凡环境,只能应用多引擎架构:

因而对业务同学应用带来一些问题,其中最重大的即为多引擎环境下导致的通信阻塞。

现阶段咱们次要还是通过业务层兼容的形式来绕过:咱们通过钉钉「音讯总线」来反对多引擎环境下的通信问题。然而短暂来看咱们还是须要有敌对的反对多引擎,须要将目前挪动端具备的 LightWeightEngine 能力扩大到桌面端,并在其根底上进行扩大,买通 isolate 让业务代码齐全共享内存。目前此计划整作为技术我的项目在 AliFlutter 项目组内推动中,期待早日达成既定目标!

总结

目前 Dutter 我的项目曾经根本达成一阶段指标,后续咱们大抵会在以下 5 个方面持续投入:

  1. 基础设施降级:挪动端 FlutterEngine 降级、flutter_boost 降级、摸索落地动态化计划等;
  2. 性能体验精进:桌面端性能精进,最大化解决目前官网反对力度、基础设施齐备度、桌面端个性等起因造成的性能问题,争取能对齐挪动端程度;
  3. 研发套件欠缺:面向钉钉内提供一站式的研发环境,目前咱们心愿能在 AliBox 根底上、面向钉钉四端研发场景,定向扩大局部以满足钉钉内利用开发诉求;
  4. 稳定性加强:解决目前在桌面端、尤其是 Windows 端稳定性上存在的危险,满足钉钉端侧稳定性要求;
  5. 研发提效:扩充业务覆盖面,开释跨端宏利,进一步晋升钉钉端侧研发人效。

以上即为钉钉 Flutter 跨四端框架在利用设计上的一些分享,心愿能为大家带来一些帮忙。

关注【阿里巴巴挪动技术】,阿里前沿挪动干货 & 实际给你思考!

正文完
 0