5

Flutter混编方案在起点客户端的实践之路

 11 months ago
source link: https://blog.csdn.net/eclipsexys/article/details/133327227
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

658538e26e54f8da04a1290aa78df21d.png

点击上方蓝字关注我,知识会给你力量

f104c2d30751c4bc93ecde7ba03b938e.png

起点读书客户端一直紧跟新技术的潮流,从很早开始,就在进行Flutter的尝试,在筹备了许久之后(移除了包大小的KPI指标),我们终于在最新的业务开发中,使用了Flutter。

Flutter虽然会带来一些包体积的增加,但带来的收益却是:

  • 愉悦的开发体验,不用再忍受漫长的编译,强大的热更新可以快速进行UI开发

  • 跨端的统一UI设计,双端不用做太多的适配就可以运行

  • 开发效率提升一倍以上,同时也提升设计和测试的效率

千呼万唤始出来,让我们来看下起点读书客户端是如何进行Flutter混编开发的。

轻量化引擎架构

由于起点读书客户端目前依然是以原生开发为主,所以我们在嵌入Flutter模块时,首先要考虑的就是使用哪种混合栈方案。目前市面上常见的混合栈方案,主要就是两种:一种是以Flutter Boost为例的单引擎方案,另一种是以EngineGroup来实现的多引擎方案。这两种混合栈的方案,各有一些优缺点。

优势劣势
单引擎混合栈与Native独立开来,对Native的依赖最小,混编开发流程清晰、方便混编存在混合栈内存泄漏等问题,会存在一些底层的Crash;Flutter和Native端都需要独立实现一套完整的网络和基础库等基础模块
多引擎混合栈相比单引擎方案,性能更好,在多实例下内存压力更小,开发更加简便对Native依赖大,多实例之间数据无法共享,需要从Native进行转发

我们在这两种架构方案的基础上,各取所长,提出了一套轻量化的引擎架构。

首先,我们采用的是EngineGroup的混合栈方案,这个方案对于小规模嵌入Flutter页面的NativeApp来说,是性价比最高的选择,它的技术架构如下所示。

但是和传统的Flutter开发方案又有所不同,我们将Flutter页面当作一个渲染容器,在一个Flutter页面中,所有的数据都来源于Native,Flutter只作渲染UI和处理交互逻辑,这种方案和之前的方案进行对比,结果如下。

优势劣势
轻量化引擎方案Flutter端极其轻量,极大化利用Flutter高效开发UI的优势数据依赖Native,存在双向通信,Native需要作一些额外的处理

在这种轻量化的架构方案下,我们作了一些改进:

  • Flutter只作UI渲染,网络请求、基础数据等内容,都通过Native桥接,最大化的复用了Native的已有能力,Flutter端不用重新复刻一套,同时,对于UI上的异常,可以很方便的进行处理,避免在Flutter中使用网络库等能力时带来的新异常

  • 抛弃Flutter端常规的路由概念,将业务页面封装为独立的scheme,类似Native中的ActionUrl,简化调用逻辑

  • 通过搭建一套数据mock的方式,在Flutter端进行开发,避免在开发阶段,需要从Native获取数据的问题

综上所述,经过多方面的考虑和调整,最终确定了当前起点读书Flutter的架构方案。

a9693b2de26ba446b7d2f893ab7c5dc9.png

在当前架构下,Native端需要实现一套通用的Channel协议,通过BasicMessageChannel�来实现一些对性能较高的Channel场景,例如埋点、网络请求等,通过MethodChannel来实现一些通用的Native方法调用,以及拓展业务特有逻辑的方法调用。

在创建Flutter页面时,借助dartEntrypointArgs�,可以从Native传递相应的参数,代码如下。

e86fd63c3634fed2ebe99e52eaf07889.png

在Flutter端,可以在entry-point�中获取相应的参数。

后续继续优化,可以将参数封装为一个Map,双端进行解析,这样在获取参数时能更加具有语义化。

Channel在这个方案中扮演着非常重要的角色,它是Flutter和Native之间的纽带。我们根据Channel的使用场景,将其分为了四类。

  • NativeCommonApi:适用于与Native频繁通信或者对性能要求较高的场景。

    • reportException:上报通用异常

    • reportTrackerImpression:上报页面曝光

    • reportTrackerClick:上报点击

    • reportTrackerColumn:上报栏目曝光

    • doActionUrlCall:执行Native action url

  • NativeFontApi:加载Native字体。

    • loadNativeFont:加载Native字体

  • NativeNetApi:加载Native网络请求数据。

    • getNativeNetBridge:通用get请求

    • postNativeNetBridge:通用post请求

  • NativeMethodCall:适用于与Native的单次交互场景,通用的方法需要在这里新增,并在Native中新增实现。

    • finish:关闭Flutter页面

    • canPopFlutterPage:是否可以Pop当前Flutter页面(iOS用)

    • isInBookShelf:获取当前bookId是否在书架

    • getUserGender:获取当前用户Gender

    • showToast:Native显示Toast

对于性能要求较高的场景,我们使用BasicMessageChannel�,而单次的Native调用,我们使用MethodChannel�,经过不同的场景(超大数据量、连续多次频繁请求)等测试,其数据符合预期,调用延时相对于请求时间来说,基本可以忽略不计,同时,其稳定性也经受住了考验。

在开发的早期阶段,桥接函数不太丰富的前提下,在开发Flutter时,有时候还是需要Native开发人员对新的通用桥接函数进行补充,但随着函数的丰富,这个操作会越来越少,最后逐渐就不太需要Native开发人员参与Flutter的开发与调试了。

UI组件化的实践

由于目前起点读书的轻量化引擎架构设计,Flutter只作UI渲染,所以我们需要充分利用Flutter的热更新特性,提高业务UI的开发效率。

黑夜模式与颜色Token

目前起点读书的黑夜模式,有两种设置方式,一种是跟随系统,用户可以在App内部设置,也可以在手机内进行切换,另一种是手动在App内部设置固定黑夜模式或者非黑夜模式。

Flutter页面在创建时,会传入当前App设置黑夜模式的枚举——「system」、「light」、「dark」�,在Flutter中,会根据设置的模式来进行切换。而如果用户在打开Flutter页面后,在手机内进行黑夜模式的切换,Flutter也会自动进行切换。相关代码如下。

7ea04f818f97ddd4980c5321942f1d31.png

在处理颜色的映射时,我们会根据设计给出的映射关系,根据是否为黑夜模式,将指定token映射为对应的色值,例如下面的示例。

Color get surface_gray_700 => isDarkMode ? white_alpha_70 : gray_700;

黑夜模式与非黑夜模式的设置是通过拓展ThemeExtension�来实现的,并在MaterialApp�中分别设置theme、darkTheme�和themeMode�,从而利用Theme,在切换时,自动对颜色Token进行切换映射。

字体与TextStyle

由于起点读书有内嵌字体,以及多行文本的Font Matrix问题,这些都会对设计师的文本设计造成困扰,在Flutter中,我们通过StrutStyle�来与设计师对齐字体和Font Matrix的影响,让开发者在开发过程中,避免单独处理文本的设计问题,同时,也减轻设计师的走查工作量。

5b99cc9ae667e0224393ba6b1bd6f2b8.png

在代码中,只需要使用设计师给出的字体token即可,代码如下。

1a17e374128e81bf60959339f181da75.png

内嵌的Native字体,会在初始化的时候通过Channel从Native桥接过来,借助FontLoader�来加载字体的uInt8List�数据流。

UI组件库

统一UI组件库,可以让开发者在开发的过程中,快速创建符合设计规范的组件,减少开发工作量,同时也减少设计的走查工作量,目前直接运行example中的main.dart即可启动启动Flutter UI组件库展示程序,如下所示。

在这里开发者可以查看当前已有规范的UI组件,并查看相关示例代码,直接在项目中使用。同时,后续会抽取更多的通用组合组件,例如通用书卡、通用评论、工具栏等组件,进一步提高开发效率。

借助Flutter的跨平台功能,example可以非常方便的运行在Web中,从而方便给设计师和产品进行走查。

图片外接纹理

Flutter原生的Image处理方式存在一些内存问题,虽然使用简单,但是对内存水位的影响较大,特别是在复用一些Native缓存图片的场景,所以,针对图片的处理,我们使用了外接纹理的方式,借助Texture来实现图片在Native的处理逻辑,复用Native已有的、成熟的处理方式,例如在Android中使用Glide进行图片加载,并控制缓存。

通过图片外接纹理,显著降低了Flutter对于图片的内存消耗,同时也减少了内存抖动,让Flutter更加稳定。相关技术方案,可以参考我的这篇文章。
Flutter混编工程之打通纹理之路

对于业务App来说,数据埋点,是一件非常重要的工作。起点读书目前在Native的埋点方案,存在很多准确性的问题,其原因就是Native的埋点方式是以数据作为驱动的,由于Native预加载的存在,就会导致有部分埋点提前进行了曝光,而在Flutter中,埋点是以UI作为驱动的,所以其天生就更加符合产品的直观逻辑感受,所以相比Native,Flutter的埋点会更加方便。

借助Flutter开源的visibility_detector�(海外团队封装库reportable),我们可以很方便的检测Widget的渲染状态,从而对其进行埋点,封装后的代码如下。

8236a3dc1d3fbbd2630c8c055a83c72d.png

埋点数据同样是通过Channel桥接到Native,复用原有的埋点和上报逻辑进行处理。

混编开发流程

当前起点Flutter工程的目录结构如下。

128449db1ada71377acb606d1a7520a7.png

其中:

  • example是UI组件库的示例工程

  • lib/biz目录下,是业务开发的主要目录

当业务开发者拿到业务需求后,只需要在example中增加业务页面的临时入口,即可进入开发流程。

通常情况下,我们会先进行需求评审,接下创建接口协议,并输出接口mock数据,在Flutter业务的开发过程中,需要首先在example中,增加mock数据,如下图所示。

6fd2319ca137d30e99386dcd1a5b3b62.png

然后在QDFlutterDemoMainPage�中,增加mock的支持。

0a78fb40dc222effb4c989e698300cca.png

这样处理之后,我们在开发Flutter业务逻辑时,就可以直接运行Flutter代码,而不用依赖Native。最后,在lib/main.dart中,增加业务逻辑页面的入口函数。

714e819db8f8fda20752cdebde5a404e.png

如果业务不包含特殊的业务处理,则直接通过�BaseFlutterActivity.start�的方式进行调用即可,如果是包含特殊业务处理的,则需要使用其继承类来进行调用,并在继承类中,实现自身的特殊业务处理。

在当前git业务分支开发完成后,需要在当前git分支执行build_aar.sh脚本,编译aar包到指定的Native目录下,然后在Native正常进行编译即可,在混编模式下,Flutter页面会自动适配到从Native获取数据,而不是从mock端获取数据,此时即可与后端进行联调。

在Debug编译下,可以通过Flutter Attach来进行断点调试。

当功能测试完成后,需要将业务分支合并到master分支,并重新编译aar后,合入Native的master分支,并进行回归测试。

在使用Flutter对Native业务进行辅助开发后,带来的收益主要是下面几个方面。

首先,对于开发人员来说:

  • 提升Native业务开发效率,原本双端的开发工作,可以统一为单端开发,效率提升将近一倍

  • 双端业务逻辑能保持统一,避免出现iOS、Android不一致的场景

  • 提升编译速度,借助Flutter的热重载和本地Mock机制,可以提高30%的开发效率,不用再等待漫长的编译

其次,对于测试人员来说:

  • 测试人员可以针对单端设备进行业务逻辑的重点测试,再对双端进行回归验证,可以避免在双端执行重复的测试用例

  • 对于埋点的测试,更是可以减少一半的重复验证工作

另外,对于设计人员来说:

  • 设计师可在进行视觉验收时,可以以单端为主,另一端为辅的方式进行走查,验收效率提升将近一倍

  • 由于双端设计统一,不用再处理某端阴影、字体、Blur等需要单独适配的场景,也提高了设计的统一度

在起点读书最新上线的新版书单广场页面中,我们使用Flutter来进行开发,打通了Flutter和Native之间的从评审、开发、测试到视觉走查的一系列流程。

b10acc0c0e8b007768718473a71ed402.png
0f3728477c81c66d769ee9b9d449adab.png
06a115ac04b8cf8a56b35fb43ecc141a.png
3a89bfaf20c5f8418a5987d862ade7ff.jpeg

欢迎大家体验。

向大家推荐下我的网站 https://www.yuque.com/xuyisheng  点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下👇


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK