iOS老项目集成Flutter(iOS混编Flutter)
问题
Flutter已经玩了些日子,但是老项目怎么集成Flutter呢?
集成了怎么热重载?
Flutter和原生安卓、iOS是怎么关联的呢?
Flutter和原生是怎么交互?
iOS老项目集成Flutter流程:
1、flutter环境安装 flutter中文网,特别详细,跟着来就行。
2、确保环境配置正确,全部都是小对号
终端执行
flutter doctor -v
如下是打印结果:
houjianan:FlutterMixed> flutter doctor -v
[✓] Flutter (Channel master, v0.10.2-pre.119, on Mac OS X 10.13.4 17E202, locale zh-Hans-CN)
• Flutter version 0.10.2-pre.119 at /Users/houjianan/flutter
• Framework revision 50098f149d (2 days ago), 2018-10-30 22:54:49 -0400
• Engine revision 3a67757300
• Dart version 2.1.0 (build 2.1.0-dev.8.0 bf26f760b1)
[✓] Android toolchain - develop for Android devices (Android SDK 28.0.3)
• Android SDK at /Users/houjianan/Library/Android/sdk
• Android NDK at /Users/houjianan/Library/Android/sdk/ndk-bundle
• Platform android-28, build-tools 28.0.3
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
• All Android licenses accepted.
[✓] iOS toolchain - develop for iOS devices (Xcode 9.4.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 9.4.1, Build version 9F2000
• ios-deploy 1.9.2
• CocoaPods version 1.5.3
[✓] Android Studio (version 3.2)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 29.1.1
• Dart plugin version 181.5656
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1136-b06)
[✓] IntelliJ IDEA Ultimate Edition (version 2018.1.1)
• IntelliJ at /Applications/IntelliJ IDEA.app
• Flutter plugin version 27.1.2
• Dart plugin version 181.4445.29
[✓] Connected device (6 available)
• “houjianan”的 iPhone • ffba6dc829885833aab8cf53b275216ee9949c05 • ios • iOS 12.0.1
• No issues found!
环境配置好了 就可以创建一个iOS项目或者在老项目上操作
3、iOS工程Enable Bitcode 需要关闭,因为Flutter混合开发不支持Bitcode
E9666FF3-7A49-478D-AB60-C4984D9FBE51.png4、flutter module创建
a、切换分支flutter channel master
houjianan:Desktop> cd /Users/houjianan/flutter
houjianan:flutter> flutter channel master
╔════════════════════════════════════════════════════════════════════════════╗
║ A new version of Flutter is available! ║
║ ║
║ To update to the latest version, run "flutter upgrade". ║
╚════════════════════════════════════════════════════════════════════════════╝
Switching to flutter channel 'master'...
git: Already on 'master'
git: Your branch is behind 'origin/master' by 19 commits, and can be fast-forwarded.
git: (use "git pull" to update your local branch)
houjianan:flutter>
拉新代码
houjianan:flutter> git pull
b、创建flutter create -t module flutter_module
houjianan:fluttermixed> cd /Users/houjianan/Desktop/fluttermixed
houjianan:fluttermixed> ls
FlutterMixed
houjianan:fluttermixed> flutter create -t module flutter_module
Creating project flutter_module...
flutter_module/.gitignore (created)
flutter_module/.idea/libraries/Dart_SDK.xml (created)
flutter_module/.idea/libraries/Flutter_for_Android.xml (created)
flutter_module/.idea/modules.xml (created)
flutter_module/.idea/workspace.xml (created)
flutter_module/.metadata (created)
flutter_module/lib/main.dart (created)
flutter_module/flutter_module.iml (created)
flutter_module/flutter_module_android.iml (created)
flutter_module/pubspec.yaml (created)
flutter_module/README.md (created)
flutter_module/test/widget_test.dart (created)
Running "flutter packages get" in flutter_module... 21.9s
Wrote 12 files.
All done!
Your module code is in flutter_module/lib/main.dart.
houjianan:fluttermixed> ls
FlutterMixed flutter_module
houjianan:fluttermixed> cd flutter_module/
houjianan:flutter_module> ls -la
total 88
drwxr-xr-x 15 houjianan staff 510 11 2 10:38 .
drwxr-xr-x 5 houjianan staff 170 11 2 10:37 ..
drwxr-xr-x 12 houjianan staff 408 11 2 10:38 .android
-rw-r--r-- 1 houjianan staff 339 11 2 10:37 .gitignore
drwxr-xr-x 5 houjianan staff 170 11 2 10:37 .idea
drwxr-xr-x 7 houjianan staff 238 11 2 10:38 .ios
-rw-r--r-- 1 houjianan staff 308 11 2 10:37 .metadata
-rw-r--r-- 1 houjianan staff 4983 11 2 10:38 .packages
-rw-r--r-- 1 houjianan staff 162 11 2 10:37 [README.md](http://readme.md/)
-rw-r--r-- 1 houjianan staff 896 11 2 10:37 flutter_module.iml
-rw-r--r-- 1 houjianan staff 1465 11 2 10:37 flutter_module_android.iml
drwxr-xr-x 3 houjianan staff 102 11 2 10:37 lib
-rw-r--r-- 1 houjianan staff 8556 11 2 10:38 pubspec.lock
-rw-r--r-- 1 houjianan staff 370 11 2 10:37 pubspec.yaml
drwxr-xr-x 3 houjianan staff 102 11 2 10:37 test
houjianan:flutter_module>
创建完之后 如下图
注意:查看隐藏文件快捷键 shift+command+。
创建iOS项目的Config文件(管理Xcode工程的配置衔接文件) 里面包含分别创建 Flutter.xcconfig、Debug.xcconfig、Release.xcconfig 三个配置文件;其中Flutter.xcconfig 是指向外目录flutter module的Generated.xcconfig 文件路径引用文件,其他两个代表Xcode的环境配置文件。
10FEC9A7-44D1-423E-B808-82F69E0DE56C.pngE88EBAF3-AE9A-422D-899F-B0D98933C0CF.png
使用了cocoapods 在Debug和Release里面添加
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.release.xcconfig"
Flutter.xcconfig
//
// Flutter.xcconfig
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#include "../../flutter_module/.ios/Flutter/Generated.xcconfig"
ENABLE_BITCODE=NO
Debug.xcconfig
//
// Debug.xcconfig
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
Release.xcconfig
//
// Release.xcconfig
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#include "Flutter.xcconfig"
#include "Pods/Target Support Files/Pods-FlutterMixed/Pods-FlutterMixed.debug.xcconfig"
FLUTTER_BUILD_MODE=release
Xcode project环境配置选择
EEE2A73B-AB3E-4C1B-890A-8C4208822188.png添加脚本
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh"build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh”embed
52EB1B8C-406A-4C1D-9250-3DD0A3445B2C.png
4D47862C-91B6-42A8-9F5D-A70FA5FD6001.png
注意:
Run Script 在Target Dependencies或者[CP]Check pods Manifest.lock后面
添加好了之后run下项目,就会执行脚本,iOS工程文件下会有一个Flutter文件夹(第一次搞是这么回事儿,有时候就是不会生成,具体为啥不清楚,有知道的请留言告诉我,感谢!)。
run之后如果没有Flutter这个文件夹,手动创建一个,然后把flutter_module->.ios->Fluter里面的App.framework、engine、flutter_assets添加进刚才手动创建的Flutter文件夹内
项目里面文件夹的颜色不同。
012931A0-DF96-4FF2-A46A-2E4332EB716D.png
蓝色文件夹是选择了Create folder references
黄色文件夹是选择了Create groups
flutter_assets一定要蓝色的 不然flutter界面啥都看不见。
用Android Studio运行项目
把main.dart文件里面的内容全部替换为如下代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or press Run > Flutter Hot Reload in a Flutter IDE). Notice that the
// counter didn't reset back to zero; the application is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// 创建一个给native的channel (类似iOS的通知)
static const methodChannel = const MethodChannel('com.pages.your/native_get');
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
print('flutter的log打印:现在输出count=$_counter');
// 当个数累积到3的时候给客户端发参数
if(_counter == 3) {
_toNativeSomethingAndGetInfo();
}
// 当个数累积到5的时候给客户端发参数
if(_counter == 1002) {
Map<String, String> map = { "title": "这是一条来自flutter的参数" };
methodChannel.invokeMethod('toNativePush',map);
}
// 当个数累积到8的时候给客户端发参数
if(_counter == 1005) {
Map<String, dynamic> map = { "content": "flutterPop回来","data":[1,2,3,4,5]};
methodChannel.invokeMethod('toNativePop',map);
}
});
}
// 给客户端发送一些东东 , 并且拿到一些东东
Future<Null> _toNativeSomethingAndGetInfo() async {
dynamic result;
try {
result = await methodChannel.invokeMethod('toNativeSomething','大佬你点击了$_counter下');
} on PlatformException {
result = 100000;
}
setState(() {
// 类型判断
if (result is int) {
_counter = result;
}
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
// appBar: new AppBar(
// // Here we take the value from the MyHomePage object that was created by
// // the App.build method, and use it to set our appbar title.
// title: new Text(widget.title),
// ),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'he button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
OC-ViewController.m
//
// ViewController.m
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#import "ViewController.h"
#import <Flutter/Flutter.h>
@interfaceViewController()
@end
@implementationViewController
- (void)viewDidLoad {
[superviewDidLoad];
}
- (void)didReceiveMemoryWarning {
[superdidReceiveMemoryWarning];
}
- (void)pushFlutterViewController {
FlutterViewController* flutterViewController = [[FlutterViewControlleralloc] initWithProject:nilnibName:nilbundle:nil];
flutterViewController.navigationItem.title= @"Flutter Demo";
__weak__typeof(self) weakSelf = self;
// 要与main.dart中一致
NSString*channelName = @"com.pages.your/native_get";
FlutterMethodChannel*messageChannel = [FlutterMethodChannelmethodChannelWithName:channelName binaryMessenger:flutterViewController];
[messageChannel setMethodCallHandler:^(FlutterMethodCall* _Nonnullcall, FlutterResult _Nonnullresult) {
// call.method 获取 flutter 给回到的方法名,要匹配到 channelName 对应的多个 发送方法名,一般需要判断区分
// call.arguments 获取到 flutter 给到的参数,(比如跳转到另一个页面所需要参数)
// result 是给flutter的回调, 该回调只能使用一次
NSLog(@"flutter 给到我:\nmethod=%@ \narguments = %@",call.method,call.arguments);
if([call.methodisEqualToString:@"toNativeSomething"]) {
UIAlertView*alertView = [[UIAlertViewalloc] initWithTitle:@"flutter回调"message:[NSStringstringWithFormat:@"%@",call.arguments] delegate:selfcancelButtonTitle:@"确定"otherButtonTitles:nil];
[alertView show];
// 回调给flutter
if(result) {
result(@1000);
}
} elseif([call.methodisEqualToString:@"toNativePush"]) {
// ThirdViewController *testVC = [[ThirdViewController alloc] init];
// testVC.parames = call.arguments;
// [weakSelf.navigationController pushViewController:testVC animated:YES];
} elseif([call.methodisEqualToString:@"toNativePop"]) {
[weakSelf.navigationControllerpopViewControllerAnimated:YES];
}
}];
// [self.navigationController pushViewController:flutterViewController animated:YES];
[selfpresentViewController:flutterViewController animated:YEScompletion:nil];
}
-(void)touchesBegan:(NSSet<UITouch*> *)touches withEvent:(UIEvent*)event{
[selfpushFlutterViewController];
}
@end
OC-AppDelegate.h
//
// AppDelegate.h
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#import <Flutter/Flutter.h>
@interfaceAppDelegate : FlutterAppDelegate<UIApplicationDelegate, FlutterAppLifeCycleProvider>
@end
OC-AppDelegate.m
//
// AppDelegate.h
// FlutterMixed
//
// Created by 侯佳男 on 2018/11/2.
// Copyright © 2018年 侯佳男. All rights reserved.
//
#import "AppDelegate.h"
@interfaceAppDelegate()
@end
@implementationAppDelegate
{
FlutterPluginAppLifeCycleDelegate*_lifeCycleDelegate;
}
- (instancetype)init {
if(self= [superinit]) {
_lifeCycleDelegate= [[FlutterPluginAppLifeCycleDelegatealloc] init];
}
returnself;
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
return[_lifeCycleDelegateapplication:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)applicationDidEnterBackground:(UIApplication*)application {
[_lifeCycleDelegateapplicationDidEnterBackground:application];
}
- (void)applicationWillEnterForeground:(UIApplication*)application {
[_lifeCycleDelegateapplicationWillEnterForeground:application];
}
- (void)applicationWillResignActive:(UIApplication*)application {
[_lifeCycleDelegateapplicationWillResignActive:application];
}
- (void)applicationDidBecomeActive:(UIApplication*)application {
[_lifeCycleDelegateapplicationDidBecomeActive:application];
}
- (void)applicationWillTerminate:(UIApplication*)application {
[_lifeCycleDelegateapplicationWillTerminate:application];
}
- (void)application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)notificationSettings {
[_lifeCycleDelegateapplication:application
didRegisterUserNotificationSettings:notificationSettings];
}
- (void)application:(UIApplication*)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken {
[_lifeCycleDelegateapplication:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
- (void)application:(UIApplication*)application
didReceiveRemoteNotification:(NSDictionary*)userInfo
fetchCompletionHandler:(void(^)(UIBackgroundFetchResultresult))completionHandler {
[_lifeCycleDelegateapplication:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
return[_lifeCycleDelegateapplication:application openURL:url options:options];
}
-(BOOL)application:(UIApplication*)application handleOpenURL:(NSURL*)url {
return[_lifeCycleDelegateapplication:application handleOpenURL:url];
}
-(BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
sourceApplication:(NSString*)sourceApplication
annotation:(id)annotation {
return[_lifeCycleDelegateapplication:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
}
- (void)application:(UIApplication*)application
performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
completionHandler:(void(^)(BOOLsucceeded))completionHandler NS_AVAILABLE_IOS(9_0) {
[_lifeCycleDelegateapplication:application
performActionForShortcutItem:shortcutItem
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
handleEventsForBackgroundURLSession:(nonnullNSString*)identifier
completionHandler:(nonnullvoid(^)(void))completionHandler {
[_lifeCycleDelegateapplication:application
handleEventsForBackgroundURLSession:identifier
completionHandler:completionHandler];
}
- (void)application:(UIApplication*)application
performFetchWithCompletionHandler:(void(^)(UIBackgroundFetchResultresult))completionHandler {
[_lifeCycleDelegateapplication:application performFetchWithCompletionHandler:completionHandler];
}
- (void)addApplicationLifeCycleDelegate:(NSObject<FlutterPlugin>*)delegate {
[_lifeCycleDelegateaddDelegate:delegate];
}
#pragma mark - Flutter
// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
UIViewController* viewController = [UIApplicationsharedApplication].keyWindow.rootViewController;
if([viewController isKindOfClass:[FlutterViewControllerclass]]) {
return(FlutterViewController*)viewController;
}
returnnil;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
[supertouchesBegan:touches withEvent:event];
// Pass status bar taps to key window Flutter rootViewController.
if(self.rootFlutterViewController!= nil) {
[self.rootFlutterViewControllerhandleStatusBarTouches:event];
}
}
@end
贴完代码运行Xcode 点击屏幕 跳转flutter界面。
更改flutter界面代码 再运行Xcode 点击屏幕 跳转flutter界面,界面被更改。
热重载具体内容看闲鱼相关文章,多人写作开发看闲鱼相关文章,可以关注闲鱼公众号,他们定期更新超优质Flutter相关文章。s