F366672f3b0ba67450bac5f7a5a0160e
工程实践 - Flutter 从入门到落地

背景

因为 Flutter 一直在快速迭代,所以我们有必要提一下撰写此文时 Flutter 开发环境信息,如下:

Flutter 1.0.1-pre.1 • channel master • https://github.com/flutter/flutter.git
Framework • revision a226c0f0d9 (4 months ago) • 2018-11-30 16:18:18 -0800
Engine • revision 7375a0f414
Tools • Dart 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)

前言

2018 年国内移动端什么最火?当然是 Flutter!在阿里闲鱼团队的领跑下,各大互联网公司的移动团队都纷纷跟进,在项目中引入 Flutter 进行实践。

至于有多火,我们可以从如下几个维度分析:

注:以下这套热度分析方法论也适合对别的项目进行评估。

1. 社区输出

下面是掘金 Flutter 和 React Native 频道的关注数和文章数:

从 2018 年初才逐渐为国内开发者所知的 Flutter 能获得这样数据,从一定程度上可以看出技术社区目前对 Flutter 的认可。

2. 开源关注度

一个开源项目的 Issue 数一直是我们考察其质量的一个最重要的维度,所以我们来看一下 Flutter 和 React Native 两个项目的 Issue 数量和内容:

虽然说 Issue 的数量多也有可能是因为新项目问题多,但是从关注度这个事情来说,Flutter 绝对是完胜了。

3. 大公司认可度

据笔者在社区的了解。目前腾讯、头条、阿里、美团点评等大公司均已有团队在深度接触 Flutter,有些甚至已经在对 Flutter 的 engine 进行深度订制以适合自身业务发展需要。

那 2019 年 Flutter 是不是会继续延续这样的势头呢?答案就在这篇文章里面:Flutter 2019 年的产品路线

  • Ease of Adoption 这个维度主要是为了降低现有项目接入 Flutter 的成本,尤其是 Flutter 分别为 iOS 和 Android 提供了支持 cocoapods 和 Gradle 的 module 引入方案,这将极大的鼓励开发人员去在现有项目尝试 Flutter;
  • Ecosystem 这个维度主要是利用 Flutter 自身的优势,提升其在同类跨平台框架的竞争力;
  • Support for platforms beyond mobile 这个维度就更重要了,因为和 Weex 以及 RN 对比,Flutter 一直被诟病的就是它不支持 Web,现在好了,Flutter 不单单计划支持 Web,连桌面都要搞!
  • Dynamic updates 这个维度主要是针对 Android,优势在哪就不用说了,亲儿子了解下!至于 iOS 为什么不搞,iOS 开发者都心知肚明。

综合上面几个维度来看,毋庸置疑,Flutter 仍然会是 2019 移动开发童鞋最值得学习的框架。

初识

笔者是 2018 年初开始了解到 Flutter 的,当时我们团队正在寻找一种替代 RN 的跨平台方案,当我们了解到 Flutter 采用的是 Google 的 Skia 引擎自建 UI 的时候,我们的内心是这样的:

为什么会有这样的内心活动,这就需要把 Flutter 和 RN 框架做个对比了:

从上图可以看出,RN 其实是通过 Bridge,把 js 代码传递到 native,最终是还转换成原生的 View 进行绘制。这就会导致我们经常会出现同一套 UI 在 iOS 和 Android 平台上存在各种不同的兼容性问题。

而 Flutter 就完全不一样,它采用的是 Google 的 Skia 引擎,大家可以把它理解成一套全新的跨平台 UI 框架,其实它的实现更像是 cocos2d-x,不过 cocos2d-x 更多在游戏领域应用而已。

初探

事不宜迟,我们很快根据团队情况制定了适合我们的 Flutter 探索方案。

首先介绍下我们团队的情况:

  1. 业务驱动,节奏快;
  2. 工具类 App,重核心功能;
  3. 不需要很强的运营能力;
  4. iOS 和 Android 同属一个团队,开发资源可以非常灵活地调度;
  5. 我们的项目已经基本实现了模块化,各个业务模块相对独立,使用统一的 Router 进行模块间通信和页面跳转。

根据这个情况,我们得出了几个结论:

  1. 我们不可能有精力大面积把业务使用 Flutter 重写;
  2. App 的核心功能还是需要用原生实现,其他非主流程功能可以用 Flutter 重写;
  3. 我们对动态性要求不高,所以 Flutter 目前的短板并不会限制我们的业务需求;
  4. 灵活的开发资源调度可以为我们接入 Flutter 提供非常有效的保障;
  5. 我们可以基于现有的框架实现无痕接入 Flutter。

而我们最初对接入 Flutter 所产生的收益预期其实只有一个:利用Flutter 的跨平台特性,提高非关键流程的开发效率!

不过最开始的时候,我们也是存在一些担忧的:

  1. Flutter 是否可以实现一端开发,多端低成本接入?而不是像 RN 那样,在处理 iOS 和 Android 两端差异的时候,需要投入比较大的精力;
  2. Flutter 对混编支持如何?是否可以低成本接入;
  3. Flutter 在资源(内存、磁盘)占用上是否在可接受范围。

带着这些担忧,我们上路了!上路之前,我们制定了下面的行进计划:

  • 第一阶段主要做的是准备工作。我们需要结合现有工程的架构,设计一套低成本接入 Flutter 的架构以及可持续集成(CI)方案;
  • 第二阶段是试验阶段,这个阶段一方面我们需要为 Flutter 接入原生 提供一些基础能力。包括基础 Util、Router、网络、缓存、存储等;另一方面我们需要解决原生和 Flutter 双向通讯的问题。有了这些基础能力之后,我们就可以进行一些试验性的开发了,而我们选择重构现有页面的主要原因有两个方面:一方面是为了灰度发布,另外一方面也是为了降级。
  • 第三阶段基本就可以进行大量的生产实践了,这个阶段我们可以根据业务的需要,迅速丰富 Flutter 端的基础控件,为后续持续迭代打下坚实基础。

筑路

开发环境搭建

通过观察 Flutter 官方的环境搭建教程,我们发现这些流程都是可以自动化的,所以我们在 Flutter 源项目中增加了如下的 Init 脚本,方便后面的同学更简单的搭建 Flutter 开发环境:

#!/bin/bash

# 跳转到用户根目录
echo "---< 跳转到用户根目录 >---"
cd ~
mkdir .flutter_source
cd .flutter_source
git clone -b beta https://github.com/flutter/flutter.git

# 设置环境变量
echo "---< 设置环境变量 >---"
echo 'export PUB_HOSTED_URL=https://pub.flutter-io.cn'>>~/.bash_rc
echo 'export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn'>>~/.bash_rc
echo "export PATH=`pwd`/flutter/bin:$PATH">>~/.bash_rc
source ~/.bash_rc


# 切换 flutter channel
echo "---< 切换 flutter channel >---"
flutter channel master

flutter channel

# 检查 flutter 环境
echo "---< 检查 flutter 环境 >---"
flutter doctor

最佳实践

基于官方的模块化接入方案

因为我们之前已经得出结论:我们不可能有精力大面积把业务使用 Flutter 重写(PS:当然我们也相信这是大部分团队面临的情况,这也是这篇文章的意义),所以我们要调研的内容自然是:如何把 Flutter 接入到现有项目。

这里先推荐一个 Flutter 自带接入教程:Add Flutter to existing apps

通过这个教程可以通过模块化的方式将 Flutter 的工程集成到现有的 iOS 和 Android 项目中,其中 iOS 目前支持的集成方式是 cocoapods,其依赖设置的核心实现在 .ios/Flutter/podhelper.rb,Android 是 Gradle,其 Plugin 的依赖配置在 .android/include_flutter.groovy,工程依赖和打包管理在 $flutterRoot/packages/flutter_tools/gradle/flutter.gradle

不过我们发现 Android 端的接入过程中,其实存在一些瑕疵,所以我们对其进行了如下的优化:

  • Flutter 官方只提供了四种 CPU 架构的 SO 库:armeabi-v7a、arm64-v8a、x86 和 x86-64, 但是目前我们使用的 SDK 大部分只使用了 armeabi 架构,这样就需要构建一个 armeabi 架构的 jar 包,可以找到 armeabi-v7a 下的 flutter.jar 使用以下脚本实现:

    cp flutter.jar flutter-armeabi-v7a.jar
    unzip flutter.jar lib/armeabi-v7a/libflutter.so
    mv lib/armeabi-v7a lib/armeabi
    zip -d flutter.jar lib/armeabi-v7a/libflutter.so
    zip flutter.jar lib/armeabi/libflutter.so

    然后修改 flutter.gradle 中 jar 依赖的路径为我们自己构建的 flutter.jar 地址。

  • 在打包成 aar 的过程中丢失了 icudtl.dat 文件,导致接入工程中时发生运行时找不到文件而崩溃。测试下来发现主要是 3.0 以上版本的 Android Gralde Plugin 会出现这种丢失,可以手动把 flutter assets 下的文件拷贝到工程目录下,也可以降低 Gradle 的版本到 2.3 版本。

  • 如果 Android Gralde Plugin 使用 2.3 的版本,需要修改一下 flutter.gradle 中 buildType 的获取方式,直接判断是否是 debug 的方式在 lib 中不生效,默认都是 false,可以修改为读取 local.properties 中的一个参数。

低成本混合架构方案

完成模块化接入之后,我们就需要考虑整体架构设计了,而我们需要做到的是对上层业务透明。那什么是透明呢?通俗来说就是用 Flutter 实现的某个业务对接入方完全透明,接入方并不知道这个业务是用 Flutter 实现还是原生实现的

之前有提到,我们的项目已经基本实现了模块化,所以我们基于现有的框架,我们做了如下的整体架构设计:

我们在 Home Project 和 Flutter Project 之间设计了一层胶水层,这层胶水层的承担的责任主要分为如下几个方面:

  • 实现 Flutter 和 Native 之间交互的逻辑,主要通过 Channel 实现;
top Created with Sketch.