Native转型Flutter经验分享

1,597 阅读8分钟

背景

为了让大家能够了解我们在进行Native转型Flutter期间遇到的问题以及为什么要进行这样的决策,首先介绍一下项目背景。

进行Flutter转型的项目是公司一个长期迭代的物联网App,该产品会发布到Android和iOS两个平台上,平均每个月都需要发布一个版本。到目前为止,整个产品已经迭代超过4年,单端代码规模几十万行,涉及15+个不同的软件模块库。

在该产品的整个生命周期中经历了几次技术演进过程,从最初的所有业务代码在一起的混沌开发状态到基于Clean Arch模式完成业务分层隔离,不同业务模块拆分为不同的业务库各自发布版本最终通过一个构建系统完成App打包;从单纯的Native代码完成业务演进为Native与H5混合型App。

团队规模从最初的10人左右发展到现在的50+人,包括Android、iOS、H5前端开发工程师。由于开发技术栈分散导致常常出现以下几个问题:

- Android的已修复的bug在iOS平台再出现
- 同样的业务双平台实现不一致
- 业务代码难以Review

随着业务压力的增加,公司对于团队交付效率的要求也越来越高。 为了解决上面的现实问题,我们选择将业务全面从Native开发转向跨平台开发技术,从众多的跨平台开发技术方案中,我们选择了Flutter。

为什么选择Flutter

为了解决多端业务逻辑一致性、技术开发栈一致性等问题,唯一的选择就是使用跨平台开发技术;目前有很多比较成熟跨平台开发技术,比如React Native、Weex、混合H5开发模式等。由于团队内部H5开发使用的前端技术栈主要是React,因此我们只对比评估了Flutter和RN,个人觉得写的比较细致和全面的具体如下:

除了参考网上的相关技术文章外,我们选择某个比较复杂的业务页面进行了RN和Flutter的实现对比,最终推动我们确定转向Flutter的重要原因就在于图中红色方框里的个性化定制的UI控件部分的差异:

定制化UI控件差异

ReactNative在以下几个方面存在问题:

  • 从维护成本来看
    • 由于UI控件开发方和使用方属于两个不同技术栈,需要维护组件使用文档
    • RN的Native控件需要在两个平台上分别维护
    • 组件开发人员需要确保双平台一致性
  • 从团队配合来看,UI控件开发方和使用方属于两个团队,
    • 增加了跨团队沟通成本
    • 业务开发过程中导致两个团队出现耦合
    • 测试出现问题后,问题定位容易出现扯皮

实现一个业务遇到的问题

当我尝试实现一个具体业务时,作为一个具有Native经验的开发者主要遇到以下几类的问题。我认为这些问题具有共通性,因此将我在解决问题的过程中查找到的比较好的资料、自己的心得分享给大家,希望能给大家带来些微帮助。

基础知识

关于Flutter的环境搭建、基础知识,推荐大家直接到Flutter中文网进行学习,主要需要学习:

  • Dart语言
    对于具备一定Android开发经验的同学来说,Dart语言学习的难度不大;iOS开发的同学,如果学习过swift的话,也能较快的熟悉;
  • Flutter框架
    Flutter采用与前端React类似的响应式编程模型,对于Native开发同学来说,难点在于思考问题方式的转换;具体的差异在下面的响应式编程中说明;

App应用框架

在最初阅读完Flutter中文网的帮助后,我迫不及待想编写一个“hello world”:

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Text("Hello the flutter");
  }
}

实际上,我并没有得到预期中的“Hello World”页面,而是显示了如下的错误页面:

在修改代码后

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home:  new Text("hello the flutter")
    );
  }
}

终于得到了“Hello World”页面:

经过阅读MaterialApp的文档发现原来Flutter将App应用框架也抽象成了一个Widget,用户可以通过Widget属性配置:

  • 页面路由管理器
  • 主题相关数据
  • 国际化相关数据
  • 应用级初始化业务

怪不得在Flutter领域有人感叹:“万物皆是Widget!”。

参考资料:MaterialApp 和 Scaffold

响应式编程

在刚刚使用Flutter的时候,困扰我最大的问题就是:什么是响应式编程?为什么说Flutter的UI框架在诞生之初就是基于响应式设计的?
因此查了很多资料,试图弄清楚 什么是响应式编程?它与函数式编程,命令式编程是什么关系?这些方式与基于UI的MVC、MVP、MVVM模式是什么关系? 如果你也遇到类似的问题,强烈推荐仔细阅读闲鱼技术的Flutter React编程范式实践

分享以下心得:

  • 响应式编程的英文是Reactive,不要因为它的中文名字把它与函数式编程、命令式编程放在一起比较
  • Reactive是一种处理View与Model的方法,与MVC、MVP、MVVM是解决的是一类问题,如下图
  • 在Flutter上进行UI开发时与Android、iOS最大的不同是通过改变数据来改变View,而不是直接设置View;
    Flutter不提倡基本不会提供操作View的API,比如我们常见的类似TextView.setText(),Button.setOnClick();
  • 当多个页面均使用Reactive方式时,如何优雅的管理页面私有数据和应用级数据就成为一个问题,因此状态管理是使用Flutter完成复杂业务必须要认真考虑的问题;
    Native与Flutter混合开发的方式,由于全局性数据均在Native层面维护,应用级数据管理的问题反而不是那么突出;

更强大的Dart语法

Dart作为Google新一代开发语言,相比Java和OC,Dart具有更先进的一些语法特性;如何充分发挥语言特性,写出优雅简洁的代码也是一个挑战。具体的Dart相关的资料推荐

由于有Java开发经验,我在阅读Dart代码时基本上都能看懂,除了以下几点是需要特别注意:

  • 可选命名参数与简化变量赋值
    以下代码是Flutter的官方代码中常见的Widget构造函数,主要使用了Dart的可选命名参数、简化变量赋值特性,极大的减少了机械编码的工作量;

    const Text(
        this.data, {
        Key key,
        this.style,
        this.strutStyle,
        this.textAlign,
        this.textDirection,
        this.locale,
        this.softWrap,
        this.overflow,
        this.textScaleFactor,
        this.maxLines,
        this.semanticsLabel,
        this.textWidthBasis,
      }) 
      
      //具体使用方式
      new Text(“文本内容”,
            textAlign: TextAlign.left,
            style: TextStyle(color: Colors.black, fontSize: 13)),
    
  • ?相关的语法糖, 解决语法空问题
    参考资料:Dart 到底是不是空安全的

    • ‘?.’条件成员访问,可以简化访问某个对象属性或方法时的非空判断
    • ‘??’判空操作符,'??'左边的值不为空则返回左边的值
    • '??='空感知赋值,当被赋值的变量为null时才被赋值
  • '..' 与 '...' 操作符

    • '..'中叫做级联运算符,主要用于简化同一个对象上连续调用多个函数以及访问成员变量的代码表达,如下:
    main() {
      Student()
        ..testMethod()
        ..testMethod1()
        ..string = "猫了个咪"
        ..printString();
    }
    

    等效于:

    main() {
      var student = Student();
      student.testMethod();
      student.testMethod1();
      student.string = "猫了个咪";
      student.printString();
    }
    
  • 异步支持
    Flutter在语法中对于异步函数有非常良好的支持,不像Java还需要引入RxJava等库;
    具体可以参考:Flutter/Dart中的异步

  • 类构造方法

    • 命名构造函数,可以为一个类实现多个不同名字的构造函数以便于更清晰的表明你的意图
    • 工厂构造函数,按照一定逻辑定义类构造器返回的具体实例,可以更方便的实现单例模式

待解决问题

从使用Flutter完成简单业务到上线一个Flutter业务到目前的产品中,还需要解决以下几个方面的问题:

  • 搭建页面路由机制,支持Native页面与Flutter页面的相互路由
  • 指定数据共享策略,确保Native与Flutter的业务数据一致性
  • 确定Native-Flutter多团队协同开发模式,解决代码库管理、调试等问题
  • 完成CI改造,混合工程做到持续构建