Flutter 混合开发
混合开发简介
使用 Flutter 从零开始开发App是一件轻松惬意的事情,但对于一些成熟的产品来说,完全摒弃原有 App 的历史沉淀,全面转向 Flutter 是不现实的。因此使用 Flutter 去统一 Android、iOS 技术栈,把它作为已有原生 App 的扩展能力,通过有序推进来提升移动终端的开发效率。
目前,想要在已有的原生 App 里嵌入一些 Flutter 页面主要有两种方案。一种是将原生工程作为 Flutter 工程的子工程,由 Flutter 进行统一管理,这种模式称为统一管理模式。另一种是将 Flutter 工程作为原生工程的子模块,维持原有的原生工程管理方式不变,这种模式被称为三端分离模式。
在 Flutter 框架出现早期,由于官方提供的混编方式以及资料有限,国内较早使用 Flutter 进行混合开发的团队大多使用的是统一管理模式。但是,随着业务迭代的深入,统一管理模式的弊端也随之显露,不仅三端(Android、iOS和Flutter)代码耦合严重,相关工具链耗时也随之大幅增长,最终导致开发效率降低。所以,后续使用 Flutter 进行混合开发的团队大多使用三端代码分离的模式来进行依赖治理,最终实现 Flutter 工程的轻量级接入。
除了可以轻量级接入外,三端代码分离模式还可以把 Flutter 模块作为原生工程的子模块,从而快速地接入 Flutter 模块,降低原生工程的改造成本。在完成对 Flutter 模块的接入后,Flutter 工程可以使用 Android Studio 进行开发,无需再打开原生工程就可以对 Dart 代码和原生代码进行开发调试。
使用三端分离模式进行 Flutter 混合开发的关键是抽离 Flutter 工程,将不同平台的构建产物依照标准组件化的形式进行管理,即 Android 使用 aar、iOS 使用 pod。也就是说,Flutter 的混编方案其实就是将 Flutter 模块打包成 aar 或者 pod 库,然后在原生工程像引用其他第三方原生组件库那样引入 Flutter 模块即可。
Flutter 模块
默认情况下,新创建的 Flutter 工程会包含 Flutter 目录和原生工程的目录。在这种情况下,原生工程会依赖 Flutter 工程的库和资源,并且无法脱离 Flutter 工程独立构建和运行。
在混合开发中,原生工程对 Flutter 的依赖主要分为两部分。一个是 Flutter 的库和引擎,主要包含 Flutter 的 Framework 库和引擎库;另一个是 Flutter 模块工程,即 Flutter 混合开发中的 Flutter 功能模块,主要包括 Flutter 工程 lib 目录下的 Dart 代码实现。
对于原生工程来说,集成 Flutter 只需要在同级目录创建一个 Flutter 模块,然后构建 iOS 和 Android 各自的 Flutter 依赖库即可。接下来,我们只需要在原生项目的同级目录下,执行 Flutter 提供的构建模块命令创建 Flutter 模块即可,如下所示。
flutter create -t module flutter_library
其中,flutter_library 为 Flutter 模块名。执行上面的命令后,会在原生工程的同级目录下生成一个 flutter_library 模块工程。Flutter 模块也是 Flutter 工程,使用 Android Studio 打开它,其目录如下图所示。
可以看到,和普通的 Flutter 工程相比,Flutter 模块工程也内嵌了 Android 工程和 iOS 工程,只不过默认情况下,Android 工程和 iOS 工程是隐藏的。因此,对于 Flutter 模块工程来说,也可以像普通工程一样使用 Android Studio 进行开发和调试。
同时,相比普通的 Flutter 工程,Flutter 模块工程的 Android 工程目录下多了一个 Flutter 目录,此目录下的 build.gradle 配置就是我们构建 aar 时的打包配置。同样,在 Flutter 模块工程的 iOS 工程目录下也会找到一个 Flutter 目录,这也是 Flutter 模块工程既能像 Flutter 普通工程一样使用 Android Studio 进行开发调试,又能打包构建 aar 或 pod 的原因。
Android 集成 Flutter
在原生 Android 工程中集成 Flutter,原生工程对 Flutter 的依赖主要包括两部分,分别是 Flutter 库和引擎,以及 Flutter 工程构建产物。
Flutter 库和引擎:包含 icudtl.dat、libFlutter.so 以及一些 class 文件,最终这些文件都会被封装到 Flutter.jar 中。 Flutter 工程产物:包括应用程序数据段 isolate_snapshot_data、应用程序指令段 isolate_snapshot_instr、虚拟机数据段 vm_snapshot_data、虚拟机指令段 vm_snapshot_instr 以及资源文件 flutter_assets。
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir.parentFile,
'flutter_library/.android/include_flutter.groovy'))
dependencies {
implementation project(":flutter")
}
如果出现“程序包 android.support.annotation 不存在”的错误,需要使用如下的命令来创建 Flutter 模块,因为最新版本的 Android 默认使用 androidx 来管理包。
flutter create --androidx -t module flutter_library
在原生 Android 工程中成功添加 Flutter 模块依赖后,打开原生 Android 工程,并在应用的入口 MainActivity 文件中添加如下代码。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View flutterView = Flutter.createView(this, getLifecycle(), "route1");
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(flutterView, layoutParams);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentTransaction ft= getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, Flutter.createFragment("Hello Flutter"));
ft.commit();
}
}
flutter build apk --debug
打包构建的 flutter-debug.aar 位于 .android/Flutter/build/outputs/aar/ 目录下,可以把它拷贝到原生 Android 工程的 app/libs 目录下,然后在原生 Android 工程的 app 目录的打包配置 build.gradle 中添加对它的依赖,如下所示。
dependencies {
implementation(name: 'flutter-debug', ext: 'aar')
}
iOS 集成 Flutter
在原生 iOS 工程中集成 Flutter 需要先配置好 CocoaPods,CocoaPods 是 iOS 的类库管理工具,用来管理第三方开源库。在原生 iOS 工程中执行 pod init 命令创建一个 Podfile 文件,然后在 Podfile 文件中添加 Flutter 模块依赖,如下所示。
flutter_application_path = '../flutter_ library/
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'iOSDemo' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
install_all_flutter_pods(flutter_application_path)
# Pods for iOSDemo
… //省略其他脚本
end '
默认情况下,Flutter 是不支持 Bitcode 的,Bitcode 是一种 iOS 编译程序的中间代码,在原生 iOS 工程中集成 Flutter 需要禁用 Bitcode。在 Xcode 中依次选择【TAGETS】→【Build Setttings】→【Build Options】→【Enable Bitcode】来禁用 Bitcode,如下图所示。
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed不过,最新版本的Flutter已经不需要再添加脚本了。重新运行原生iOS工程,如果没有任何错误则说明iOS成功集成Flutter模块。
除了使用Flutter模块方式外,还可以将Flutter模块打包成可以依赖的动态库,然后再使用CocoaPods添加动态库。首先,在flutter_library根目录下执行打包构建命令生成framework动态库,如下所示。flutter build ios --debug
然后,在原生 iOS 工程的根目录下创建一个名为 FlutterEngine 的目录,并把生成的两个framework 动态库文件拷贝进去。不过,iOS 生成模块化产物要比 Android 多一个步骤,因为需要把 Flutter 工程编译生成的库手动封装成一个 pod。首先,在 flutter_ library 该目录下创建 FlutterEngine.podspec,然后添加如下脚本代码。
Pod::Spec.new do |s|
s.name = 'FlutterEngine'
s.version = '0.1.0'
s.summary = 'FlutterEngine'
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 = { 'xzh' => '1044817967@qq.com' }
s.source = { :git => "", :tag => "#{s.version}" }
s.ios.deployment_target = '9.0'
s.ios.vendored_frameworks = 'App.framework', 'Flutter.framework'
end
target 'iOSDemo' do
pod 'FlutterEngine', :path => './'
end
#import "ViewController.h"
#import
#import
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [[UIButton alloc]init];
[button setTitle:@"加载Flutter模块" forState:UIControlStateNormal];
button.backgroundColor=[UIColor redColor];
button.frame = CGRectMake(50, 50, 200, 100);
[button setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
[button addTarget:self action:@selector(buttonPrint) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)buttonPrint{
FlutterViewController * flutterVC = [[FlutterViewController alloc]init];
[flutterVC setInitialRoute:@"defaultRoute"];
[self presentViewController:flutterVC animated:true completion:nil];
}
@end
Flutter 模块调试
那么,能不能在混合项目中开启 Flutter 的热重载呢?答案是可以的,只需要经过如下步骤即可开启热重载功能。首先,关闭原生应用,此处所说的关闭是指关闭应用的进程,而不是简单的退出应用。在 Flutter 模块的根目录中输入 flutter attach 命令,然后再次打开原生应用,就会看到连接成功的提示,如下图所示。
在 Flutter 工程中,我们可以直接点击debug按钮来进行代码调试,但在混合项目中,直接点击 debug 按钮是不起作用的。此时,可以使用 Android Studio 提供的 flutter attach 按钮来建立与 flutter 模块的连接,进行实现对 flutter 模块的代码调试,如图下图所示。