|
作者:yamichonghe,腾讯WXG客户端开发工程师跨平台开发框架是客户端领域的经典课题,几乎从操作系统诞生开始就是我们软件从业者们的思考命题。为了促进Flutter在4个端的成熟,企业微信研发团队也和Google团队针对电脑端Flutter稳定版的落地做了多轮技术沟通。终于在近期的版本实现同一个功能跨平台4端同步上线。企业微信每一个迭代都需要确保iOS、Android、Windows、Mac四个客户端平台的版本功能完全一致,版本发布时间一致。这是非常大的挑战。任何研发投入都是X4的,且由于系统差异,相同功能的研发周期和技术方案也会有明显差异。我们前期实现了逻辑底层架构4端统一,但是UI层怎么办?迫切需要更优的跨平台方案。但是要在历史的Native代码行数已经过千万级的超大型软件系统——企业微信上引入新的跨平台框架何其困难。经典案例:Flutter实现跨平台人事管理系统人事管理是企业经营运作中“人财物事”的核心组成部分,而花名册和人员入转调离管理能力又是人事大板块中最为基础的部分,是企业的共性基础需求,也是后续更多HR应用的底层系统支撑能力。整体需求逻辑复杂,涉及新的交互页面上百个,在企业微信框架内补齐人事板块需要win、mac、ios、android四个平台都支持。企业微信相关产研团队面临极大挑战如何在较小人力投入下短时间内能够顺利迭代出一套完善稳定的人事系统,而此时研发团队持续两年迭代沉淀的Flutter跨平台ui融合框架起到关键作用,全平台技术栈高度一体化,研发人效上比传统分平台开发模式。提升1倍以上,后期产品、设计、测试验收协同成本也降低50%以上。下面我们会详细为大家介绍企业微信在跨平台ui道路上的建设历程。一、项目背景经过Tob业务的高速发展,市场的竞争也愈发激烈,包含了企业微信、钉钉、飞书等各类的办公软件,为了抢占B端市场,满足企业用户的业务需求,功能数量增长非常快。企业微信目前已经深耕众多行业,并且有android/ios/mac/windows/web五大开发平台,使企业微信迅速发展成为了一个超大型应用。我们也遇到了超大型App通常会存在的问题,每次版本迭代都需要五端进行同步迭代发版,各端人力开发成本急剧上升。为了提高开发效率,企业微信在跨平台上也一直有做一些尝试:底层跨平台开发架构企业微信客户端的设计架构采用的是四端C++底层跨平台开发架构,将db、网络、日志等能力通过C++来实现,各端可以复用逻辑层接口。虽然逻辑层统一实现了,但是UI层仍然是由各平台独立开发,因此我们也在继续探索UI跨平台的方案。小程序UI/H5跨平台为了提高业务上层的开发效率以及与微信互通的能力,企业微信在早期就已经接入了小程序和H5的方案,但是小程序和H5的方案跟原生体验会有比较大的差别,无法满足所有的业务场景。在跨平台的选型上,Flutter在绘制上能够保持各端的一致性,并且拥有出色的性能,Dart对于原生开发的同学在技术栈上也会更加友好。在综合对比了主流的跨平台框架后,我们决定将Flutter作为跨端开发的主要框架之一。Flutter移动端跨平台2020年开始企业微信就已经在探索跨平台开发框架,将Flutter作为企业微信移动端主要的UI跨平台开发框架之一。通过一年多的基础建设和业务上的开发,我们Flutter移动端建设也达到了工程化的架构,在架构上我们经历了原始的模块化到插件化的迭代,在跨平台的体验上,组件以及动画逐渐对齐了原生的体验效果。在移动端在业务开发中,得益于Flutter强大的跨平台能力,为我们整个项目团队带来了一定的效率提升,所以我们希望将Flutter这项跨平台技术推动到整个客户端中心,来解决桌面端的人力紧张等问题。Flutter四端跨平台在桌面端的平台上也是通过四端跨平台底层来进行开发的,四端的逻辑层能够得到了很好的复用,但是Win/MAC在开发原生应用的时候仍然是各平台来进行独立开发的,MAC因为用户量较少等原因,人力相对Win来说比较紧张,人力上的不足就会导致MAC的需求很难跟上版本的节奏,但是依然有客户对MAC的功能有诉求的。所以我们希望能够通过跨平台的能力来解决这部分的不足。企业微信在桌面端的跨平台建设上就已经支持小程序/electron框架,小程序因为体验上跟原生应用有很大的差别、electron无法适用于四端的跨平台开发。因此都无法满足我们日常需求开发。在2021年的时候,我们就已经开始在桌面端接入Flutter,期间针对多项难点问题持续攻坚,直到Flutter3.0之后,Flutter全平台进入了stable,我们也逐步完善了Flutter跨四端的框架能力,企业微信四端统一技术栈的设想也正式走上轨道。二、Flutter跨四端的融合工程架构与挑战2.1整体架构图企业微信Flutter工程的整体架构图如下,从下往上:企业微信四端原生应用:原生应用是企业微信Flutter跨平台能力的基石,在底层上主要包含了C++四端跨平台逻辑处理能力,是Flutter处理网络/DB/线程调度/Service的核心,在上层中包含了Flutter的容器,承载着Flutter运行以及与原生之间的交互。配套的还有跨平台相关的CI打包。Flutter应用部署方式:企业微信Flutter跨平台能力可以通过源码集成部署到原生的应用中,也可以通过application的方式独立运行。跨语言通信层:Flutter作为上层业务开发,需要与原生进行通信,在通信层,主要包含了通过dart::ffi直接调用c++底层能力;通过channel调用原生的api接口,以及通过socket的方式对原生应用的接口进行单元测试。四端统一跨平台:跨平台层由Flutter统一四端开发,包含了Flutter工程化开发的脚手架。并且代码模块化,由基础组件提供四端的路由/组件/RPC的等能力。在动态化能力上支持liteapp的动态化能力,这一层是Flutter开发主要核心部分。2.2四端跨平台的困难与挑战在接入企业微信的过程中,需要攻克很多难点问题:1)四端跨平台混合工程,整个企业微信客户端包含跨平台部分,拥有着千万行的代码量级,业务模块上百个,涉及界面上千个,并且跨多个团队协作开发,环境依赖复杂,需要保证不影响现有的架构下完成接入Flutter跨平台的开发能力。2)多端跨语言的调用,Flutter通过dart来进行开发,避免不了与原生平台进行通信,涉及到终端dart/kotlin/objectC/c++等编程语言,需要有一套通用高性能的跨语言接口调用方案去解决四端的跨语言通信问题。3)桌面端稳定性治理,Flutter桌面端仍然处理早期的稳定版本,在桌面端落地的过程中,会遇到各式各样的坑,因此想要在桌面端落地,需要自主分析问题以及修改引擎来修复这些坑。4)保障跨平台的用户体验,Flutter通过skia渲染来达到跨平台开发的一致性,但是也因此失去了一些平台的UI组件特性,为了保障产品体验,需要在Flutter上持续完善原生组件能力。三、企业微信超大型原生工程嵌入Flutter应用整个企业微信客户端包含跨平台部分,代码量级超1500万行。客户端本地模块和业务的数量达到了上百个,相关页面超过2000个。企业微信接入Flutter之后,会影响到各端的编译流程和依赖结构,但是要保障现有的开发模式不受影响,并且提供一套完整的自动化以及容器化的方案。同时面对各端的复杂编译环境(gradle、bazel、xcode、cmake)同时保障Flutter环境的高效开发以及编译的稳定性,面对这个巨大体量的工程,为我们接入Flutter带来了一定的困难。企业微信Flutter研发流程图Flutter在移动端提供了add2app的方式接入到原生的项目当中,并且提供了Fluttermodule的工程结构,可以很方便地将Flutter的module接入到原生工程进行打包和调试。但是在桌面端,官方目前还没有提供混合工程的接入方案,因此我们需要在打包编译的时候做一些额外的配置,以支持混合工程的开发的目的。虽然桌面端没有提供add2app的命令直接输出混合开发的产物,但是我们可以通过Flutterapplication工程,借助Flutterbuild相关的命令进行应用程序的打包,不同平台的主要产物如下:Win:Mac:App.framework/app.so为dart的aot编译产物,主要包含了项目的所有dart源码。FlutterMacOS.framework/flutter_windows.dll为Flutterengine层和Embedder平台嵌入层的代码,engine主要是用来驱动Flutter运行的,平台的嵌入层是用于呈现所有Flutter内容的原生系统应用,它充当着宿主操作系统和Flutter之间的粘合剂的角色,主要是原生平台的代码。这两个文件就是原生工程主要依赖的产物,另外一些资源/插件相关的文件也需要,需要将这些产物混合到原生工程里面并进行引用和编译,然后通过FlutterMacOS.framework/flutter_windows.dll引入的sdk来调用原生平台的代码启动Flutter页面。四、四端跨语言通信建设对于Flutter的通信主要分为以下两部分:1:前面提到,企业微信是通过C++来实现逻辑层的跨平台,企业微信作为原生与Flutter跨平台融合工程,为了提高开发的效率,以及与原生平台的兼容,避免不了需要复用底层C++已有的能力,并且由于调用量巨大,Flutter上要能够通过高性能的通道直接调用到C++层。2:Flutter上层的开发避免不了使用原生已有的接口,需要与宿主工程的接口打通,而宿主工程又包含Android/iOS/MAC/Windows四大平台,并且上层的接口使用的语言各不一样,因此需要考虑一套多端跨语言的通信建设。1:如何高效复用C++统一跨平台能力dart2.15之后提供了dart::ffi的方式调用c/c++,在项目的实际开发过程中,我们也遇到一些大型工程下ffi的使用问题:1:dart调用c++操作步骤繁琐,接口维护和约束困难2:c++调用dart方法只支持静态方法或者顶层函数3:dart上开放了指针的分配和释放,调用c++之后内存管理混乱,容易造成内存泄漏4:如果出现接口绑定不匹配的情况或者so忘记更新,会导致全局的异常,影响正常开发流程为了解决以上的问题,我们参考grpc的设计流程,设计了一套跨语言的rpc调用模型,通过protoc插件来自动生成dartclient端和c++server端的接口,简化了开发的成本,并且对接口进行了一定的约束。另外调用c++的接口不再受限于静态方法或者顶层函数,开发调用c++的接口就跟调用本地的dart接口是一样的。在rpc的调用过程中,通过将rpc的transport层,替换成各个语言之间的调用通道,在Flutter上就是利用单个ffi接口进行请求的收发,从而达到跨语言调用的目的,在框架内部进行线程以及内存的维护与管理。调用的流程如下:final GovernRpcServiceApi service = GovernRpcServiceApi(CppChannel());final req = GetGovernMyReportListReq()..limit = 10;final result = await service.getGovernMyReportListFromServer(req);集成部署的情况下,能够通过ffi直接调用到底层,各端能够很好复用已有的能力。但是在win上,由于是企业微信采用的是多进程的架构,需要Flutter应用进行独立部署,与企业微信宿主之间的通信需要经过企业微信的ipc通道,如果是独立部署的Flutter应用,在transport层,将数据通道从ffi转换为ipc的通道,以此来达到调用企业微信跨平台底层的能力。虽然对于不同的部署方式transport的传输通道会有区别,但是对于开发者来说,调用确是透明的,开发不需要关心当前走的是ffi还是ipc,也不需要关心当前Flutter应用的打包以及运行方式。2:四端跨语言接口调用方案Flutter提供了channel的方式进行原生平台接口的调用,如果只是依靠channel的方式来进行与原生平台通信,接口的维护就会变得非常麻烦,由于平台上的扩展,各端沟通成本也会提高,channel不适合于大型工程上的开发。官方推荐通过pigeon的方案来自动化生成接口,但是pigeon早期尚未支持桌面端,因此不适用于企业微信的业务开发,另一方面,pigeon的接口依然是通过channel来维护的,企业微信的接口需要考虑服务发现、动态注册、安全校验等能力,通过pigeon的维护方式不便于处理这些场景。因此,在dart调用c++的基础上,我们继续扩展了dart调用其他平台接口的能力,并且实现了一套channel的自动化框架:rpc-channel,和pigeon的主要区别如下:我们通过protobuf来统一各个平台的接口,并且实现protocplugin为我们生成各个平台的接口代码,再由各端实现grpcserver端的分发以及处理请求的能力。native平台作为server端只需要实现对应的接口即可。在Flutter端我们依然通过grpc的接口来进行调用,只不过调用所需要的transport通道变成了platformChannel的方式来调用,通过这种方式,我们收拢了所有的channel调用接口,并且都通过单个channel来做数据分发,单个channel的方案,更加方便于我们对所有的channel接口进行统一的管理,做服务发现、安全校验、统一日志等逻辑。调用的方式如下://由CppChannel 变成了PlatformChannel,通道即发生了变化final GovernRpcServiceApi service = GovernRpcServiceApi(PlatformChannel());final req = GetGovernMyReportListReq()..limit = 10;final result = await service.getGovernMyReportListFromServer(req);五、融合工程遇到困难与挑战1:windows针对cpp/channel跨进程通信在windows上,为了减少与主工程的耦合性,我们将Flutter插件作为独立的进程运行,跟其他端不一样的是,Flutter与原生工程的通信方式会有一些改变,包括我们的channel以及底层的调用,因此我们在企业微信的ipc通信的基础上,实现了channel/dart2cpp的通信,具体的调用流程如下:win由于是独立进程,dart2cpp以及channel的调用都是在独立进程下的,因此没办法直接调用到宿主的工程,要借助于企业微信的ipc通信,从上面介绍过channel以及ffi接口,由于我们对channel以及dart2cpp的接口进行统一的管理,所有的事件都会经过stub类来进行集中处理装包并进行数据的传递。我们在stub里面,额外对win进行了适配,如果是win会将请求通过ipc转发到宿主工程上,而不是直接调用分进程的接口,调用的过程如下。2:windows32位编译问题以及处理方案在Flutter在3.0之后在engine层面提供了32位windows的编译选项,但是由于dart的限制,也是只允许编译jit的模式,并且Flutter层面的编译尚未支持,企业微信在探索jit的模式是在3.0之前,比官方更早地完成了32位jit的适配,并且包含了Flutter32位windows编译选项的改造:1:由于3.0之后已经支持32位的编译,Flutterengine可以编译windowsjit-release产物,相关的gn命令以及build如下:python .\flutter\tools\gn --target-os=win --windows-cpu=x86 --runtime-mode=jit_release --no-gomaninja -C .\out\win_jit_release_x862:Flutter编译改造Flutter编译windows主要是通过Flutterbuildwindows相关的命令,默认是编译64位的包,并且没有相关的参数支持32位的编译,编译完32engine之后,需要改造flutter仓库相关的代码,适配32位的windows。Flutterbuild相关的命令主要是由packages/flutter_tools/bin/flutter_tools.dart经过dart编译得来的,修改flutter_tools的代码之后,删除bin/cache/flutter_tools.snapshot,执行flutterdoctor命令重新编译flutter_tools,即可更新flutterbuild命令。这里我们根据Flutterbuildwindows的流程,增加jit-release的编译模式。Flutterbuildwindows相关的流程核心主要是从BuildWindowsCommand开始。主要要修改的是:_runCmakeGeneration:主要是通过cmake命令编译win产物,需要将targetPlatform作为参数传进来,如果是x86架构,cmake命令后面要加上Win32参数,以便构造Win32的产物。3:Win7特定版本打开Flutter黑屏的问题在线上的投诉中,有部分win7设备的用户反馈黑屏的问题,经过分析黑屏的用户都是在win7某一个特定的小版本上,Flutter上也有相关的issue在跟进:https://github.com/flutter/flutter/issues/89583目前issue上提供的解决办法是安装.net库解决,但是并没有定位的真正的原因,企业微信通过分析DirectX相关的库,发现黑屏的用户主要是缺少d3dcompiler_47.dll库引起的,通过内置这个库就可以解决这个问题,而不用引导用户安装.net。4:Win分进程窗口无法前置问题:当点击Flutter的区域时,无法将企业微信窗口前置。原因:由于windows采用了多进程模型,企业微信和Flutter不在同一个进程中,点击Flutter区域只是激活了Flutter进程的窗口,企微对应的窗口没有激活。解决方案:在Flutter窗口收到鼠标激活消息时(WM_MOUSEACTIVATE),将该窗口对应的Ancestor窗口前置。5:Windows7搜狗输入法错位问题错误现象:输入nihao,按1确认输入你好,再继续输入其他文字,会把你好给删掉。出错的跟本原因:搜狗在win7(win7SP1)系统上输入法确认输入的时候,会同时发GCS_COMPSTR和GCS_RESULTSTR两个输入法消息,在win10上是只有GCS_RESULTSTR一个的,这种消息的错乱直接导致Flutter在处理composing文字的时候出现反馈中的问题。错误分析:从收到的输入法消息上看,在确认输入的时候多了一个GCS_COMPSTRcommit的消息,这个消息是个空的。commit为空消息会把当前正在输入的内容清空。Engine层收到这个空的消息之后,会把engine层把正在输入的文字全部清掉,然后通过channel通知Flutter,Flutter收到消息之后,发如果个空的消息,就会通过channel通知enginesetText为空(只有空文本这个时机才会触发flutter->engine)。问题在于engine通知Flutter的过程是个异步的,在通知Flutter之后,紧接着RESULTR事件来了,RESULT事件将engine层的text设为“你好”,改完了之后,flutter通知engine上一次处理GCS_COMPSTR事件来了,又把engine层的文本给清空,后面的事件中Flutter都不会通知engine,所以在下一次输入的时候,engine层认为输入框中正在输入的文字是空的。引发出来的文字错乱问题:前面的文字被莫名其妙删除之后,再输入文字,会出现重复的文本。错误原因:在Flutter通知engine更新text为空的时候,导致Flutter记录composingRange的数据出错,range变成了(0,0),range出错直接导致UpdateComposingText的过程中:text*.replace(composing_range*.start(),composingrange.length(),text)replace就会在原有文本的0坐标下,替换成新的text,但是由于length是0,所以就出现了重复的情况。解决办法:调换处理GCS_COMPSTR和GCS_RESULTSTR的逻辑,让GCS_COMPSTR空的消息最后处理。这种解决办法的核心在于,engine在处理GCS_RESULTSTR消息的时候,会有一个CommitComposing的逻辑处理,表示结束掉当前的composing状态,当结束composing之后,收到GCS_COMPSTR为空的时候,因为composing的文字为空了,再去处理composing中的文字已经没有意义了。6:Mac内存泄漏内存泄漏问题1:由于Flutter目前还没有考虑到混合工程的结构,因此在接入到企业微信之后,每次进出Flutter应用,发现对应的FlutterEngine都没有被释放,这种问题在独立应用中是没有的。因此我们开始分析并且解决了内存泄漏相关的问题。泄露FlutterEngine的主要原因:FlutterEngine中通过弱引用持有viewController,当viewController退出的时候,会触发engine.setViewController=nil,但是viewController因为弱引用的关系,已经变成nil了,导致后面shutdownEngine相关的逻辑都不会执行。解决的办法:修改FlutterEngine的实现,engine.setViewController=nil的情况正常触发后面的流程。- (void)setViewControllerFlutterViewController*)controller { if (_viewController != controller || controller == nil) { //正常触发后面的逻辑 }}2:退出Flutter页面,FlutterEmbedderKeyResponder和FlutterKeyboardManager内存泄漏。原因:FlutterEmbedderKeyResponder通过block强引用了FlutterKeyboardManager,而FlutterKeyboardManager又通过addPrimaryResponder引用FlutterEmbedderKeyResponder从而造成循环引用。解决办法:修改FlutterKeyboardManager.mm的代码,通过弱引用来解除这个循环引用的关系。低版本OpenGLcrash析构引起的crashcrash的主要原因:为了解决内存泄漏,Flutter在退出的时候完全释放到Flutter相关的引用,从而导致触发了FlutterOpenGLRenderer释放OpenGLContext,在10.13的或者更低的系统上,openGLContext在析构的时候会出现了crash。解决办法:在FlutterOpenGLRenderer中,让openGLContext不要释放,来规避这个crash。六、UI体验优化以及调试工具1:四端UI组件库在四端的ui组件上,我们分为了移动端和桌面端两套UI组件,在组件中我们除了完善企业微信现有组件外,对各端常遇到的体验问题也做了改进。移动端组件体验优化IOS原生容器与Flutter容器切换导航栏优化背景:企业微信采用的是单容器多Flutter页面的混合栈方式,Flutter内部通过CupertinoNavigationBar来模拟IOS导航栏的切换效果。但是Flutter的导航栏采用的是自渲染的方式,ios的导航栏在切换到Flutter容器的时候,由于是两个不同的导航栏,导致原生导航栏的动画无法正常衔接上,就会出现两个导航栏同时位移的动画,如图所示:为了解决以上的问题我们探索了两种方案:1:Flutter单页面单容器的方案,导航栏由原生来渲染,页面的切换动画完全由原生来控制。2:原生切换到Flutter容器的时候,先展示IOS的导航栏,动画消失后再把IOS的导航栏隐藏掉。第一种方案的好处是达到原生一致的效果,但是对于Flutter开发来说,导航栏的自定义性就会变得很差,如果要渲染icon,响应点击事件就会变得非常麻烦,而且与导航栏相关的交互情况要考虑得也非常多,对现有的混合栈结构的改动非常大。因此我们采用的是第二种方案,在容器和Flutter上实现了一套带原生动画的导航栏,在进入Flutter容器动画的过程中,会先展示ios原生的导航栏,flutter在导航栏渲染之后,会通过截图的方式将导航栏上的元素截给native,native通过的方式在导航栏上渲染flutter的元素,动画完成的过程之后,再隐藏掉原生容器的导航栏。实现上述技术点的关键在于Flutter导航栏要做到:1:IOS的NavigationBar在页面初始化的时候就必须得准备好颜色和布局,后续动画的过程中不能对颜色和布局进行变更,在进入Flutter页面之前,先读配置文件或者由代码指定导航栏样式。另外由于NavigationBar的元素在动画的过程中也是不能进行变更的,我们利用ImageView提前在NavigationBar上占位,动画的过程中,只更新ImageView的内容。2:Flutter导航栏渲染出来的效果和IOS导航栏的渲染效果必须是完全一致的,这样在原生的导航栏消失之后才不会出现闪动的情况,因此需要我们对Flutter上的导航栏进行一些改造,对齐IOS的导航栏规范。3:需要对Flutter导航栏上的元素进行截图,并且遇到导航栏元素刷新的情况,截图有可能是多次的,如果不通过截图的方式,遇到icon或者中英文、大小字体的情况,Flutter的导航栏是很难对齐原生的,这里用进行传输,实测下来也并不会影响到实际体验。实现之后整体的效果如下,切换到Flutter容器跟其他原生页面是完全一致的体验。已关注关注重播分享赞关闭观看更多更多腾讯技术工程已关注分享点赞在看已同步到看一看写下你的评论分享视频,时长00:050/000:00/00:05切换到横屏模式继续播放进度条,百分之0播放00:00/00:0500:05倍速播放中0.5倍0.75倍1.0倍1.5倍2.0倍超清流畅您的浏览器不支持video标签继续观看企业微信Flutter与大型Native工程跨四端融合实践观看更多转载,企业微信Flutter与大型Native工程跨四端融合实践腾讯技术工程已关注分享点赞在看已同步到看一看写下你的评论视频详情IOS导航栏内部切换效果优化在实现完容器直接切换的动画之后,我们面临第二个问题,内部的导航栏动画优化,如果是两个相同背景颜色的导航栏之间的切换,Flutter几乎是达到了原生一致的效果,但是如果两个导航栏上颜色不一致,企业微信上会有更加复杂的动画:而Flutter对不同颜色的导航栏之间的切换采用的是渐变的方案,但是设计希望对齐企业微信以及微信原生的表现,页面和导航栏都有整体的拖动效果,但是导航栏的元素是不会产生较大的变化。解决办法:我们改造了CupertinoNavigationBar的动画,CupertinoNavigationBar在模拟器IOS动画的过程中,其实是利用了Hero相关的特性,通过HeroFlightShuttleBuilder了完全重写了Hero动画。动画整体的思路在于,去掉渐变相关的动画,并且通过Stack的组件,在原有导航栏动画的基础上,新增与当前导航栏颜色一致的Container,利用ModalRoute.of(context)的方式,拿到页面的转场动画(这里与hero的动画是有区别的),最后对Conatiner做SlideTransition转场动画。额外需要注意的是,用户侧滑返回跟点击返回的动画是有区别的,需要做一些判断:实现的效果如下:已关注关注重播分享赞关闭观看更多更多腾讯技术工程已关注分享点赞在看已同步到看一看写下你的评论分享视频,时长00:040/000:00/00:04切换到横屏模式继续播放进度条,百分之0播放00:00/00:0400:04倍速播放中0.5倍0.75倍1.0倍1.5倍2.0倍超清流畅您的浏览器不支持video标签继续观看企业微信Flutter与大型Native工程跨四端融合实践观看更多转载,企业微信Flutter与大型Native工程跨四端融合实践腾讯技术工程已关注分享点赞在看已同步到看一看写下你的评论视频详情以上两个是IOS遇到的体验影响比较大的问题,还有其他一些对齐IOS点击态效果、文本输入框下划线对齐IOS背景色、侧滑返回快速点击无响应等体验问题我们也都在组件中完善并且解决了,并且提供了demo的独立程序。桌面端组件完善在桌面端接入Flutter之后,Flutter目前对桌面端的组件完善程度并不够,我们也在完善桌面端相关的UI组件,并且提取了一些桌面端组件常见的问题:1:Flutter提供了MouseRegion来实现Hover态,开发在实现组件的时候需要关注桌面端组件与Hover的操作,这种表现在移动端是没有的。2:对键盘事件的处理,比如列表需要支持按住某个按键切换为横向滚动,实现上可以利用Listener监听鼠标的滚动事件,并且通过pointerSignalResolver做相应的拦截,拦截之后,将controllerjump到横向指定的offset。下面是Flutter桌面端的组件库:2:Flutter窗口控件化因为引入了分进程,Flutter与企业微信不在同一进程中,通过分进程打开的Flutter页面属于分进程的一个独立窗口。窗口的生命周期和样式不在企微中管理,这种方式很难适配复杂的业务场景。相当于每个使用了Flutter的业务都要关心Flutter窗口的样式,在不满足业务场景时,要修改分进程代码支持。对业务方不友好且很难维护。改进方案如下:将FlutterWindow作为子窗口嵌入企业微信的HostWindow中通过FlutterConatinerView控制HostWindow的显示区域通过这两层封装在使用层面上将window降级view,使用Flutter就可以和使用Control或者Widget一样方便。FlutterProcessManager负责管理分进程,当创建FlutterContainerView时,如果分进程还没启动,则唤起分进程IPCController则负责和Flutter进行通信,通过FlutterContainerView告知分进程打开指定的Flutter页面。封装之后,窗口的层次关系如下。Flutter只负责展示业务内容,窗口的属性、样式等,都通过企业微信来设置。通过和其他View进行组合使用,可以达到如图所示的效果。3:windows文字渲染以及阴影等问题win在文字渲染上遇到两个比较严重的问题:文字渲染的细节不对这里是因为Flutter默认使用skia的渲染模式是grayscale灰度字体渲染方式,但是在win客户端普遍使用的是subpixel渲染方式。导致文字渲染跟win有一些区别,这部分需要我们通过修改engine来修复核心代码为:SkPixelGeometry pixel_geometry = kUnknown_SkPixelGeometry;#ifdef WIN32 UINT structure = 0; if (SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION, 0, &structure, 0)) { if (structure == FE_FONTSMOOTHINGORIENTATIONRGB) pixel_geometry = kRGB_H_SkPixelGeometry; else if (structure == FE_FONTSMOOTHINGORIENTATIONBGR) pixel_geometry = kBGR_H_SkPixelGeometry; }#endif SkSurfaceProps surface_props(0, pixel_geometry);#ifdef WIN32 font.setEdging(SkFont::Edging::kSubpixelAntiAlias);修复前:修复后:渲染字体错乱在某些win的机型上,如果当前系统语言不是简体中文,Flutter渲染的字体会有明显的误差,文字展示比较奇怪,不是标准的简体中文。主要原因是,Flutter在渲染字体的时候,用系统当前默认的字体去渲染,当前的字体如果无法渲染这个文字,就会自动匹配一个字体来完成这个文字的渲染,这里由于skia的匹配算法匹配到了其他语言去,因此导致了渲染文字出错。Fluttertext组件中提供了一个文字渲染失败的回调fontFamilyFallback,如果当前字体无法渲染字符的时候,会回调到Flutter上层,可以由Flutter上层指定要用字体,这里我们给这个回调指定了微软雅黑,从而解决语言错乱的问题。修复前:修复后:4:应用独立部署调试整个环境搭建起来之后,因为Flutter四端跨平台的能力,移动端的同学也能够去开发一些桌面端的应用,但由于是混合开发的模式,开发别的平台应用的时候,需要别对应平台的工程代码,并且不同平台的开发环境以及仓库都不一样,桌面端工程的开发对于移动端的同学来说非常不方便。现有的组件化模式本质还是一个大仓全代码的编译过程,虽然代码按模块隔离了,但是编译的时候没有做到隔离,debug阶段还要严重依赖宿主工程。为了提高开发以及走查的效率,我们将Flutter的主工程拆分为多个微应用,为每个业务模块提供exampleapplication的运行的能力,并且在example中依赖于runner的基础组件,runner主要提供grpc的远程调用服务,负责将channel/dart2cpp的接口通过grpc远程调用发送给服务端,这里的服务端就是我们的宿主app,通过这种模式,在调试阶段,将Flutter应用完全从企业微信的宿主app里面解耦开来,带来的好处是,更快的编译速度,更全的平台开发体验,更稳定的调试系统。最后,在开发Flutter业务的时候,我们只需要debug版本的企业微信应用程序即可与原生进行通信,业务模块只需要依赖Flutter环境就可以独立运行起来。七、总结企业微信使用Flutter统一了四端的UI开发框架,在业务开发上效率得到了明显的提升,以企业微信首个跨四端的大型应用人事助手为例,相比于四端独立开发,使用Flutter作为跨平台开发,整个需求的迭代协同效率大大提升:1:UI开发上我们统一了dart技术栈,不同平台的同事都可以参与到Flutter开发当中来,解决桌面端人力不足的问题。2:通过移动端跨平台+桌面端跨平台的方案+mvvm的架构,我们研发效能提升1倍以上。3:对于设计/产品走查都只需要移动端和桌面端各走查一次,测试对ui渲染层也只需要测单端,节约了1倍的人力。得益于移动端的模块化架构,桌面端的工程可以很好复用移动端已有的基础组件能力。我们将ui的数据以及交互从各端UI中分离,由provider进行统一的处理,来简化各端UI上的开发成本,桌面端和移动端UI开发只需要简单的布局即可,结构如下:例如在人事助手的首页中待处理消息的列表卡片UI,两个卡片无论布局还是显示效果都有明显的差别,在UI上不能完全复用。桌面端移动端通过上述的开发结构,整体的流程如下:1:通过mixinTodoInfoAdapter的方式,约束各平台UI组件所需要的数据字段,以及交互。2:桌面端和移动端分别使用对应的ui进行布局,将uiWidget和TodoInfoAdapter进行数据的绑定。3:provider作为viewmodel,在初始化的时候通过cgi请求,对proto数据进行处理,这里与model层进行交互。4:provider将cgi的resp中的相应数据转换成为ToDoInfoAdapter,转换成功之后通过notifyListener刷新ui的数据。5:provider根据resp中的部门id,异步拉取部门的数据,拉到数据之后,更新adapter,调用notifyListener重新更新ui数据。对于cell的点击事件,也是作为adpater中的一个参数,在viewmodel(HrSystemHomeProvider)中统一处理。由于四端的代码复用,桌面端首页卡片Cell减少了大约48%的重复代码。目前企业微信也在不断利用和完善Flutter四端的能力,也在自研引擎上修复了不少Flutter的问题,提高Flutter在跨平台上的开发体验。在整个社区的贡献上,企业微信在Flutter方向参与过多次对外分享活动,整个2022年上半年GoogleGDG给我们发出了三次分享邀请:2022年4月先后两次受邀参加google在国内的首届FlutterFestival,为广大的中国Flutter开发者们,创造更多技术交流与分享的机会。2022年6月GoogleGDG在国内举办的GoogleIO/ExtendFlutter专场活动,企业微信在该次大会上分享主题是《企业微信Flutter跨四端研发实践》,重点介绍了近半年企业微信在Flutter跨四端融合领域的研发成果,期间对于Flutter工程插件化、容器管理、通信建设、跨平台远程GRPC独立调试、win32位兼容、桌面端稳定性等多项开发者关注问题进行分享。
|
|