Flutter开发之《马蜂窝的跨平台开发实践》笔记(53)

摘自:Flutter 实现原理及在马蜂窝的跨平台开发实践

  1. 跨平台开发,针对 Android 与 iOS 的风格设计了两套设计语言的控件实现(Material & Cupertino)。这样不但能够节约人力成本,而且在用户体验上更好的适配 App 运行的平台。
  2. 同时支持 JIT 和 AOT 编译。JIT 编译方式使其在开发阶段有个备受欢迎的功能——热重载(HotReload),这样在开发时可以省去构建的过程,提高开发效率。而在 Release 运行阶段采用 AOT 的编译方式,使执行效率非常高,让 Release 版本发挥更好的性能。

Flutter 在马蜂窝商家端App 中的应用实践

在这里插入图片描述
在这里插入图片描述

开发方式:Flutter + Native

由于商家端已经是一款成熟的 App,不可能创建一个新的 Flutter 工程全部重新开发,因此我们选择 Native 与 Flutter 混编的方案来实现。
在了解 Native 与 Flutter 混编方案前,首先我们需要了解在 Flutter 工程中,通常有以下 4 种工程类型:

  1. Flutter Application

标准的 Flutter App 工程,包含标准的 Dart 层与 Native 平台层。

  1. Flutter Module

Flutter 组件工程,仅包含 Dart 层实现,Native 平台层子工程为通过 Flutter 自动生成的隐藏工程(.ios / .android)。

  1. Flutter Plugin

Flutter 平台插件工程,包含 Dart 层与 Native 平台层的实现。

  1. Flutter Package

Flutter 纯 Dart 插件工程,仅包含 Dart 层的实现,往往定义一些公共 Widget。

了解了 Flutter 工程类型后,即在现有工程下创建 Flutter Module 工程,以本地依赖的方式集成到现有的 Native 工程中。

官方集成方案(以 iOS 为例)
a. 在工程目录创建 FlutterModule,创建后,工程目录大致如下:
在这里插入图片描述
b. 在 Podfile 文件中添加以下代码:

flutter_application_path = '../flutter_Moudule/'

该脚本主要负责:

  • pod 引入 Flutter.Framework 以及 FlutterPluginRegistrant 注册入口
  • pod 引入 Flutter 第三方 plugin
  • 在每一个 pod 库的配置文件中写入对 Generated.xcconfig 文件的导入
  • 修改 pod 库的 ENABLE_BITCODE = NO(因为 Flutter 现在不支持 bitcode)

c. 在 iOS 构建阶段 Build Phases 中注入构建时需要执行的 xcode_backend.sh (位于 FlutterSDK/packages/flutter_tools/bin) 脚本:

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build

该脚本主要负责:

  • 构建 App.framework 以及 Flutter.framework 产物
  • 根据编译模式(debug/profile/release)导入对应的产物
  • 编译 flutter_asset 资源
  • 把以上产物 copy 到对应的构建产物中

d. 与 Native 通信

  • 方案一:改造 AppDelegate 继承自 FlutterAppDelegate
  • 方案二:AppDelegate 实现 FlutterAppLifeCycleProvider 协议,生命周期由 FlutterPluginAppLifeCycleDelegate 传递给 Flutter

以上就是官方提供的集成方案。我们最终没有选择此方案的原因,是它直接依赖于 FlutterModule 工程以及 Flutter 环境,使 Native 开发同学无法脱离 Flutter 环境开发,影响正常的开发流程,团队合作成本较大;而且会影响正常的打包流程。(目前 Flutter 团队正在重构嵌入 Native 工程的方式)

最终我们选择另一种方案来解决以上的问题:远端依赖产物。
在这里插入图片描述

iOS 集成方案

通过对官方混编方案的研究,我们了解到 iOS 工程最终依赖的其实是 FlutterModule 工程构建出的产物(Framework,Asset,Plugin),只需将产物导出并 push 到远端仓库,iOS 工程通过远端依赖产物即可。
依赖产物目录结构如下:
在这里插入图片描述

  • App.framework : Flutter 工程产物(包含 Flutter 工程的代码,Debug 模式下它是个空壳,代码在 flutter_assets 中)。
  • Flutter.framework: Flutter 引擎库。与编译模式(debug/profile/release)以及 CPU 架构(arm*, i386, x86_64)相匹配。
  • lib.a & .h 头文件: FlutterPlugin 静态库(包含在 iOS 端的实现)。
  • flutter_assets: 包含 Flutter 工程字体,图片等资源。在 Flutter1.2 版本中,被打包到 App.framework 中。

Android 集成方案

Android Nativite 集成是通过 Gradle 远程依赖 Flutter 工程产物的方式完成的,以下是具体的集成流程。

a.创建 Flutter 标准工程

$ flutter create flutter_demo

b. 修改工程的默认配置
在这里插入图片描述

  1. 修改 app module 工程的 build.gradle 配置 apply plugin: ‘com.android.application’ => apply plugin: ‘com.android.library’,并移除 applicationId 配置

  2. 修改 root 工程的 build.gradle 配置
    在集成过程中 Flutter 依赖了三方 Plugins 后,遇到 Plugins 的代码没有被打进 Library 中的问题。通过以下配置解决(这种方式略显粗暴,后续的优化方案正在调研)。

subprojects {
   project.buildDir = "${rootProject.buildDir}/app"
}
  1. app module 增加 maven 打包配置

c. 生成 Android Flutter 产物

$ cd android
$ ./gradlew uploadArchives

官方默认的构建脚本在 Flutter 1.0.0 版本存在 Bug——最终的产物中会缺少 flutter_shared/icudtl.dat 文件,导致 App Crash。目前的解决方式是将这个文件复制到工程的 assets 下( 在 Flutter 最新 1.2.1 版本中这个 Bug 已被修复,但是 1.2.1 版本又出现了一个 UI 渲染的问题,所以只能继续使用 1.0.0 版本)。

d. Android Native 平台工程集成,增加下面依赖配置即可,不会影响 Native 平台开发的同学

implementation 'com.mfw.app:MerchantFlutter:0.0.5-beta'
为了确保用户界面不会挂起,消息和响应是异步传递的,需要用 async 修饰方法,await 修饰调用语句。Flutter 工程和宿主工程通过在 Channel 构造函数中传递 Channel 名称进行关联。单个应用中使用的所有 Channel 名称必须是唯一的; 可以在 Channel 名称前加一个唯一的「域名前缀」。

混合栈管理

  • Flutter 管理页面有两个概念:Route 和 Navigator。

  • Navigator 是一个路由管理的 Widget(Flutter 中万物皆 Widget),它通过一个栈来管理一个路由 Widget 集合。通常当前屏幕显示的页面就是栈顶的路由。

  • 路由 (Route) 在移动开发中通常指页面(Page),这跟 web 开发中单页应用的 Route 概念意义是相同的,Route 在 Android 中通常指一个 Activity,在 iOS 中指一个 ViewController。所谓路由管理,就是管理页面之间如何跳转,通常也可被称为导航管理。这和原生开发类似,无论是 Android 还是 iOS,导航管理都会维护一个路由栈,路由入栈 (push) 操作对应打开一个新页面,路由出栈 (pop) 操作对应页面关闭操作,而路由管理主要是指如何来管理路由栈。

图 14 :Flutter 路由管理
在这里插入图片描述

如果是纯 Flutter 工程,页面栈无需我们进行管理,但是引入到 Native 工程内,就需要考虑如何管理混合栈。并且需要解决以下几个问题:

  1. 保证 Flutter 页面与 Native 页面之间的跳转从用户体验上没有任何差异

  2. 页面资源化(马蜂窝特有的业务逻辑)

  3. 保证生命周期完整性,处理相关打点事件上报

  4. 资源性能问题

参考了业界内的解决方法,以及项目自身的实际场景,我们选择类似于 H5 在 Navite 中嵌入的方式,统一通过 openURL 跳转到一个 Native 页面(FlutterContainerVC),Native 页面通过 addChildViewController 方式添加 FlutterViewController(负责 Flutter 页面渲染),同时通过 channel 同步 Native 页面与 Flutter 页面。

  • 每一次的 push/pop 由 Native 发起,同时通过 channel 保持 Native 与 Flutter 页面同步——在 Native 中跳转 Flutter 页面与跳转原生无差异

  • 一个 Flutter 页面对应一个 Native 页面(FlutterContainerVC)——解决页面资源化

  • FlutterContainerVC 通过 addChildViewController 对单例 FlutterViewController 进行复用——保证生命周期完整性,处理相关打点事件上报

  • 由于每一个 FlutterViewController(提供 Flutter 视图的实现)会启动三个线程,分别是 UI 线程、GPU 线程和 IO 线程,使用单例 FlutterViewController 可以减少对资源的占用——解决资源性能问题

发布了249 篇原创文章 · 获赞 224 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/shifang07/article/details/98634357