Flutter - iOS集成开发
在Flutter的应用场景中,有时候一个APP只有部分页面是由Flutter实现的,比如:我们常用的闲鱼App,它宝贝详情页面是由Flutter实现的,这种开发模式被称为混合开发。
混合开发的一些其他应用场景:
在原有项目中加入Flutter页面,在Flutter项目中加入原生页面
原生页面中嵌入Flutter模块
Flutter页面中嵌入原生模块
Flutter 混编方案介绍
如果你想要在已有的原生 App 里嵌入一些 Flutter 页面,有两个办法:
- 将原生工程作为 Flutter 工程的子工程,由 Flutter 统一管理。这种模式,就是统一管理模式。
- 将 Flutter 工程作为原生工程共用的子模块,维持原有的原生工程管理方式不变。这种模式,就是三端分离模式。
由于 Flutter 早期提供的混编方式能力及相关资料有限,国内较早使用 Flutter 混合开发的团队大多使用的是统一管理模式。但是,随着功能迭代的深入,这种方案的弊端也随之显露,不仅三端(Android、iOS、Flutter)代码耦合严重,相关工具链耗时也随之大幅增长,导致开发效率降低。
所以,后续使用 Flutter 混合开发的团队陆续按照三端代码分离的模式来进行依赖治理,实现了 Flutter 工程的轻量级接入。
除了可以轻量级接入,三端代码分离模式把 Flutter 模块作为原生工程的子模块,还可以快速实现 Flutter 功能的“热插拔”,降低原生工程的改造成本。而 Flutter 工程通过 Android Studio 进行管理,无需打开原生工程,可直接进行 Dart 代码和原生代码的开发调试。
三端工程分离模式的关键是抽离 Flutter 工程,将不同平台的构建产物依照标准组件化的形式进行管理,即 Android 使用 aar、iOS 使用 pod。换句话说,接下来介绍的混编方案会将 Flutter 模块打包成 aar 和 pod,这样原生工程就可以像引用其他第三方原生组件库那样快速接入 Flutter 了。
将Flutter集成到现有的iOS应用中需要如下几个主要步骤:
- 创建Flutter module;
- 为已存在的iOS应用添加Flutter module依赖;
- 在Object-c中调用Flutter module;
- 编写Dart代码;
- 运行项目;
- 热重启/重新加载;
- 调试Dart代码;
- 发布应用;
1 | - flutter_hybrid |
flutter_hybrid
下面分别是flutter模块
,原生Android模块
,与原生iOS模块
,并且这三个模块时并列结构
创建Flutter module
在做混合开发之前我们首先需要创建一个Flutter module。
假如你的Native项目是这样的:xxx/flutter_hybrid/FlutterHybridiOS
:
1 | $ cd xxx/flutter_hybrid/ |
上面代码会切换到你的iOS项目的上一级目录,并创建一个flutter模块:
1 | .android |
上面是flutter_module
中的文件结构,你会发现它里面包含.android
与.ios
,这两个文件夹是隐藏文件,也是这个flutter_module
宿主工程:
.android
-flutter_module
的Android宿主工程;.ios
-flutter_module
的iOS宿主工程;lib
-flutter_module
的Dart部分的代码;pubspec.yaml
-flutter_module
的项目依赖配置文件;
因为宿主工程的存在,我们这个
flutter_module
在不加额外的配置的情况下是可以独立运行的,通过安装了Flutter与Dart插件的AndroidStudio打开这个flutter_module
项目,通过运行按钮是可以直接运行它的。
为已存在的iOS应用添加Flutter module依赖
接下来我们需要配置我们iOS项目的Flutter module依赖,接下来的配置需要用到CocoaPods,如果你还没有用到CocoaPods,可以参考https://cocoapods.org/上面的说明来安装CocoaPods。
在Podfile
文件中添加flutter
依赖
- 如果你的iOS项目中没有
Podfile
文件可以通过:1
pod init
- 找到
Podfile
文件,填入依赖信息
版本的新旧,依赖信息引入的方式也不同Flutter >= v1.10.14 版本添加的配置:
1
2
3flutter_application_path = 'path/to/my_flutter/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
install_all_flutter_pods(flutter_application_path)下面是博主的配置:请参考
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16flutter_application_path = '../flutter_module/'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'FlutterHybridiOS' do
# Step2 添加
install_all_flutter_pods(flutter_application_path)
target 'FlutterHybridiOSTests' do
inherit! :search_paths
# Pods for testing
end
target 'FlutterHybridiOSUITests' do
inherit! :search_paths
# Pods for testing
end
end老版本Flutter,
Podfile
配置1
2flutter_application_path = 'path/to/my_flutter/'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)下面是博主的配置:请参考
1
2
3
4
5
6
7
8
9
10
11
12target 'FlutterHybridiOS' do
flutter_application_path = '../flutter_module/'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
target 'FlutterHybridiOSTests' do
inherit! :search_paths
end
target 'FlutterHybridiOSUITests' do
inherit! :search_paths
end
end
除此之外,老版本flutter还需要添加 build phase以构建Dart代码
根据上图的提示创建一个build phase
,然后展开Run Script
并添加下面配置:
1 | "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build |
最后记得根据上图的提示,将Run Script
到紧挨着Target Dependencies phase
的下面,接下来就可以通过⌘B
构建你的项目了。
通过
flutter doctor
命令查看你的Flutter版本,不同Flutter版本需要添加的配置是不同的:
安装依赖
在iOS项目的根目录运行:
1 | pod install |
你会看到:
1 | pod install |
当你在flutter_module/pubspec.yaml
添加一个Flutter插件时,需要在flutter_module
目录下运行:
1 | flutter packages get |
来刷新podhelper.rb
脚本中插件列表,然后在iOS目录下运行:
1 | pod install |
这样以来podhelper.rb
脚本才能确保你的插件和Flutter.framework能够添加到你的iOS项目中。
禁用Bitcode
目前Flutter还不支持Bitcode,所以集成了Flutter的iOS项目需要禁用Bitcode:
用XCode打开你的项目如:xxx.xcworkspace
:
然后在:
1 | Build Settings->Build Options->Enable Bitcode |
目录下中禁用Bitcode:
如果在这个过程中遇到问题可以查看Under the hood文档。
在Object-c中调用Flutter module
至此,我们已经为我们的iOS项目添加了Flutter所必须的依赖,接下来我们来看如何在Object-c中调用Flutter模块:
在Object-c中调用Flutter模块有两种方式:
- 直接使用
FlutterViewController
的方式; - 使用
FlutterEngine
的方式;
FlutterViewController
// flutter_hybrid ▸ FlutterHybridiOS ▸ FlutterHybridiOS ▸ ViewController.m
#import <Flutter/Flutter.h>
#import "AppDelegate.h"
#import "ViewController.h"
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // 如果你需要用到Flutter插件时
FlutterViewController *flutterViewController = [FlutterViewController new];
GeneratedPluginRegistrant.register(with: flutterViewController);//如果你需要用到Flutter插件时
[flutterViewController setInitialRoute:@"route1"];
[self presentViewController:flutterViewController animated:true completion:nil];
通过这种方式我们可以使用flutterViewController setInitialRoute
的方法为传递了字符串“route1”来告诉Dart代码在Flutter视图中显示哪个小部件。 Flutter模块项目的lib/main.dart
文件需要通过window.defaultRouteName
来获取Native指定要显示的路由名,以确定要创建哪个窗口小部件并传递给runApp:
import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'route1':
return SomeWidget(...);
case 'route2':
return SomeOtherWidget(...);
default:
return Center(
child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
);
}
}
FlutterEngine
1 | // AppDelegate.h |
1 | // AppDelegate.m |
如果你的项目的AppDelegate.h已经有了别的集成,那么可惨参考实现FlutterAppLifeCycleProvider的方式进行配置。
1 | // flutter_hybrid ▸ FlutterHybridiOS ▸ FlutterHybridiOS ▸ ViewController.m |
因为我们在AppDelegate.m中提前初始化了
FlutterEngine
,所以这种方式打开一个Flutter模块的速度要比第一种方式要快一些。
调用Flutter module时传递数据
在上文中,我们无论是通过直接使用FlutterViewController
的方式还是通过FlutterEngine
的方式,都允许我们在加载Flutter module时传递一个String类型的initialRoute
参数,从参数名字它是用作路由名的,但是既然Flutter给我们开了这个口子,那我们是不是可以搞点事情啊,传递点我们想传的其他参数呢,比如:
1 | [flutterViewController setInitialRoute:@"{name:'devio',dataList:['aa','bb',''cc]}"]; |
然后在Flutter module通过如下方式获取:
1 | import 'dart:ui';//要使用window对象必须引入 |
通过上述方案的讲解是不是给大家分享了一个新的思路呢。
注意使用
FlutterEngine
的方式中调用setInitialRoute
会无效,你会发现Dart拿到的永远是“/”,这个是Fltter SDK的一个Bug,所以如果必须依赖setInitialRoute
那么请使用方式一的形式;
编写Dart代码
接下来就是在编写Flutter module中的lib下编写Dart带了,快去Enjoy Coding
吧!!!
运行项目
接下来,我们就可以运行它了,经过上述步骤,我们就可以以运行普通iOS项目的方式来通过XCode运行一个集成了Flutter的iOS项目了。
热重启/重新加载
大家知道我们在做Flutter开发的时候,它带有热重启/重新加载的功能,但是你可能会发现,混合开发中在iOS项目中集成了Flutter项目,Flutter的热重启/重新加载功能好像失效了,那怎么启用混合开发汇总Flutter的热重启/重新加载呢:
- 打开一个模拟器,或连接一个设备到电脑上;
- 关闭我们的APP,然后运行
flutter attach
;
1 | $ cd flutter_hybrid/flutter_module |
Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)
iPhone X • 3E3FA943-715F-482F-B003-D46F5902C56C • ios • iOS 12.1 (simulator)
1 |
|
注意-d
后面跟的设备ID。
运行APP,然后你会看到:
1 | $ flutter attach |
说明连接成功了,接下来就可以通过上面的提示来进行热加载/热重启了,在终端输入:
- r : 热加载;
- R : 热重启;
- h : 获取帮助;
- d : 断开连接;
调试Dart代码
混合开发的模式下,如何更好更高效的调试我们的代码呢,接下来我就跟大家分享一种混合开发模式下高效调试代码的方式:
关闭APP(这步很关键)
点击AndroidStudio的
Flutter Attach
按钮(需要首先安装Flutter与Dart插件)启动APP
接下来就可以像调试普通Flutter项目一样来调试混合开发的模式下的Dart代码了。
除了以上步骤不同之外,接下来的调试和我们之前课程中的Flutter调试技巧都是通用的。
大家在运行iOS工程时一定要用Xcode运行,因为Flutter模式下的AndroidStudio运行的是Flutter module下的.ios
中的iOS工程。
发布应用
发布iOS应用我们需要有一个99美元的账号用于将App上传到AppStore,或者是299美元的企业级账号用于将App发布到自己公司的服务器或第三方公司的服务器。
接下来我们就需要:
- 申请APPID
- 在Tunes Connect创建应用
- 打包程序
- 将应用提交到app store
- 等步骤。
因为官方文档中有详细的说明,在这我就不再重复了。