起点读书客户端一直紧跟新技术的潮流,从很早开始,就在进行Flutter的尝试,在筹备了许久之后(移除了包大小的KPI指标),我们终于在最新的业务开发中,使用了Flutter。
Flutter虽然会带来一些包体积的增加,但带来的收益却是:
千呼万唤始出来,让我们来看下起点读书客户端是如何进行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的架构方案。
在当前架构下,Native端需要实现一套通用的Channel协议,通过BasicMessageChannel�来实现一些对性能较高的Channel场景,例如埋点、网络请求等,通过MethodChannel来实现一些通用的Native方法调用,以及拓展业务特有逻辑的方法调用。
在创建Flutter页面时,借助dartEntrypointArgs�,可以从Native传递相应的参数,代码如下。
在Flutter端,可以在entry-point�中获取相应的参数。
// XXX主页
@pragma('vm:entry-point')
void XXX(List<String>? args) => SafeApp().run(ThemeApp(args: args, child: XXXPage(args)));
❝后续继续优化,可以将参数封装为一个Map,双端进行解析,这样在获取参数时能更加具有语义化。 ❞
Channel在这个方案中扮演着非常重要的角色,它是Flutter和Native之间的纽带。我们根据Channel的使用场景,将其分为了四类。
对于性能要求较高的场景,我们使用BasicMessageChannel�,而单次的Native调用,我们使用MethodChannel�,经过不同的场景(超大数据量、连续多次频繁请求)等测试,其数据符合预期,调用延时相对于请求时间来说,基本可以忽略不计,同时,其稳定性也经受住了考验。
❝在开发的早期阶段,桥接函数不太丰富的前提下,在开发Flutter时,有时候还是需要Native开发人员对新的通用桥接函数进行补充,但随着函数的丰富,这个操作会越来越少,最后逐渐就不太需要Native开发人员参与Flutter的开发与调试了。 ❞
由于目前起点读书的轻量化引擎架构设计,Flutter只作UI渲染,所以我们需要充分利用Flutter的热更新特性,提高业务UI的开发效率。
目前起点读书的黑夜模式,有两种设置方式,一种是跟随系统,用户可以在App内部设置,也可以在手机内进行切换,另一种是手动在App内部设置固定黑夜模式或者非黑夜模式。
Flutter页面在创建时,会传入当前App设置黑夜模式的枚举——「system」、「light」、「dark」�,在Flutter中,会根据设置的模式来进行切换。而如果用户在打开Flutter页面后,在手机内进行黑夜模式的切换,Flutter也会自动进行切换。相关代码如下。
在处理颜色的映射时,我们会根据设计给出的映射关系,根据是否为黑夜模式,将指定token映射为对应的色值,例如下面的示例。
Color get surface_gray_700 => isDarkMode ? white_alpha_70 : gray_700;
黑夜模式与非黑夜模式的设置是通过拓展ThemeExtension�来实现的,并在MaterialApp�中分别设置theme、darkTheme�和themeMode�,从而利用Theme,在切换时,自动对颜色Token进行切换映射。
由于起点读书有内嵌字体,以及多行文本的Font Matrix问题,这些都会对设计师的文本设计造成困扰,在Flutter中,我们通过StrutStyle�来与设计师对齐字体和Font Matrix的影响,让开发者在开发过程中,避免单独处理文本的设计问题,同时,也减轻设计师的走查工作量。
在代码中,只需要使用设计师给出的字体token即可,代码如下。
内嵌的Native字体,会在初始化的时候通过Channel从Native桥接过来,借助FontLoader�来加载字体的uInt8List�数据流。
统一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的渲染状态,从而对其进行埋点,封装后的代码如下。
埋点数据同样是通过Channel桥接到Native,复用原有的埋点和上报逻辑进行处理。
当前起点Flutter工程的目录结构如下。
其中:
当业务开发者拿到业务需求后,只需要在example中增加业务页面的临时入口,即可进入开发流程。
通常情况下,我们会先进行需求评审,接下创建接口协议,并输出接口mock数据,在Flutter业务的开发过程中,需要首先在example中,增加mock数据,如下图所示。
然后在QDFlutterDemoMainPage�中,增加mock的支持。
这样处理之后,我们在开发Flutter业务逻辑时,就可以直接运行Flutter代码,而不用依赖Native。最后,在lib/main.dart中,增加业务逻辑页面的入口函数。
如果业务不包含特殊的业务处理,则直接通过�BaseFlutterActivity.start�的方式进行调用即可,如果是包含特殊业务处理的,则需要使用其继承类来进行调用,并在继承类中,实现自身的特殊业务处理。
在当前git业务分支开发完成后,需要在当前git分支执行build_aar.sh脚本,编译aar包到指定的Native目录下,然后在Native正常进行编译即可,在混编模式下,Flutter页面会自动适配到从Native获取数据,而不是从mock端获取数据,此时即可与后端进行联调。
❝在Debug编译下,可以通过Flutter Attach来进行断点调试。 ❞
当功能测试完成后,需要将业务分支合并到master分支,并重新编译aar后,合入Native的master分支,并进行回归测试。
在使用Flutter对Native业务进行辅助开发后,带来的收益主要是下面几个方面。
首先,对于开发人员来说:
其次,对于测试人员来说:
另外,对于设计人员来说:
在起点读书最新上线的新版书单广场页面中,我们使用Flutter来进行开发,打通了Flutter和Native之间的从评审、开发、测试到视觉走查的一系列流程。