本文先介绍一下现有工程如何集成 Flutter 实现混合开发,以及混合项目如何打包,再探索下如何下降原生和 Flutter 之间的依赖,使 Flutter 开发对原生开发的影响尽可能下降,以及一些我在尝试中遇到的问题及解决。android
Flutter 是 Google 发布的一个用于建立跨平台、高性能移动应用的框架。Flutter 和 QT mobile 同样,都没有使用原生控件,相反都实现了一个自绘引擎,使用自身的布局、绘制系统。开发者能够经过 Dart 语言开发 App,一套代码同时运行在 iOS 和 Android平台。Flutter 提供了丰富的组件、接口,开发者能够很快地为 Flutter 添加 Native 扩展。ios
开发者须要安装好 Flutter 的环境,执行flutter doctor -v
验证。git
flutter_doctor_vgithub
验证经过后便可开始集成 Flutter。web
最官方的教程应该是Add Flutter to existing apps了,按照教程以下一步步操做:shell
1.建立 flutter modulexcode
使用flutter create xxx
指令建立的 Flutter 项目包括用于 Flutter/Dart 代码的很是简单的工程。你能够修改 main.dart 的内容,以知足你的须要,并在此基础上进行构建。浏览器
假设你有一个已经存在 iOS 工程(以 flutterHybridDemo 为例)在some/path/flutterHybridDemo
,那么你新建的 flutter_module 和 iOS 工程应该在同一目录下(即都在 path 下)。网络
$ cd some/path/ $ flutter create -t module flutter_module
flutter_module目录结构app
经过
shift+command+.
显示/隐藏隐藏文件夹
2.将 flutter module 做为依赖添加到工程
假设文件夹结构以下:
some/path/ flutter_module/ lib/main.dart .ios/ ... flutterHybridDemo/ flutterHybridDemo.xcodeproj flutterHybridDemo/ AppDelegate.h AppDelegate.m ...
集成 Flutter 框架须要使用CocoaPods
,这是由于 Flutter 框架还须要对 flutter_module 中可能包含的任何 Flutter 插件可用。
- 若是须要,请参考cocoapods.org了解如何在您的电脑上安装 CocoaPods。
建立 Podfile:
$ cd some/path/flutterHybridDemo $ pod init
此时工程中会出现一个 Podfile 文件,添加项目依赖的第三方库就在这个文件中配置,编辑 Podfile 文件添加最后两行代码:
# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'TestOne' do # Uncomment the next line if you're using Swift or would like to use dynamic frameworks # use_frameworks! # Pods for TestOne target 'TestOneTests' do inherit! :search_paths # Pods for testing end target 'TestOneUITests' do inherit! :search_paths # Pods for testing end end #新添加的代码 flutter_application_path = '../flutter_module' eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
- 若是你的工程(flutterHybridDemo)已经在使用 Cocoapods ,你只须要作如下几件事来整合你的 flutter_module 应用程序:
(1)添加以下内容到 Podfile:
flutter_application_path = '../flutter_module' eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
(2)执行pod install
当你在some/path/flutter_module/pubspec.yaml
中修改 Flutter 插件依赖时,须要先执行flutter packages get
经过 podhelper.rb 脚原本刷新插件列表,而后再从some/path/flutterHybridDemo
执行一次pod install
。
podhelper.rb 脚本将确保你的插件和 Flutter 框架被添加到你的工程中,以及 bitcode 被禁用。
(3)禁用 bitcode
由于 Flutter 如今不支持 bitcode。须要设置 Build Settings->Build Options->Enable Bitcode 为 NO。
bitcode 禁用
3.为编译 Dart 代码配置 build phase
打开 iOS 工程,选中项目的 Build Phases 选项,点击左上角+号按钮,选择 New Run Script Phase。
配置 build phase
将下面的 shell 脚本添加到输入框中:
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
最后,确保 Run Script 这一行在 "Target dependencies" 或者 "Check Pods Manifest.lock" 后面。
配置 build phase
至此,你能够编译一下工程确保无误:⌘B
。
4.在 iOS 工程中使用 FlutterViewController
首先声明你的 AppDelegate 是 FlutterAppDelegate 的子类。而后定义一个 FlutterEngine 属性,它能够帮助你注册一个没有 FlutterViewController 实例的插件。
在 AppDelegate.h:
#import <UIKit/UIKit.h> #import <Flutter/Flutter.h> @interface AppDelegate : FlutterAppDelegate @property (nonatomic,strong) FlutterEngine *flutterEngine; @end
在AppDelegate.m,修改didFinishLaunchingWithOptions
方法以下:
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Only if you have Flutter Plugins #include "AppDelegate.h" @implementation AppDelegate // This override can be omitted if you do not have any Flutter Plugins. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil]; [self.flutterEngine runWithEntrypoint:nil]; [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine]; return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end
若是 AppDelegate 已经继承于别的类的时候,能够经过让你的 delegate 实现FlutterAppLifeCycleProvider
协议:
#import <Flutter/Flutter.h> #import <UIKit/UIKit.h> #import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Only if you have Flutter Plugins @interface AppDelegate : UIResponder <UIApplicationDelegate, FlutterAppLifeCycleProvider> @property (strong, nonatomic) UIWindow *window; @end
而后生命周期方法应该由 FlutterPluginAppLifeCycleDelegate 来代理:
@implementation AppDelegate { FlutterPluginAppLifeCycleDelegate *_lifeCycleDelegate; } - (instancetype)init { if (self = [super init]) { _lifeCycleDelegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; } return self; } - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { [GeneratedPluginRegistrant registerWithRegistry:self]; // Only if you are using Flutter plugins. return [_lifeCycleDelegate application:application didFinishLaunchingWithOptions:launchOptions]; } // Returns the key window's rootViewController, if it's a FlutterViewController. // Otherwise, returns nil. - (FlutterViewController*)rootFlutterViewController { UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController; if ([viewController isKindOfClass:[FlutterViewController class]]) { return (FlutterViewController*)viewController; } return nil; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { [super touchesBegan:touches withEvent:event]; // Pass status bar taps to key window Flutter rootViewController. if (self.rootFlutterViewController != nil) { [self.rootFlutterViewController handleStatusBarTouches:event]; } } - (void)applicationDidEnterBackground:(UIApplication*)application { [_lifeCycleDelegate applicationDidEnterBackground:application]; } - (void)applicationWillEnterForeground:(UIApplication*)application { [_lifeCycleDelegate applicationWillEnterForeground:application]; } - (void)applicationWillResignActive:(UIApplication*)application { [_lifeCycleDelegate applicationWillResignActive:application]; } - (void)applicationDidBecomeActive:(UIApplication*)application { [_lifeCycleDelegate applicationDidBecomeActive:application]; } - (void)applicationWillTerminate:(UIApplication*)application { [_lifeCycleDelegate applicationWillTerminate:application]; } - (void)application:(UIApplication*)application didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings { [_lifeCycleDelegate application:application didRegisterUserNotificationSettings:notificationSettings]; } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { [_lifeCycleDelegate application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [_lifeCycleDelegate application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; } - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options { return [_lifeCycleDelegate application:application openURL:url options:options]; } - (BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url { return [_lifeCycleDelegate application:application handleOpenURL:url]; } - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation { return [_lifeCycleDelegate application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; } - (void)application:(UIApplication*)application performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler NS_AVAILABLE_IOS(9_0) { [_lifeCycleDelegate application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; } - (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString*)identifier completionHandler:(nonnull void (^)(void))completionHandler { [_lifeCycleDelegate application:application handleEventsForBackgroundURLSession:identifier completionHandler:completionHandler]; } - (void)application:(UIApplication*)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { [_lifeCycleDelegate application:application performFetchWithCompletionHandler:completionHandler]; } - (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate { [_lifeCycleDelegate addDelegate:delegate]; } @end
在 ViewController 中添加跳转到 FlutterViewController 的测试代码便可:
#import "ViewController.h" #import <Flutter/Flutter.h> #import "AppDelegate.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; [button addTarget:self action:@selector(handleButtonAction) forControlEvents:UIControlEventTouchUpInside]; [button setTitle:@"Jump to flutterViewController" forState:UIControlStateNormal]; [button setBackgroundColor:[UIColor grayColor]]; button.frame = CGRectMake(80.0, 210.0, 300.0, 40.0); button.center = self.view.center; [self.view addSubview:button]; } - (void)handleButtonAction { AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate; FlutterEngine *flutterEngine = delegate.flutterEngine; FlutterViewController *flutterVC = [[FlutterViewController alloc]initWithEngine:flutterEngine nibName:nil bundle:nil]; [self presentViewController:flutterVC animated:YES completion:nil]; } @end
5.使用热重载的方式调试 Dart 代码
热重载指的是不用从新启动就看到修改后的效果,相似 web 网页开发时保存就看到效果的方式。
进入 flutter module,在终端执行命令:
$ cd some/path/flutter_module $ flutter run
flutter run
而且你能在控制台中看下以下内容:
? To hot reload changes while running, press "r". To hot restart (and rebuild state), press "R". An Observatory debugger and profiler on iPhone X is available at: http://127.0.0.1:54741/ For a more detailed help message, press "h". To quit, press "q".
你能够在 flutter_module 中编辑 Dart code,而后在终端输入 r 来使用热重载。你也能够在浏览器中输入上面的 URL 来查看断点、分析内存和其余的调试任务。
1. flutter build ios
执行flutter build ios
以建立 release 版本(flutter build 默认为--release,如需建立 debug 版本执行flutter build ios —debug
)。
2.成功后修改 Xcode 为 release 模式配置
3.最后选择 Product > Archive 以生成构建版本便可
archive 成功
Flutter 的工程结构比较特殊,由 Flutter 目录、Native 工程的目录(即 iOS 和 Android 两个目录)组成。默认状况下,引入了 Flutter 的 Native 工程没法脱离父目录进行独立构建和运行,由于它会反向依赖于 Flutter 相关的库和资源。
实际上,在真实的开发状况下,开发者不多会建立一个彻底 Flutter 的工程重写项目,更多的状况是原生工程集成 Flutter。
1.问题
这样就带来了一系列问题:
(1)构建打包问题:引入 Flutter 后,Native 工程因对其有了依赖和耦合,从而没法独立编译构建。在 Flutter 环境下,工程的构建是从 Flutter 的构建命令开始,执行过程当中包含了 Native 工程的构建,开发者要配置完整的 Flutter 运行环境才能走通整个流程;
(2)混合编译带来的开发效率的下降:在转型 Flutter 的过程当中必然有许多业务仍使用 Native 进行开发,工程结构的改动会使开发没法在纯 Native 环境下进行,而适配到 Flutter 工程结构对纯 Native 开发来讲又会形成没必要要的构建步骤,形成开发效率的下降。
2.目标
但愿能将 Flutter 依赖抽取出来,做为一个 Flutter 依赖库,供纯 Native 工程引用,无需配置完整的 Flutter 环境。
3.Flutter 产物
iOS 工程对 Flutter 有以下依赖:
Flutter.framework:Flutter 库和引擎
App.framework:dart 业务源码相关文件
flutter_assets:Flutter依赖的静态资源,如字体,图片等
Flutter Plugin:编译出来的各类 plugin 的 framework
把以上依赖的编译结果抽取出来,便是 Flutter 相关代码的最终产物。
那么咱们只须要将这些打包成一个 SDK 依赖的形式提供给 Native 工程,就能够解除 Native 工程对 Flutter 工程的直接依赖。
产物的产生:
对 flutter 工程执行 flutter build 命令后,生成在.ios/Flutter
目录下,直接手动拷贝 framework 到主工程便可。
注意事项:
framework 选择 Create groups 加入文件夹,flutter_assets 选择 Create folder references 加入文件夹。
add_in_project
加入完成后的结构:
thirdFramework
framework 加入后,记住必定要确认 framework 已在 TARGETS -> General -> Embedded Binaries 中添加完成。
embedded_binaires
最后改造 APPDelegate 便可:
#import <UIKit/UIKit.h> #import <Flutter/Flutter.h> @interface AppDelegate : FlutterAppDelegate <UIApplicationDelegate> @property (strong, nonatomic) FlutterEngine *flutterEngine; @end
#import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.flutterEngine = [[FlutterEngine alloc]initWithName:@"io.flutter" project:nil]; [self.flutterEngine runWithEntrypoint:nil]; return YES; }
4. 优化
为了更方便管理 framework,能够将这些文件上传到远程仓库,经过 CocoaPods 导入,Native 项目只需及时更新 pod 依赖便可。
1.在 Android Studio 上跑设备
More than one device connected; please specify a device with the '-d <deviceId>' flag, or use '-d all' to act on all devices.
选择模拟器
提示你当前有两个模拟器设备,跑设备的时候要选择运行在哪一个设备上,flutter run
后面拼接上“-d <deviceId>”,deviceId 是第二列的内容。
flutter run -d emulator-5554 flutter run -d C517D2D4-EAFA-42CA-B260-A18FA0ABFF60
电脑连着真机也同理,改为真机的 deviceId 便可。
2.flutter build ios 报错
build 时可能遇到的错误:
It appears that your application still contains the default signing identifier.Try replacing 'com.example' with your signing id in Xcode:
open ios/Runner.xcworkspace
build 时可能遇到的错误
解决方法:
修改some/flutter_module/.ios/
下 Runner 工程的 Bundle Identifier 和原生工程的一致,再次运行flutter build ios
便可。
3.开发时打包产物编译失败
当你用flutter build ios
的产物添加到原生工程中,跳转到 Flutter 界面会黑屏并报出以下错误:
flutter_build_questions
Failed to find snapshot: …/Library/Developer/CoreSimulator/Devices/…/data/Containers/Bundle/Application/…/FlutterMixDemo.app/Frameworks/App.framework/flutter_assets/kernel_blob.bin
如何解决:
调试模式下用flutter build ios —debug
的产物,再次拖入工程便可。
缘由:
首先咱们对比下,执行flutter build ios
和执行flutter build ios --debug
后 .ios/Flutter/App.framework/flutter_assets
的文件内容:
flutter_build_ios.png
flutter_build_ios_debug.png
能够发现,差异是在于三个文件:isolate_snapshot_data、kernel_blob.bin、vm_snapshot_data。
这里涉及 Flutter 的编译模式知识,具体能够参阅Flutter 的两种编译模式。
Flutter 开发阶段的编译模式:使用了 Kernel Snapshot 模式编译,打包产物中,能够发现几样东西:
isolate_snapshot_data:用于加速 isolate 启动,业务无关代码,固定,仅和 flutter engine 版本有关;
platform.dill:和 Dart VM 相关的 kernel 代码,仅和 Dart 版本以及 engine 编译版本有关。固定,业务无关代码;
vm_snapshot_data:用于加速 Dart VM 启动的产物,业务无关代码,仅和 flutter engine 版本有关;
kernel_blob.bin:业务代码产物 。
Flutter 生产阶段的编译模式:选择了 AOT 打包。
4.集成后 Native 工程报错
Shell Script Invocation Error
line 2:/packages/flutter_tools/bin/xcode_backend.sh: No such file or directory
集成后 Native 工程报错
解决方法:
修改 TARGETS -> Build Setting -> FLUTTER_ROOT 为电脑安装的 Flutter 环境的路径便可。
集成后 Native 工程报错
5.如何在 iOS 工程 Debug 模式下使用 release 模式的 flutter
只须要将 Generated.xcconfig 中的 FLUTTER_BUILD_MODE 修改成 release,FLUTTER_FRAMEWORK_DIR 修改成 release 对应的路径便可。
1.说明:
本文仅供用于学习参考,请勿用于商业用途。如需转载,请标明出处,谢谢合做。
本文系参考网络公开 Flutter 学习资料以及我的学习体会总结所得,部份内容为网络公开学习资料,若有侵权请联系做者删除。
2.参考资料:
Flutter 中文网:https://flutterchina.club
咸鱼技术-flutter:https://www.yuque.com/xytech/flutter
iOS Native混编Flutter交互实践:https://juejin.im/post/5bb033515188255c5e66f500#heading-3
Flutter混编之路——开发集成(iOS篇):https://www.jianshu.com/p/48a9083ebe89