1. 首页
  2. 科技部落

信也科技的Flutter实践之路

一、Flutter介绍

Flutter是Google开源的移动开发框架,主打高性能、跨平台。从2019年下半年开始,集团部分业务线开始尝试使用Flutter,我们主要关注Flutter的高性能、跨平台和热更新特性。

1、高性能

高性能主要是与Hybrid、RN/Weex等动态化方案做比较。首先Flutter使用Skia作为渲染引擎,所以在UI布局时不需要像RN/Weex那样在JavaScript和Native之间通信,其次Flutter 采用Dart语言开发,在发布时使用AOT模式直接编译成机器码,执行效率高于JavaScript的解释执行。

2、跨平台

Flutter自带Skia渲染引擎和Dart运行时,极大减少了对运行平台的依赖性。真正实现一处编写多处运行,目前已经支持Android、iOS 、Fuchsia、macOS、Web(目前beta版)、Windows和Linux(官方还在开发中)。

3、热更新

虽然目前官方表示暂时不支持热更新:

This is currently not on Flutter’s roadmap, for reasons discussed in this comment:#14330 (comment)

但:

  • 在Android平台,Flutter曾在1.2.1版本加入了Dynamic Patching特性来支持热更新,从2019年开始该特性不再支持。我们可以参考相关源码自行实现。另外Flutter在Android平台将业务逻辑打包成libapp.so,我们可以借助so的热更新方案实现Flutter的热更新。
  • 在iOS平台,国内大厂都在积极探索Flutter的热更新方案,例如MXFlutter(QQ团队)、Kraken(淘宝)等,下文也会介绍我们在iOS平台热更新的尝试。

二、Flutter工程化实践

(一)开发模式

1、混合开发

由于我们的App已有线上运行的Native版本,所以我们采用Native和Flutter混合开发模式,并优先使用在新业务模块和频繁更新的业务模块上。工程结构如下:

  • 创建独立的flutter module工程,H5团队可以直接在flutter module工程上进行开发,不用关注Native工程的环境配置(method channel需要mock调试)。并且该工程可以很方便的打包各平台产物。
  • Native团队在开发阶段使用Native宿主工程引用flutter module,方便调试method channel功能,在提测和发布阶段则使用Native宿主工程引用flutter module对应的平台产物。
信也科技的Flutter实践之路

2、模块化开发

我们在启动Flutter项目时就考虑到公共业务模块和基础模块的封装沉淀,这些公共模块可以在不同业务线产品上实现代码复用,模块化开发也使得模块之间解耦,不同模块可以独立迭代发布。

信也科技的Flutter实践之路

(二)开发流程

信也科技的Flutter实践之路

开发流程主要分为三个阶段:开发调试、Flutter发布、Native宿主集成。其中Flutter发布环节包含Flutter Package、Flutter Plugin发布到PUB私服和Flutter各平台打包产物发布到各托管平台。比较重要的环节有:

1、打包产物合并
信也科技的Flutter实践之路

我们以Android平台为例讲述Flutter打包合并产物的过程:

  • 使用flutter build构建aarflutter build aar --no-debug --no-profile --target-platform android-arm
  • 如上图所示构建后的产物会输出在build/host/outputs/repo下,flutter会把工程代码资源和引用到的Plugin(Native代码)分别打包成独立的本地maven库,并且版本号都是固定不变的1.0。宿主如果想集成这些产物有两种思路:
    • 将所有产物maven库修正版本号后分别上传至远程maven仓库
    • 合并所有产物maven库,生成一个对应flutter代码版本的maven库并上传至远程maven仓库。
  • 我们使用了上述的方案二,这样宿主只需要关注一个flutter产物maven库即可,不用关注它引用的plugin库。合并打包工具的gradle脚本部分代码示例:apply plugin:'com.kezong.fat-aar'
    ...
    dependencies {
    implementation fileTree(dir:'libs', include: ['*.jar'])
    //固定引用
    embed 'com.my_flutter_app:flutter_release:1.0'//第一层引用
    embed 'io.flutter.plugins.sharedpreferences:shared_preferences_release:1.0'
    embed 'io.flutter.plugins.webviewflutter:webview_flutter_release:1.0'
    embed 'io.flutter:flutter_embedding_release:1.0.0-6158f03ef5fae3ef620264595021609ed9ea3a5c'
    embed 'io.flutter:armeabi_v7a_release:1.0.0-6158f03ef5fae3ef620264595021609ed9ea3a5c'//第二层引用
    api 'androidx.appcompat:appcompat:1.1.0'
    api 'androidx.annotation:annotation:1.1.0'
    api 'androidx.legacy:legacy-support-v4:1.0.0'
    api 'androidx.webkit:webkit:1.0.0'
    ...
    }
2、PUB私服

目前我们使用 unpub(字节跳动出品)搭建了PUB私服,也可以直接把Git私服作为PUB私服使用。

三、XFlutter开发框架

信也科技的Flutter实践之路

在Flutter实践的过程中我们逐渐积累完善了XFlutter框架,包括基础组件、业务组件和工具链。基础组件为所有业务组件及业务应用提供最基础的能力,公共业务组件实现了集团内不同产品线公共模块的复用,工具链则会贯穿在整个Flutter工程的实施过程中,为Flutter实施保驾护航。

四、常见问题及解决方案

(一)热更新

1、Android平台

在Android平台上我们直接对libapp.so进行热更新,主要有so的版本匹配、so的差分处理、so的下载及验证、so的加载等环节,下面我们只列举了so的加载环节的部分代码,其他环节可以参考Android开发中的Apk增量更新相关技术。

  • 我们通过阅读源码发现so加载的入口源码在FlutterLoader类中:public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
    //...部分代码省略if (args != null) {
    Collections.addAll(shellArgs, args);//我们只需要在args中追加一条--aot-shared-library-name路径即可
    }
    String kernelPath = null;
    shellArgs.add("--aot-shared-library-name=" + this.aotSharedLibraryName);
    shellArgs.add("--aot-shared-library-name=" + applicationInfo.nativeLibraryDir + File.separator + this.aotSharedLibraryName);
    shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
    //...部分代码省略
    FlutterJNI.nativeInit(applicationContext, (String[])shellArgs.toArray(new String[0]), (String)kernelPath, appStoragePath, engineCachesPath);
    //...部分代码省略
    }
  • 设置so路径示例代码:public void setSoPath(String soPath) {
    try {
    if (!TextUtils.isEmpty(soPath) && new File(soPath).exists() && new File(soPath).isFile()) {
    String[] strings = new String[]{"--aot-shared-library-name=" + soPath};
    FlutterMain.ensureInitializationComplete(context, strings);
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
  • 对于Flutter资源的热更新我们更多的是采用加载网络资源+本地缓存来简单实现。
2、iOS平台

在iOS平台上的热更新,我们团队目前使用Flutter Web+H5离线包的方案,主要原因有两个:一是我们的产品有一个简单的H5版本,我们希望能够使用Flutter Web来实现Web端的复用,二是目前市面上的Flutter iOS热更新方案并不是很成熟,大都摈弃了Dart语言,使得我们现有的Flutter工程无法复用。

(二)Flutter Web 兼容

上文提到我们正在尝试使用Flutter Web 来实现Web平台的代码复用及iOS平台热更新的问题,在开发过程中需要注意以下问题:

  • 尽量使用Dart来实现功能而不是使用Flutter Plugin,比如RSA、AES加密算法使用纯Dart代码开发。
  • 所有引用的Flutter Plugin需封装后使用,把对Web平台的适配做一层封装。

(三)崩溃监控

Flutter的崩溃监控主要分为两部分:

  • Flutter Dart代码引起的崩溃,核心代码如下:...
    //1、收集Flutter framework捕获后抛出的Error
    FlutterError.onError = (details) {
    Zone.current.handleUncaughtError(details.exception, details.stack);
    };
    //2、收集Flutter app级别抛出的Error
    runZoned<Future<Null>>(() async {
    //在这里执行main方法中原有的代码逻辑
    ...
    }, onError: (error, stackTrace) {
    //在这里过滤error后进行打印输出或者上传监控平台
    ...
    });
    ...
  • Flutter engine引起的崩溃:目前我们使用bugly来监控Flutter engine的崩溃情况。

五、结束语

Flutter开发相关技术远不止这些,如Flutter的状态管理、混合开发中多容器问题、线上性能监控等,本文仅列举了公司在Flutter工程化相关的实践经验,希望对大家有所帮助。

本文来自拍码场,经授权后发布,本文观点不代表信也智慧金融研究院立场,转载请联系原作者。