钉钉 Flutter 跨四端方案设计与技术实践

网站建设5年前发布
40 0 0

本文主要介绍钉钉基于 Flutter 构建的跨四端应用框架(代号 Dutter),内容主要包含方案设计、最佳实践以及部分 FlutterEngine 层面的问题定位等。希望能通过本文的分享,为有类似诉求的团队提供一定参考。,Dutter 即 DingTalk Flutter,是钉钉内基于 Flutter 构建的跨四端研发框架。,Dutter 项目「起于 Flutter 但不止于 Flutter」。项目的主要目标是希望能够借助 Flutter 跨平台能力,在不降低用户体验的前提下,提升钉钉端侧研发效率,缓解钉钉端侧研发资源不足、各端人力不平衡的问题。,目前 Dutter 运行框架已完成钉钉四端集成,并完成了一系列共创业务的灰度和试点。现阶段钉钉内基于 Flutter 研发业务有「日程签到」「+面板」以及部分内部灰度业务:,我们选择 Flutter 并启动 Dutter 项目主要有两方面的考虑:,下面我们针对这简单展开做一下说明。,随着钉钉发展到第7个年头,客户端侧「业务需求」「研发资源」和「技术演进」这三者之间的矛盾愈发强烈:,以上各点可汇总到一个问题:我们技术研发资源不足。为解决上述问题,有两个途径:1. 继续扩大技术团队规模;2.  提升团队研发效率。,以目前钉钉端侧将近150人的团队规模来看,总体量并不算小,继续扩招存在一定难度。既然团队规模无法无限扩张,我们就需要在研发效率上挖掘提升空间:,由此可见,如果我们能借助于跨平台技术,使技术同学可以通过「一份代码实现覆盖所有端」,将原来一个需求需要多个平台、多个同学分别做的事情收敛到 1~2 个同学上,即可极大的提高我们的研发效率。,钉钉内已经有「小程序」「H5」等跨端技术,我们需要提效是否可以直接使用现有技术栈来达成目标?对于钉钉端侧团队来说,选基于 Web 的方案来做跨平台理论上可行,但是实际很难达到预期效果。主要原因在于两方面:,Flutter 作为最近几年发展起来跨平台技术,不同于 Web 生态,其基于类 Native 的架构设计,选择性放弃动态化、更关注于跨平台。在保证具有类似 Native 性能和体验基础之上,赋予开发者「一次开发多端构建运行」的能力。因此相比小程序技术,Flutter 更适合用于解决我们端侧技术团队的痛点。,除此以外,我们对国内跨平台技术进行摸底调研之后发现基于 Flutter 的跨平台项目后发优势明显,上限高、发展潜力大,更具长期投入价值。,在对业界跨平台方案的长期跟踪中我们发现,「自绘引擎」是现阶段一大热点,而大多「自绘引擎」方案,是在 Flutter 项目开源并热度上升之后开始启动。这个时间点上的巧合并非偶然,我们通过下面这种图来说明主流跨平台方案在技术实现上区别:,从上面这张图我们可以看到,对于跨平台方案设计者来说,Flutter 项目最大的价值是:为生态提供了一个开源的、设计优秀的、兼容性优良的、性能优异的、边界清晰的 自绘引擎。,基于这套开源的自绘引擎,具备技能能力的团队只要稍加修改即可将其应用到自己的跨平台方案中以替换掉 Native 组件,复用 Flutter 具备的跨平台一致性能力,提升方案业务与技术价值。,对于钉钉来说,考虑到现阶段我们在跨平台的投入和目标,还不是类似其它方案一样推出自己的跨平台自绘引擎。但是从技术方向来看,选择基于 Flutter 来做跨平台方案,一方面我们可以快速享受 Flutter 的技术红利,在交付产物性能和质量上与其它主流方案保持一致;另外一方面我们也可以在这个过程中培养相关技术团队,为后续更深层次的定制和改造做技术储备。,本章节会概要介绍钉钉 Dutter 跨端框架设计情况,并针对其中具有代表性的问题做一些补充说明。,Dutter 核心模块包含三大套件:,整体如下简图所示:,把上述简图展开,即可得到框架整体模块图,大致如下:,从下向上以此为:,数据通信这块主要就是指 Flutter 与平台侧两种主要通信方式:Channel 与 FFI。Channel 在 Flutter 应用中相对比较广泛,绝大部分设计到 Flutter 与平台通信都是基于此模式展开,其优势在于集成度高、封装好使用简单;劣势主要在于通信效率问题;FFI 在 Flutter 2.0 中已经作为正式特性推出,其最大特性在于同步调用、内存共享、执行效率高,但是在易用性、扩展性等方面还有一定提升空间。,关于 Channel,钉钉侧使用相比官方文档并无本质差别,想分享的经验在于 Channel 数量管理上。官方原生资料并未太多涉及 Channel 管理相关内容,以钉钉实际使用经验来看,我们还是推荐大家在一方业务中,尽量将 Channel 收敛到 1~2 个做共享,并在共享 Chennel 基础之上封装供业务使用的「响应」和「分发」接口。,这样做主要有以下好处:,上述两点,尤其是第2点对钉钉做移动端与桌面端兼容有着巨大的意义。在「钉钉 Flutter 桌面端应用方案」中有说明,我们现在在移动端使用的是单引擎架构、但是桌面端部分采用的是多引擎架构。如果没有对 Channel 做合理的封装、让业务同学直接面向 FlutterEngine 来做注册与调用,则会极大的增加多引擎模式下的代码管理成本,并且会造成移动端和桌面端实现不一致。,我们现在的做法是将 FlutterEngine 与 Channel 封装到 Dutter 框架内部,对上层接口暴露统一封装之后的实例:DutterMethodChannel。对于业务层代码,已经无需感知底层架构是单引擎模式或者多引擎模式,仅需按照统一的规则和模式来注册或者调用相关服务。通过此模式,在降低了业务使用复杂度的同时,也为底层框架设计带来了极大的灵活性,为后续移动端切换多引擎方案提供了有力支撑。,FFI 已经在 Flutter 2.0 版本正式发布,其相比 Channel 最大的优势在于执行效率更高,更适合于对性能要求较高的场景。此章节不涉及具体 FFI 的使用方法,而是想为大家简单分享在使用 FFI 时内存管理上所需注意的事项。,我们都知道,目前移动端开发(Java、OC、Swift)都有自动管理内存的的机制;Flutter 所使用的 dart 语言也有基于垃圾回收自动内存管理。各种语言在自己作用域中都可以按照各自规则来合理管理内存,保证内存空间合理稳定的应用。,但是 FFI 作为一种跨作用直调的方法,虽然基于内存共享的机制下简化调用链路,但是对内存管理也提出了更高的要求。在这种模式下,如果不能很好的管理(开辟&释放)内存空间,则有很大概率导致野指针或者内存泄漏问题。,在官方文档 Flutter FFI 与 Dart FFI 章节的介绍中,对内存管理上的说明较为有限。通过查阅相关接口资料可知,在 dart:ffi 中提供了手动管理内存的方式:,在此基础之上我们即可定义 Dutter FFI 内存管理策略。首先我们需要我们需要准确定义核心原则:,在 1 和 2 的基础上,我们把 FFI 操作相关接口以及数据结构进行封装,统一到「Dutter FFI Bridge」模块。,在对覆盖面和复杂度充分考虑之后,Dutter FFI 接口中除默认基础类型外,我们仅增加对 String 类型的支持。对于其它数据类型,业务方可以通过将其序列化的方式来进行传递。在传递过程中,对定长字符串,可以直接通过「UTF-8 编码的 char * 数组」传递;如果是不定长字符串(如调用返回值),则需要使用使用自定义数据结构 DTFUInt8String 传递。具体到实现:,
,「消息总线」是一个钉钉特色模块,我们主要是是为解决钉钉端侧基于不同技术栈实现的业务通信问题:比如一个基于 Flutter 实现的业务,希望通知一个基于小程序实现的页面刷新 UI,即可通过消息总线来实现此功能:,消息总线定位是一个轻量级「端」到「端」的超级通道,目标是让业务具备跨运行环境无缝通信的能力。在逻辑上包含「总线」「控制器」「注册发送」三大模块;在实现上通过「可持久化消息」「管道分级」「权限管控」等方式保证整体运行可靠、高效和安全。,因为钉钉端侧业务特点,我们非常注重模块化建设。Flutter 业务采用的模块化方案发展自钉钉 Native 侧模块化框架,我们在最初即坚持杜绝 Flutter 业务层直接耦合:,模块化之后并不仅仅只是对我们研发效能有提升,同时也带来了显著的业务和技术价值。比如:,容器化是支撑 Flutter 在钉钉内快速落地的有力保障。通过钉钉在 H5 和 小程序项目中沉淀的容器基础,在 Flutter 场景我们继续参考容器化思想,在设计和能力上快速对接。一方面得以快速复用现有沉淀的基础设施;另外一方面降低业务开发上手复杂度,保证原容器常用能力在 Flutter 场景可以继续使用,技术栈得以延续。,从发展时间轴来看,钉钉端侧容器大致经历过3个版本:,在目前容器架构基础上,我们可以保证对未来新技术良好的兼容性。在后续发展中如再次需要对接类似 Flutter 新技术栈时,可以按照现有标准快速打通,并在概念、能力、基础设施上保证最大化复用。,钉钉 Flutter 目前使用的组件库有两套:dingui_flutter 以及 dingtalk_uikit,其中 dingui_flutter 是我们现阶段重点建设的部分,dingui_flutter 是按照钉钉视觉团队提出的 DingUI 视觉规范实现的一套 Flutter 版本组件,目前核心组件可以做到四端兼容:,dingui_flutter 目标是可贡献给社区,但现阶段因为稳定性、完善度等问题,暂时还在钉钉内部使用,后续发展成熟之后我们会将其尽早开源。,目前在钉钉桌面端中 Flutter 使用模式基本与移动端相同:Flutter 作为钉钉内的一个功能模块,客户端主体仍以原 Native 实现为主。对于部分基于 Flutter 实现的业务,在启动时通过 Dutter 框架封装的接口转场,根据特定转场模式执行转场动作。,为了达到上述效果,我们在桌面端应用中主要解决了以下三问题:,后面我们就针对上述问题分别做一下说明。,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 端产物集成示意图:,Flutter 不支持 Windows 32位系统,应该是现阶段阻碍 Flutter 在国内桌面端生态铺开的核心阻碍之一。钉钉在解决此问题时,基本上尝试了我们能想到的所有方案:从最初的双进程,到中间的的整体升级64位,以及后面的 FFW,但上述方案最终还是因为各种各样的问题无法落地。,虽然最终未能落地,但是在上述尝试的过程中,我们了解到两个非常重要的信息:,在以上两点的支持下,由钉钉 周镛 同学最终探索出了编译 Windows 32位 FlutterEngine 的方案,并通过 JIT 模式加载 Flutter 编译产物,最终满足在 Windows 端使用的诉求。,为了能够在 Windows 平台使用 Flutter,剥离细节之后我们大致做了以下几件事(详细资料后面会有文章做专门分享):,通过以上步骤我们即完成了在 Windows 32 位钉钉集成 Flutter 的主要工作,其后使用无论是 JIT 还是 AOT 在功能上并无本质区别,但是在性能上的差异较大。目前我们灰度过程中发现的主要问题有:,因此现阶段我们采用的仅能算作一个刊用方案,后续我们仍需在此部分加大投入,争取尽早让一个完全的 Flutter 集成到钉钉 Windows 端。,这个是我们在桌面端落地过程中遇到的第三个问题。由于在移动端我们使用的是基于 FlutterBoost 构建的单引擎架构,而桌面端则因为其特殊环境,只能使用多引擎架构:,因此对业务同学使用带来一些问题,其中最严重的即为多引擎环境下导致的通讯阻塞。,现阶段我们主要还是通过业务层兼容的方式来绕过:我们通过钉钉「消息总线」来支持多引擎环境下的通讯问题。但是长久来看我们还是需要有友好的支持多引擎,需要将目前移动端具备的 LightWeightEngine 能力扩展到桌面端,并在其基础上进行扩展,打通 isolate 让业务代码完全共享内存。目前此方案整作为技术项目在 AliFlutter 项目组内推进中,期待早日达成既定目标!,目前 Dutter 项目已经基本达成一阶段目标,后续我们大致会在以下5个方面继续投入:

© 版权声明

相关文章