使用 Flutter 从头开始写一个 App是一件轻松惬意的事情。可是对于成熟产品来讲,彻底摒弃原有 App 的历史沉淀,全面转向 Flutter 并不现实。用 Flutter 去统一 iOS/Android 技术栈,把它做为已有原生 App 的扩展,而后经过逐步试验有序推动从而提高终端开发效率,可能才是现阶段 Flutter 最有效的集成方式。java
那么,Flutter 工程与原生工程该如何组织管理?不一样平台的 Flutter 工程打包构建产物该如何抽取封装?封装后的产物该如何引入原生工程?原生工程又该如何使用封装后的 Flutter 能力?android
这些问题使得在已有原生 App 中接入 Flutter 看似并非一件容易的事情。那接下来,我就和你介绍下如何在原生 App 中以最天然的方式接入 Flutter。ios
既然要在原生应用中混编 Flutter,相信你必定已经准备好了原生应用工程。若是你尚未准备好也不要紧,我会以一个最小化的示例和你演示这个改造过程。git
首先,咱们分别用 Xcode 与 Android Studio 快速创建一个只有首页的基本工程,工程名分别为 iOSDemo 与 AndroidDemo。github
到此,Android 工程就已经准备好了;而对于 iOS 工程来讲,因为基本工程并不支持以组件化的方式管理项目,所以咱们还须要多作一步,将其改形成使用 CocoaPods 管理的工程,也就是要在 iOSDemo 根目录下建立一个只有基本信息的 Podfile 文件。Podfile文件的配置以下:bash
use_frameworks!
platform :ios, '8.0'
target 'iOSDemo' do
#todo
end
复制代码
而后,在命令行输入 pod install 命令后,会自动生成一个 iOSDemo.xcworkspace 文件,该文件存放的就是咱们项目须要的依赖库,这时咱们就完成了 iOS 工程改造。app
若是你想要在已有的原生 App 里嵌入一些 Flutter 页面,有两个办法能够实现,即统一管理模式和三端分离模式。框架
因为 Flutter 早期提供的混编方式能力及相关资料有限,国内较早使用 Flutter 混合开发的团队大多使用的是统一管理模式。可是,随着功能迭代的深刻,这种方案的弊端也随之显露,不只三端(Android、iOS、Flutter)代码耦合严重,相关工具链耗时也随之大幅增加,致使开发效率下降。模块化
因此,后续使用 Flutter 混合开发的团队陆续按照三端代码分离的模式来进行依赖治理,实现了 Flutter 工程的轻量级接入。工具
除此以外,三端代码分离模式还能够把 Flutter 模块做为原生工程的子模块,从而快速实现 Flutter 功能的“热插拔”,下降原生工程改造的成本。而 Flutter 工程经过 Android Studio 进行管理,无需打开原生工程,可直接进行 Dart 代码和原生代码的开发调试。
三端工程分离模式的关键是抽离 Flutter 工程,将不一样平台的构建产物依照标准组件化的形式进行管理,即 Android 使用 aar、iOS 使用 pod。换句话说,接下来介绍的混编方案会将 Flutter 模块打包成 aar 和 pod,这样原生工程就能够像引用其余第三方原生组件库那样快速接入 Flutter 了。
当咱们建立一个新的Flutter 工程时,除了一些通用配置外,Flutter还包括 Flutter 工程和原生工程的目录(即 iOS 和 Android 两个目录)。在这种状况下,原生工程就会依赖于 Flutter 相关的库和资源,从而没法脱离父目录进行独立构建和运行。
原生工程对 Flutter 的依赖主要分为两部分:
在已经有原生工程的状况下,咱们须要在同级目录建立 Flutter 模块,构建 iOS 和 Android 各自的 Flutter 依赖库。这也很好实现,Flutter 就为咱们提供了这样的命令。咱们只须要在原生项目的同级目录下,执行 Flutter 命令建立名为 flutter_library 的模块便可,命令以下。
Flutter create -t module flutter_library
复制代码
这里的 Flutter 模块,也是 Flutter 工程,咱们用 Android Studio 打开它,其目录以下图所示。
仔细查看能够发现,Flutter 模块有一个细微的变化:Android 工程下多了一个 Flutter 目录,这个目录下的 build.gradle 配置就是咱们构建 aar 的打包配置。这就是模块工程既能像 Flutter 传统工程同样使用 Android Studio 开发调试,又能打包构建 aar 与 pod 的秘密。
实际上,iOS 工程的目录结构也有细微变化,但这个差别并不影响打包构建,所以此处就再也不展开了。
而后,咱们打开 main.dart 文件,将其逻辑更新为如下代码逻辑,即一个写着“Hello from Flutter”的全屏红色的 Flutter Widget,以下所示。
import 'package:flutter/material.dart';
import 'dart:ui';
void main() => runApp(_widgetForRoute(window.defaultRouteName));//独立运行传入默认路由
Widget _widgetForRoute(String route) {
switch (route) {
default:
return MaterialApp(
home: Scaffold(
backgroundColor: const Color(0xFFD63031),//ARGB红色
body: Center(
child: Text(
'Hello from Flutter', //显示的文字
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 20.0,
color: Colors.blue,
),
),
),
),
);
}
}
复制代码
咱们建立的 Widget 其实是包在一个 switch-case 语句中的。这是由于封装的 Flutter 模块通常会有多个页面级 Widget,原生 App 代码则会经过传入路由标识字符串,告诉 Flutter 究竟应该返回何种 Widget。为了简化案例,在这里咱们忽略标识字符串,统一返回一个 MaterialApp。
接下来,咱们要作的事情就是把这段代码编译打包,构建出对应的 Android 和 iOS 依赖库,实现原生工程的接入。
如今,咱们首先来看看 Android 工程如何接入。
以前咱们提到原生工程对 Flutter 的依赖主要分为两部分,对应到 Android 平台,这两部分分别是:
搞清楚 Flutter 工程的 Android 编译产物以后,咱们须要对 Android 的 Flutter 依赖进行抽取,步骤以下。
首先,在 Flutter_library 的根目录下,执行 aar 打包构建命令,以下所示。
Flutter build apk --debug
复制代码
这条命令的做用是编译工程产物,并将 Flutter.jar 和工程产物编译结果封装成一个 aar,以下图所示。
打包构建的 flutter-debug.aar 位于.android/Flutter/build/outputs/aar/ 目录下,咱们把它拷贝到原生 Android 工程 AndroidDemo 的 app/libs 目录下,并在 App 的打包配置 build.gradle 中添加对它的依赖。
...
repositories {
flatDir {
dirs 'libs' // aar目录
}
}
android {
...
compileOptions {
sourceCompatibility 1.8 //Java 1.8
targetCompatibility 1.8 //Java 1.8
}
...
}
dependencies {
...
implementation(name: 'flutter-debug', ext: 'aar')//Flutter模块aar
...
}
复制代码
Sync 一下项目,Flutter 模块就被添加到了 Android 项目中。
而后,咱们试着改一下 MainActivity.java 的代码,把它的 contentView 改为 Flutter 的 widget,以下所示。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View FlutterView = Flutter.createView(this, getLifecycle(), "defaultRoute"); //传入路由标识符
setContentView(FlutterView);//用FlutterView替代Activity的ContentView
}
复制代码
从新运行Android原生工程,效果以下图。
iOS 工程接入的状况要稍微复杂一些。在 iOS 平台,原生工程对 Flutter 的依赖分别是:
iOS 平台的 Flutter 模块抽取,实际上就是经过打包命令生成这两个产物,并将它们封装成一个 pod 供iOS原生工程引用。
相似地,首先咱们在 Flutter_library 的根目录下,执行 iOS 打包构建命令。
Flutter build ios --debug
复制代码
这条命令的做用是编译 Flutter 工程生成两个产物:Flutter.framework 和 App.framework。一样,把 debug 换成 release 就能够构建 release 产物(固然,你还须要处理一下签名问题)。
而后,在 iOSDemo 的根目录下建立一个名为 FlutterEngine 的目录,并把这两个 framework 文件拷贝进去。iOS 的模块化产物工做要比 Android 多一个步骤,由于咱们须要把这两个产物手动封装成 pod。所以,咱们还须要在该目录下建立 FlutterEngine.podspec,即 Flutter 模块的组件定义。
Pod::Spec.new do |s|
s.name = 'FlutterEngine'
s.version = '0.1.0'
s.summary = 'XXXXXXX'
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/xx/FlutterEngine'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'chenhang' => 'hangisnice@gmail.com' }
s.source = { :git => "", :tag => "#{s.version}" }
s.ios.deployment_target = '8.0'
s.ios.vendored_frameworks = 'App.framework', 'Flutter.framework'
end
复制代码
而后,执行pod lib lint 命令,Flutter 模块组件就已经作好了。接下来,咱们再修改 Podfile 文件把它集成到 iOSDemo 工程中,添加以下脚本。
...
target 'iOSDemo' do
pod 'FlutterEngine', :path => './'
end
复制代码
而后,执行pod install 命令,Flutter 模块就集成进 iOS 原生工程中了。再次,咱们试着修改一下 AppDelegate.m 的代码,把 window 的 rootViewController 改为 FlutterViewController,以下所示。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
FlutterViewController *vc = [[FlutterViewController alloc]init];
[vc setInitialRoute:@"defaultRoute"]; //路由标识符
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
复制代码
最后运行原生项目,一个写着“Hello from Flutter”的全屏红色的 Flutter Widget 也展现出来了,以下图所示。
在原生工程中集成Flutter是现阶段最多见的方式。经过分离 Android、iOS 和 Flutter 三端工程,抽离 Flutter 库和引擎及工程代码为组件库,以 Android 和 iOS 平台最多见的 aar 和 pod 形式接入原生工程,从而将不一样平台的构建产物依照标准组件化的形式进行管理。
若是每次经过构建 Flutter 模块工程,都是手动搬运 Flutter 编译产物,那很容易就会由于工程管理混乱致使 Flutter 组件库被覆盖,从而引起难以排查的 Bug。而要解决此类问题的话,咱们能够引入 CI 自动构建框架,把 Flutter 编译产物构建自动化,原生工程经过接入不一样版本的构建产物,实现更优雅的三端分离模式。