共计 5926 个字符,预计需要花费 15 分钟才能阅读完成。
Flutter 作为 Google 开源的新一代跨平台、高性能 UI 框架,旨在帮忙开发者高效地构建出跨平台的、UI 与交互体验统一的精美利用,推出后始终倍受开发者的青眼。
当须要开发一个全新的利用时,咱们能够很不便地从零开始,齐全应用 Flutter 进行开发。但如果是针对一个现有的利用,须要引入 Flutter 技术,显然应用 Flutter 全副重写一遍是不事实的。侥幸的是,Flutter 很好地反对了以独立页面、甚至是 UI 片段的形式集成到现有的利用中,即所谓的混合开发模式。本文次要从一个 Android 开发的视角,谈谈 Android 平台下,Flutter 的混合开发与构建。
1,Hello Flutter
对于这门技术,应用过的应该绝大多数都会说好;没用过的举荐尝试一下,跑个 Demo 体验体验,有可能它就是你须要学习和把握的最初一门新技术了。回过头来,Flutter 到底有什么独特的魅力让它能从一众技术中怀才不遇呢?总结一下,次要有以下几点:
- 跨平台:能够做到一套代码完满适配 Android、iOS 平台,将来还会笼罩更多平台,大大节俭了开发人力与保护老本,同时领有杰出的跨端 UI 体现一致性。
- 高效开发:SDK 提供了丰盛的 UI 组件,开箱即用;申明式的 UI 构建形式,大大减少出错率;Debug 模式提供热重载能力,可实时预览代码变更,不须要从新编译装置。
- 高性能:采纳自建渲染引擎,独立于零碎并可独自优化;区别于 RN、WEEX,没有中间层转换的额定开销;Release 模式下代码编译为 AOT 指令,运行高效。
受害于以上的外围劣势,Flutter 推出后圈了很多挪动开发者的粉,各互联网大厂也纷纷将其作为一项根底技术进行钻研。在 Flutter 初期,其利用场景次要是从 0 构建一个全新 App,对混合开发的反对很不敌对。但作为一门跨平台的技术框架,到底还是须要依赖原生平台提供的诸多零碎能力,此外还有泛滥现存原生 App 蠢蠢欲动,因而在这个需要背景下,混合开发的反对与欠缺至今已倒退得越来越好,上面咱们就用一个简略的示例开始 Android 端的 Flutter 混合开发与构建之旅。
2,引入 Flutter 模块
要在一个已有的 Android Project 中应用 Flutter,须要引入一个 Flutter Module。在 Android Studio(须要确保 Flutter 插件曾经胜利装置并启用)中关上现有 Android 工程,通过应用 File > New > New Module… 菜单,咱们能够新创建一个 Flutter 模块或是导入一个内部的 Flutter 模块。
这里以最简略的 Android App 我的项目为例,导入 Flutter 模块。在 Flutter 模块导入胜利之后,原工程文件、构造都会产生一些变动,次要有:
- settings.gradle 文件新增了以下内容。其实就是执行对应 Flutter 模块下 .android/include_flutter.groovy 脚本文件,该步骤会引入一个名为 Flutter 的 Android Library Module,同时还会引入 Flutter 模块所依赖的所有插件。
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_module/.android/include_flutter.groovy'
))
include ':flutter_module'
project(':flutter_module').projectDir = new File('../flutter_module')
在引入 Flutter 模块之前,我的项目中仅有 app 一个 Module;而在引入之后,能够看到除了原有的 app Module 外,Flutter Gradle 插件主动引入了额定几个子 Module。
阐明如下:
- flutter_module:指代要引入的指标 Flutter Module,不会 apply Android 相干的任何插件,次要是蕴含 Flutter 相干源码、资源、依赖等。
- flutter:为 Flutter Gradle 插件引入的 Android Library Module;次要负责编译 flutter_module 及其依赖的第三方 Package、Plugin 的 Dart 代码,以及打包 Flutter 资源等。
- device_info:为 Flutter Gradle 插件主动引入的 Flutter Android Plugin Library Module,这是因为一开始我在 flutter_module 的 pubspec.yaml 文件中增加了对 device_info 这个插件的依赖。Flutter Gradle 工具会将 flutter_module 依赖到的所有插件其 Android 平台侧的代码、资源作为一个 Library Module 引入到我的项目中一起参加构建。如果要查看 flutter_module 引入了哪些 Plugin,能够查看其对应目录下的 .flutter-plugins 与 .flutter-plugins-dependencies 文件,这两个文件是执行 flutter pub get 时生成的,记录了插件的本地文件目录、依赖信息等。
3,应用 Flutter
3.1 增加依赖
首先,须要在 App 模块的 build.gradle 脚本文件中增加对 Flutter 工程的依赖,只有这样 Flutter 模块才会参加到整个利用的构建中来,咱们也才可能在 App 模块中调用到 Flutter 提供的 Java 层 API。
dependencies {implementation project(':flutter')
}
3.2 运行 Flutter 页面
3.2.1 增加 Flutter 页面
咱们能够抉择应用 Activity、Fragment 或者 View 来承载 Flutter 的 UI,这里次要介绍后面两种形式,并假如 flutter_module 中曾经通过 runApp 办法渲染了一个 widget。
Flutter Activity
首先,咱们介绍下应用 Flutter Activity 的形式。应用 io.flutter.embedding.android.FlutterActivity 类能够很不便的启动一个 Flutter Activity,当然咱们也能够继承它并扩大本人的逻辑。
FlutterActivity
.withNewEngine()
.build(context)
.also {startActivity(it)
}
Flutter Fragment
另外一种就是 Flutter Fragment 形式。能够应用 FlutterFragmentActivity 或者 FlutterFragment 来增加 Flutter UI 片段:a. 应用 FlutterFragmentActivity 能够主动创立并增加一个 FlutterFragment;b. 手动创立 FlutterFragment 后增加到指标 Activity 中。
val flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint(getDartEntrypointFunctionName())
.initialRoute(getInitialRoute())
.appBundlePath(getAppBundlePath())
.flutterShellArgs(FlutterShellArgs.fromIntent(intent))
.handleDeeplinking(shouldHandleDeeplinking())
.renderMode(renderMode)
.transparencyMode(transparencyMode)
.shouldAttachEngineToActivity(shouldAttachEngineToActivity())
.build<FlutterFragment>()
fragmentManager
.beginTransaction()
.add(
FRAGMENT_CONTAINER_ID,
flutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
3.2.2 平台层和 Flutter 层通信
不论是开发 Plugin 还是业务逻辑,平台层与 Flutter 层通信是必不可少的,为此就须要应用到 MethodChannel。平台层通过 MethodChannel 申请调用 Flutter 层 API 时,数据在通过打包编码后,通过 JNI、DartVM 传到 Flutter 层解码后应用;待后果计算实现后,又会从新打包编码,通过 DartVM、JNI 传回到 Native 层;同理,在 Flutter 层申请调用平台层的 API 时,数据处理是统一的,只是流转方向相同。通过这种形式,平台层与 Flutter 层就建设了一个双向的、异步的通信通道。
在上面的示例代码中,Native 层应用 dev.flutter.example/counter 创立一个 MethodChannel,并设置 Handler 接管 Dart 的近程办法调用 incrementCounter,并调用 reportCounter 将后果回传,如下所示。
channel = MethodChannel(flutterEngine.dartExecutor, "dev.flutter.example/counter")
channel.setMethodCallHandler { call, _ ->
when (call.method) {
"incrementCounter" -> {
count++
channel.invokeMethod("reportCounter", count)
}
}
}
Dart 层应用雷同的名称创立 MethodChannel,并设置 Handler 解决回调后果,随后调用 incrementCounter 办法申请 counter。
final _channel = MethodChannel('dev.flutter.example/counter');
_channel.setMethodCallHandler(_handleMessage);
_channel.invokeMethod('incrementCounter');
Future<dynamic> _handleMessage(MethodCall call) async {if (call.method == 'reportCounter') {
_count = call.arguments as int;
notifyListeners();}
}
在下面的示例中,咱们是通过手动创立 MethodChannel 进行通信的,这在进行简略通信的场景是没问题的,但在通信接口 API 比较复杂的状况就不是很实用了。一是繁琐,因为咱们须要手写大量的打包、拆包代码;二是容易出错。
这个时候就轮到 Pigeon 大显神通了。Pigeon 是一个官网推出的代码生成工具,能够生成类型平安的双向通信 API 接口,具体能够参考官网的 例子,Pigeon 官网链接。
4,Flutter APK 解析
咱们曾经理解了如何在现有 Android 我的项目中引入并应用 Flutter,接下来咱们再来探索一下 Flutter APK 的构造,看看 Flutter Tools 在这个 APK 包内到底打包了哪些货色。上面两图别离为 Debub 模式和 Release 模式下构建进去的 Flutter APK 包构造,疏忽了非 Flutter 相干的项。
能够看到两个模式下的 APK 构造大致相同,区别如下:
- lib/{arch}/libflutter.so:为对应架构的 Flutter Engine 共享库,负责 Flutter 渲染、JNI 通信、DartVM。如果不须要对应架构的版本,通过 abiFilters 能够 Exclude 掉。
- lib/{arch}/libapp.so:只存在于 Release 模式下,共享库中蕴含 Dart AOT 生成的二进制指令和数据。在运行时,Flutter Engine 通过 Dynamic Load 的形式,从共享库中读取对应的可执行机器指令以及数据。
- assets/flutter_assets:Flutter 援用到的相干资源:
- fonts:蕴含字体库。
- FontManifest.json:援用到的字体库清单文件,json 格局,所有应用到的字体、以及字体文件在 flutter_assets 下的门路。
- AssetManifest.json:其余资源清单文件,json 格局,为所有资源名称到资源门路的映射,Flutter 在加载某一项资源时,会通过这个配置清单找到对应门路的资源进行读取后加载。
- kernel_blob.bin、isolate_snapshot_data、vm_snapshot_data:只存在于 Debug 模式下,别离为 DartVM 字节码与数据,其作用相似于 libapp.so,只是存在模式、打包形式不同。在 Debug 模式下,Flutter Tools 将指令和数据别离打包,次要是为了热重载 (HotReload) 服务的,而在 Release 模式下是对立打包成共享库。
5,踩过的坑
Flutter 混合开发使得开发者能够渐进式地进行 Flutter 开发与迁徙,是 Flutter 寄生于原生平台至关重要的一环,不过在接入 Flutter 的过程中,也呈现了一些问题:
- 路由治理简单:这外面包含 Flutter 层外部的页面路由治理以及 Flutter 与原生的混合栈治理。前者在 Navigator 2.0 API 中曾经失去了很好的欠缺与反对,但后者仍面临着诸多限度与有余,须要改良。目前我的项目中还未波及到后者这种很简单的业务场景,因而对这一块的钻研比拟少,感兴趣的同学能够理解一下诸如 flutter_boost 此类的开源解决方案。
- 生命周期不对应:Android 的组件个别都会有本人的生命周期,Flutter 的 Widget State 也有一套本人的生命周期,但这两者其实并不是一一对应的。比方原生的 Activity 页面尽管曾经被 Finish 并 Destroy 掉了,但 Flutter 层的页面并不一定会随之而被 Dispose,尤其是在应用 Cache Flutter Engine 的时候。Flutter 页面是能够脱离原生页面而存在的,它们能够被动静地 Attach 和 Detach,Attach 时会触发从新渲染,Detach 时 UI 相干的所有操作都会 Pending 直到从新被 Attach。所以在混合开发中,业务逻辑不应该适度依赖 Widget State 的一些生命周期办法,因为它们可能会被延后执行从而导致一些奇怪的 Bug。