查看原文
其他

网易新闻客户端Flutter混合开发实践

宋珊珊 网易传媒技术团队 2022-09-10

Flutter简单介绍

Flutter是Google打造的UI工具包,帮助开发者通过一套代码同时在iOS和Android上构建媲美原生体验的精美应用。

开发者可以使用Flutter开始一个全新的应用,也可以把Flutter理解为应用内置的一个引擎,把这个引擎引入到现有的工程中。

Flutter框架图如下:

如上图所示,Flutter 框架被组织为多层结构,每个层都建立在前一层之上。上面统称 Framework,下面的一切都叫做 Engine。Framework完全使用 Dart 编程语言编写。Engine的绝大部分使用 C++ 编写,专属 Android 的部分用 Java 编写,专属 iOS 的部分则用 Objective-C 编写。Flutter的平台相关层很低,平台只是提供一个画布,剩余的所有渲染相关的逻辑都在Flutter内部,这就使得它具有了很好的跨端一致性。

Dart主要由Google负责开发和维护的一种强类型、跨平台的开发语言。具有高生产力、快速高效、可移植、易学的OO编程风格和原生支持响应式编程等优秀特性。

引言

网易新闻项目本身很庞大,业务繁多,全部改为Flutter实现肯定是不现实的,在使用Flutter的前期阶段,我们挑选了相对独立的几个模块,在现有工程的基础上对其进行Flutter改造,以循序渐进的改造方式保证项目稳健,下面以Android为例,从以下几个方面介绍下此混合开发实践过程:

  • 集成方式

  • 开发模式

  • 工程管理

  • 原生端与Dart端通信

  • 混合栈及路由

  • 调试

  • 遇到的问题及解决方案

  • 稳定性保证

Flutter集成方式

如上介绍说明,开发者可以使用Flutter开始一个全新的应用,也可以把Flutter作为应用的一个module,把这个module引入到现有的工程中,因此有如下图的两种接入方式:

网易新闻采用上图右所示的方式,以现有工程为主端的Flutter Module的方式集成到现有工程中去,具体集成方法可参考https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps,开发者在lib文件下使用Dart语言编写相应的业务代码。

开发模式

在网易新闻开发中的debug和release两种模式下,Flutter的构建和编译模式如下:

debug模式下:Flutter编译是支持JIT的, 对于Dart开发者在主工程的build.gradle中以Compile project方式接入Flutter工程,方便开发和调试;

release模式下:Flutter的编译模式为AOT,业务Dart代码直接生成二进制指令文件,如下图所示,其中编写的Dart代码通过gen_snapshot编译器生成 vm/isolate_snapshot_data/instr,engine 通过Ninja 构建系统来生成flutter.jar,包括了engine c++部分的代码(flutter.jar中的libflutter.so),以及一套将Flutter嵌入Android的java类和接口(FlutterMain,FlutterView,FlutterNativeView等),最后通过gradle将这些Flutter相关代码编译和嵌入到原生App中。

通过了解Flutter的构建和编译模式,可知Flutter module的接入最终的产物完全就是一个.aar包, 因此最终我们整个工程的release包futter是以.aar形式直接集成到现有工程中打包的,这样对于目前现有的工程的整个生产流程侵入干扰性达到最小。

另外需要注意以下两点:

  • minSdkVersion 需要至少为16

  • 主工程的build gradle插件版本要升级至3.x

工程管理

上面提到的Flutter module,以git submodule的方式分别引入到Android和iOS两端的主工程中

这样对于Flutter Dart端开发者,在本地工程目录中保留.gitmodule文件,通过git module维护flutter 代码,达到dart开发者协同开发&调试;

而Android和iOS两端native开发人员来说,Flutter module可以完全透明, 在运行时Flutter module会以.aar的方式compile到主工程,这样对原生开发者来说Flutter的接入是无感知的。

原生端与Dart端通信

Flutter虽然是个跨平台方案,在UI,手势触控,动画及基本的网络请求等上已经基本做到平台无关,但是在某些平台特性的功能上,还是必须要根据不同的平台做处理,且在前期过渡改造阶段,有些业务逻辑仍然需要原生端的支持,这就设计到与native的通信。

从上图可以看出,dart端与原生端的通信是通过Flutter提供的channel机制达到双向通信,具体有三种channel:

  • BasicMessageChannel: 主要是传递字符串和一些半结构体的数据

  • MethodChannel: 传递方法调用

  • EventChannel:数据流的通信

其实通过源码可以看出,无论哪种传递,本质上都是数据的传递,通信都是通过BinaryMessage, 具体实现类是FlutterNativeView,使用的消息格式为二进制格式数据,三种channel只不过是上层包装的逻辑不同而已,具体使用哪个通信,根据具体业务实现来定。

在网易新闻项目中,对于消息传递,制定了一套统一的消息协议规范,方便Android,iOS原生端和Dart端的通信。

另外官网上提供的Plugin,基本上都是通过channel来调用原生能力,开发者可根据需要直接在pubspec.yaml中引入使用,另外在pub库中也有越来越多的plugin, 大家也可以自行开发通用的plugin使用。

混合栈及路由

以module的方式接入Flutter, 对于要改造的模块或者页面,在Activity或Fragment的onCreateView中创建FlutterView,Activity或者Fragment为FlutterView的一个容器;另Flutter的framework层提供的navigator管理控制Flutter内部的页面, 如下图中pageWidgetC1,C2,C3的都为Dart实现的flutter页面,而它们是处在同一个Activity中的,因此就有了混合栈的问题(原生栈和Flutter内部栈同时存在)。

对于混合栈路由的处理,如下图左所示,一个Activity里有多个Flutter页面,Flutter之间的跳转切换由Flutter内部栈管理,不过相应的对于页面跳转动画,手势右滑跟随退出等效果也要同原生端效果再实现一套,且需要同Android,iOS原生端现有的保持一致性,但Android,iOS两端现有的本来就是不一致的,因此就有了如下右图所示的第二个方案,一个Activitiy对应一个Flutter页面,路由由原生维护一套,Flutter页面之间的跳转其实是Native的跳转,比如Flutter page1跳转Flutter Page2,需要page1通过约定的消息协议通知到Native,Native收到跳转Flutter页面的协议后会再开启一个承载FlutterView的Activity来绘制Flutter page2页面,但该方案对于跳转层级比较深的页面,多个FlutterView实例对于性能有一定的损耗,因为每个flutterView的底层对应一个engine,  且页面比较隔离,对于数据同步问题要通过native获取,当然对于第一个方案混合路由来说,在原生页面和Flutter页面交替出现的场景下,也同样会存在这些问题。

这两种方案都是可选的,但都不是最优方案,Flutter设计的初衷应该是多以新的纯的Flutter工程为出发点来支持跨平台开发的,对于在现有工程改造的混合开发没有很好的支持,具体采用哪种方案,根据自己项目情况来定,或者更改Flutter的engine来优化内存和数据同步等问题。

网易新闻因为前期选取的改造模块比较独立,一个模块对应一个Activtiy,  该Activtiy里有多个Flutter页面,对于跳转动画,手势右滑跟随退出等效果同Android原生端效果一致,且在统一路由跳转处增加配置项,可切换一对一方案,这样可以保证iOS的接入与原生的交互体验一致性,后续随着越来越多的业务模块改造,对于现有设计方案,我们会不断更新调整优化。

调试

Flutter hot reload是Flutter的一大亮点,可称之为神器,开发者有任何修改,都不需要重新启动应用,即可看到改动效果进入Flutter module, 输入命令flutter attach。

如上就说明连接成功,只要有改动,键盘敲击R或者r 即可看到更改。

如果你运行命令后一直在waiting,可尝试杀死该应用进程后再重新进入相关Flutter页面就可连接上了

遇到的问题及解决方案

SO库兼容性问题

在Flutter官方只提供了4中CPU架构的so库,armeabi-v7a、arm64-v8a、x86和x86-64,其中x86系列只支持Debug模式,没有提供armeabi架构的库,而网易新闻上只保留armeabi的so文件。虽然我们可以通过修改engine等配置文件来编译出armeabi架构的so, 但是实际上现在市面上绝大部分手机设备都已经支持armeabi-v7a,其提供的硬件加速浮点运算指令可以大大提高Flutter的运行速度,且修改engine需要额外的版本维护,所以我们前期灰度期间在Flutter分支上直接屏蔽掉不支持armeabi-v7a的设备,直接使用armeabi-v7a的so文件。在编译阶段通过gradle构建将armeabi-v7a下的libflutter.so拷贝到armeabi目录下。

包体大小问题

引入Flutter后,通过上述flutter构建的图可以看出,最终产物so,icu, vm/isolate_snapshot_data/instr大小都是很可观的,对于Android来说,包体增大6M左右,随着业务代码的增多,包体将会更大,起初,我们采用动态下发这些文件以解决包体大小的问题,并验证了其可行性,但是这样处理有个问题,就是如果这些文件下载失败,该如何处理?此外官方团队也在重点优化包体大小,因此暂时放弃动态下发这一方案。

资源共享问题

在flutter中引入图片资源,需要在lib下建立image包,并需要在pubspec.yaml文件下的assets下声明; 对于混合开发来说,如果两端都需要的图片资源,就要存在两套,这样一来增加了包体大小,并且需要维护两套资源,最初为了实现两端共享本地资源的问题,解决方案是在dart端通过BasicMessageChannel根据图片名称获取native端存在的图片资源,但与此同时产生的另外一个问题就是同一份资源在Android和iOS原生端的资源命名不一致,为此开发成本较大,带来收益较小,暂放弃本地资源共享这一方案。

稳定性保证

对于稳定性保证采用如下方案:

  • 选取个别渠道上进行灰度测试;

  • 动态接口开关控制用户逐渐放量测试;

  • 通过FlutterError.onError和runZoned捕获异常,异常错误信息用channel机制通过native上报到崩溃收集系统(fabric-Crashlytics)。

上线情况:网易新闻Flutter用户量由4%逐渐放量到目前10%, 在几个版本的迭代过程中,异常收集达到万分之一,已达到生产标准。 

当然我们还在继续,我们iOS也相继接入,实现方案也在不断调整和优化,官方的Flutter团队也在努力不断优化和完善,希望整个Flutter生态越来越好,由于个人水平有限,有不对之处望指正,最后,欢迎加入和关注我们

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存