- 官网:https://flutter.dev/
- Flutter 中文网:https://flutterchina.club/
- Flutter Git: https://github.com/flutter/flutter
- Flutter boost: https://github.com/alibaba/flutter_boost
Mac系统Flutter环境集成
使用镜像
由于在国内访问Flutter有时可能会受到限制,Flutter官方为中国开发者搭建了临时镜像,可以将如下环境变量加入到用户环境变量中:
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
获取Flutter SDK
官网下载安装包:https://flutter.io/sdk-archive/#macos
解压后拷贝安装到想安装的目录(安装到哪里都可以,但是后面需要将这个安装路径添加到环境变量中)
我的安装目录(装在这里不是很好,但是添加了环境变量,就先不动了):
/Users/HUANGXIAO/flutter
添加flutter相关工具到环境变量中:
cd /Users/HUANGXIAO/flutter
export PATH=`pwd`/flutter/bin:$PATH
现在只是设置了临时环境变量,长期使用需要将其设置为永久的环境变量。
在家目录打开 .bash_profile 文件:
cd ~
open -e .bash_profile
配置环境变量:
添加flutter安装目录到path中,使用命令添加或者直接编辑.bash_profile
export PATH='你的安装目录'/bin:$PATH
我的环境变量如下
export JAVA_HOME=$(/usr/libexec/java_home)
export ANDROID_HOME="/Users/HUANGXIAO/Library/Android/sdk"
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
export PATH=/Users/HUANGXIAO/flutter/bin:$PATH
[[ -s "$HOME/.rvm/scripts/rvm" ]] && source "$HOME/.rvm/scripts/rvm" # Load RVM into a shell session *as a function*
更新环境变量:
source .bash_profile
验证目录是否在已经在PATH中:
echo $PATH
到此Flutter 已安装好,并导入到环境变量中。
安装Flutter依赖
终端输入:
flutter doctor
根据提示安装相应的依赖软件,比如 Android Studio、XCode、VSCode等,并安装相应的Flutter插件。
可参照Flutter中文网教程安装:https://flutterchina.club/setup-macos/
这一步可能会遇到一些问题,这篇文章总结得比较全面,可以参考,在此感谢!
https://www.jianshu.com/p/603649a02956
最终安装完后会全部是√,说明环境和依赖已经OK了,接下来就可以进行开发了。
iOS 项目集成Flutter编译环境
Flutter 与 原生项目 混编有两种方案:
1. 自动创建Android和iOS项目
如果项目之初就已经决定使用Flutter与Native混编方案,那么可以直接用Flutter生成项目,其中会自动生成iOS和Android相对应的原生项目。这也是比较简单而且高效的混编方案。
Andriod Studio 创建:
File → New → New Flutter Project
注意选择混编是Android和iOS的开发语言:
命令创建:
同样注意选择混编是Android和iOS的开发语言:
flutter create -i swift -a kotlin Name
创建好后的目录结构:
android为Android项目文件,ios为iOS项目文件。
这样就创建好混编项目,以iOS为例,用Xcode打开Runner.xcworkspace项目,可以看到:
Flutter已自动将Flutter与iOS代码集成好了。可以根据需要修改项目配置,如Display Name, Bundle Id 等。
iOS项目自动采用cocopod集成第三方库。
2. iOS老项目集成Flutter
官方指导:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps
如果已经有了iOS项目,需要Flutter与iOS混编。
创建Flutter module,请确保安卓、iOS、Flutter三个项目的根目录必须在同一目录下:
flutter create -t module flutter_module
如果iOS使用swift语言,请加上 -i swift
安卓、iOS、Flutter三个项目的根目录必须在同一目录下:
- iOS项目使用cocoapods管理第三方依赖包,并在Podfile加入如图以下代码,注意Flutter项目路径:
flutter_application_path = 'path/to/my_flutter/'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
打开Flutter项目 , 获取包:
flutter packages get
然后,将Flutter跑一遍,已确保Flutter生成iOS项目集成需要的依赖产物:
flutter build ios
Flutter项目运行成功后,iOS项目执行:
pod install
成功后可以在pods - Development Pods下看到Flutter 安装的包:
-
iOS项目Enable Bitcode改为NO
- iOS项目添加脚本,注意脚本位置要放在check pods manifest.lock之后:
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
如果一切顺利,编译iOS项目⌘B编译应该会成功。此时iOS项目Flutter编译环境已集成完成。
iOS Flutter混编
AppDelegate.swift修改
import UIKit
import Flutter
import FlutterPluginRegistrant // Only if you have Flutter Plugins.
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
var flutterEngine : FlutterEngine?;
// Only if you have Flutter plugins.
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.flutterEngine = FlutterEngine(name: "io.flutter", project: nil);
self.flutterEngine?.run(withEntrypoint: nil);
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
}
Native跳转Flutter:
官方方法:
import UIKit
import Flutter
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(type:UIButtonType.custom)
button.addTarget(self, action: #selector(handleButtonAction), for: .touchUpInside)
button.setTitle("Press me", for: UIControlState.normal)
button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
}
@objc func handleButtonAction() {
let flutterEngine = (UIApplication.shared.delegate as? AppDelegate)?.flutterEngine;
let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)!;
self.present(flutterViewController, animated: false, completion: nil)
}
}
指定Flutter路由:
flutterViewController.setInitialRoute("route1")
在我实际集成的时候,使用flutterEngine是没办法指定跳转的路由的,也就是flutterViewController.setInitialRoute("route1")无效!无效!无效!
如果不使用flutterEngine,直接初始化FlutterViewController并指定路由跳转是能够成功跳转相应路由的!但是,不使用flutterEngine会导致FlutterViewController()关闭是内存无法释放!内存无法释放!内存无法释放!
let flutterViewController = FlutterViewController()
flutterViewController.setInitialRoute("route1")
self.present(flutterViewController, animated: false, completion: nil)
这是Flutter官方最大的bug,也是最大的坑!
只有等待谷歌官方后续解决吧。
寻求解决办法:
1.使用消息传递机制跳转页面
iOS代码:
// 带默认返回操作Fluttervc
class func createFluttervcWithDefaultHandler(paramStr:String) -> (BaseFlutterViewController) {
let engine = HX_AppDelegate.flutterEngine
let messageChannel = HX_AppDelegate.messageChannel
let flutterVC = BaseFlutterViewController(engine: engine, nibName: nil, bundle: nil)!
let channelName = "com.novasoftware.ShoppingMall.address"
let channel = FlutterMethodChannel(name: channelName, binaryMessenger: flutterVC)
channel.setMethodCallHandler { (call: FlutterMethodCall, result: FlutterResult) in
print(call.method)
if call.method == "back" {
flutterVC.dismiss(animated: true, completion: nil)
}
}
print("Native:" + paramStr)
engine!.navigationChannel.invokeMethod("", arguments: paramStr)
messageChannel!.sendMessage(paramStr) // 发送消息
return flutterVC
}
// 自定义handler的Fluttervc
class func createFluttervcWithHandler(paramStr:String, handler:@escaping FlutterMethodCallHandler) -> (BaseFlutterViewController) {
let engine = HX_AppDelegate.flutterEngine
let messageChannel = HX_AppDelegate.messageChannel
let flutterVC = BaseFlutterViewController(engine: engine, nibName: nil, bundle: nil)!
let channelName = "com.novasoftware.ShoppingMall.address"
let channel = FlutterMethodChannel(name: channelName, binaryMessenger: flutterVC)
channel.setMethodCallHandler(handler)
print("Native:" + paramStr)
engine!.navigationChannel.invokeMethod("", arguments: paramStr)
messageChannel!.sendMessage(paramStr) // 发送消息
return flutterVC
}
Native页面跳转Flutter指定页面,并带参数传递:
var flutterVC = BaseFlutterViewController()
let handler : FlutterMethodCallHandler = { (call: FlutterMethodCall, result: FlutterResult) in
print(call.method)
if call.method == "back" {
// 消息交互
flutterVC.dismiss(animated: true, completion: nil)
}
}
let paramStr = "MyCoupon?" + (SM_token ?? "")
flutterVC = HXHelper.createFluttervcWithHandler(paramStr: paramStr, handler: handler)
self.viewContainingController()?.present(flutterVC, animated: true, completion: nil)
Flutter代码:
const String _kReloadChannelName = 'reload';
const BasicMessageChannel<String> _kReloadChannel =
BasicMessageChannel<String>(_kReloadChannelName, StringCodec());
void main() {
_kReloadChannel.setMessageHandler(run);
print("Flutter---" + ui.window.defaultRouteName);
run(ui.window.defaultRouteName);
}
Future<String> run(String route) async {
print("Flutter---" + route);
switch (_getPageName(route)) {
case 'address':
String param = _getPageParamJsonStr(route);
AddressParamEntity entity =
AddressParamEntity.fromJson(json.decode(param));
runApp(AddressPage1(
entity: entity,
));
break;
case 'ReceiveCoupon':
runApp(CouponPage(token: _getPageParamJsonStr(route)));
break;
case 'MyCoupon':
runApp(MyCouponPage(token: _getPageParamJsonStr(route)));
break;
default:
break;
}
return '';
}
String _getPageName(String s) {
if (s.indexOf("?") == -1) {
return s;
} else {
return s.substring(0, s.indexOf("?"));
}
}
String _getPageParamJsonStr(String s) {
if (s.indexOf("?") == -1) {
return "";
} else {
return s.substring(s.indexOf("?") + 1);
}
}
int getAddressId(String s) {
return int.parse(s.substring(0, s.indexOf(",")));
}
String getAddressToken(String s) {
return s.substring(s.indexOf(",") + 1);
}
这样能使Native跳转到Flutter相应的页面,并传递参数。
并且可以使用消息传递机制做一些交互:
static const platform = const MethodChannel('com.novasoftware.ShoppingMall.address');
Future<Null> back() async {
try {
await platform.invokeMethod('back');
} on PlatformException catch (e) {
}
}
var flutterVC = BaseFlutterViewController()
let handler : FlutterMethodCallHandler = { (call: FlutterMethodCall, result: FlutterResult) in
print(call.method)
if call.method == "back" {
flutterVC.dismiss(animated: true, completion: nil)
}
}
let paramStr = "ReceiveCoupon?" + (SM_token ?? "")
flutterVC = HXHelper.createFluttervcWithHandler(paramStr: paramStr, handler: handler)
self.present(flutterVC, animated: true, completion: nil)
这样测试了一段时间,本以为就此搞定。没想到后面页面出现乱码,视图出现马赛克,等等一些问题。
可以确定,是Flutter内存泄漏导致的,这样做还是有个问题,同一个页面关闭后内存并没有释放,只是第二次打开这个页面的时候,不会重新申请内存新建页面,也就是同一个Flutter页面不会多次创建,但是也不会释放。比如,第二次打开这个页面,上次在输入框输入的文本并没有清空,还显示在那里,而且,连光标都没有释放!
这种办法行不通,另外寻求解决办法。
后面发现阿里巴巴的闲鱼团队在使用Flutter框架,并且有了很多成功的案例。
最重要的,他们还开源了一套Flutter Native混合开发的框架Flutter Boost!
集成Flutter Boost
在对应的pubspec.yaml文件中加入依赖
flutter_boost: ^0.0.415
之后调用:
flutter packages get
执行:
flutter build ios
在iOS的根目录下执行:
pod install
使iOS和flutter都添加FlutterBoost插件。
- Dart 代码集成
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterBoost.singleton.registerPageBuilders({
'Address': (pageName, params, _) {
print(params);
AddressParamEntity entity =
AddressParamEntity.fromJson(json.decode(params["object"]));
return AddressPage1(
entity: entity
);
}, // 页面1
'ReceiveCoupon': (pageName, params, _) {
print(params);
return CouponPage(
token: params["token"]
);
}, // 页面2
'MyCoupon': (pageName, params, _) {
print(params);
return MyCouponPage(
token: params["token"]
);
}// 页面3
);
FlutterBoost.handleOnStartPage();
}
Map<String, WidgetBuilder> routes = {
"second": (BuildContext context) =>
MyCoupon(token: "")
};
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed),
routes: routes,
home: Container());
}
void _onRoutePushed(
String pageName, String uniqueId, Map params, Route route, Future _) {
}
}
-
iOS代码集成
需要 将libc++ 加入 "Linked Frameworks and Libraries"
这个主要是项目的General 的Linked Frameworks and Libraries 栏下,点击加号(+)搜索libc++,找到libc++.tbd即可。
修改AppDelegate.swift
import UIKit
import CoreData
import Flutter
import FlutterPluginRegistrant
import flutter_boost
@UIApplicationMain
class AppDelegate: FLBFlutterAppDelegate {
var flutterEngine : FlutterEngine?
var messageChannel : FlutterBasicMessageChannel?
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.setupIQKeyBoardManager()
if (HX_Defaults_Standard.string(forKey: DEFAULT_TOKEN) == nil) {
let login = R.storyboard.login().instantiateInitialViewController()
self.window?.rootViewController = login
} else {
let tabbar = BaseTabBarController()
self.window?.rootViewController = tabbar
}
// ------------------------
let router = HXFlutterRouter.sharedRouter
router.navigationController = self.window?.rootViewController?.navigationController
// 初始化FlutterBoost,也可以在其他地方做初始化
FlutterBoostPlugin.sharedInstance()?.startFlutter(with: router, onStart: { (flutterVC) in
// 或许这里需要些什么代码,让Flutter能跳转Native页面,还需要研究
})
// ------------------------
self.window?.backgroundColor = UIColor.white
self.window?.makeKeyAndVisible()
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
....
}
实现FLBPlatform协议
import UIKit
class HXFlutterRouter : NSObject, FLBPlatform{
var navigationController: UINavigationController?
static let sharedRouter = HXFlutterRouter()
let accessibilityEnable = true
func openPage(_ name: String, params: [AnyHashable : Any], animated: Bool, completion: @escaping (Bool) -> Void) {
if let present = params["present"] as? Bool {
if present {
let vc = FLBFlutterViewContainer()
vc.setName(name, params: params)
navigationController?.present(vc, animated: animated, completion: {
completion(true)
})
return
}
}
let vc = FLBFlutterViewContainer()
vc.setName(name, params: params)
navigationController?.pushViewController(vc, animated: animated)
completion(true)
}
func closePage(_ uid: String, animated: Bool, params: [AnyHashable : Any], completion: @escaping (Bool) -> Void) {
if let vc = navigationController?.presentedViewController as? FLBFlutterViewContainer {
if vc.isKind(of: FLBFlutterViewContainer.self) && vc.uniqueIDString == uid {
vc.dismiss(animated: animated) { }
return
}
}
navigationController?.popViewController(animated: animated)
}
func flutterCanPop(_ canpop: Bool) {
navigationController?.interactivePopGestureRecognizer?.isEnabled = canpop
}
}
其中的openPage 方法会接收来至flutter-->native以及native-->flutter的页面跳转,可以根据需求书写。
Native 跳转 Dart
let paramsTem : [String : Any] = ["id": self.entity.OrderId,
"orderNumber": self.entity.CustomOrderNumber ?? "",
"token": SM_token as Any]
//根据与Dart代码约定的参数传递方式,转成json字符串,当然也可以约定直接用Dictionary。
let params = ["object": HXHelper.convertDictionaryToString(dict: paramsTem)]
HXFlutterRouter.sharedRouter.navigationController = self.viewContainingController()!.navigationController
let flutterVC = FLBFlutterViewContainer()
flutterVC.setName("AfterSellPage", params: params)
self.viewContainingController()!.present(flutterVC, animated: true, completion: nil)
Dart 与 Native 交互
同样可以使用消息传递机制使Dart与Native交互
static const platform = const MethodChannel('com.novasoftware.ShoppingMall.address');
Future<Null> use() async {
try {
// Dart 传递“Use”消息给 Native
await platform.invokeMethod('use');
} on PlatformException catch (e) {
}
}
HXFlutterRouter.sharedRouter.navigationController = self.navigationController
let flutterVC = FLBFlutterViewContainer()
flutterVC.setName("MyCoupon", params: ["token": SM_token as Any])
if let flutterVc = FlutterBoostPlugin.sharedInstance()?.currentViewController() {
let channelName = "com.novasoftware.ShoppingMall.address"
let channel = FlutterMethodChannel(name: channelName, binaryMessenger:flutterVc)
channel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: FlutterResult) in
print(call.method)
if call.method == "use" {
// Native接收消息
flutterVC.dismiss(animated: true, completion: {
self?.tabBarController?.selectedIndex = 0
})
}
}
}
self.present(flutterVC, animated: true, completion: nil)
问题:
- 打开一个dart页面后,侧滑返回手势失效
打开一个dart页面返回后。发现侧滑返回手势失效了。
打断点调试,发现打push一个Dart页面后,
interactivePopGestureRecognizer?.isEnabled == false.
应该是flutter boost打开一个Dart页面后,将其设置未false了。检测发现FLBPlatform协议有控制方法。
实现该方法,返回true即可。
funcflutterCanPop(_canpop:Bool) {
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
总结:
到此Flutter Boost初步集成。中间踩了太多坑,当然还有很多坑还没跳出来。
目前使用发现还是有内存泄露,不过已经比直接使用好用很多。
还是非常感谢闲鱼团队。
待解决的问题:
- Dart 跳转Native 页面。 爬文档并没找到Dart是怎么跳转iOS 原生页面代码的,不过安卓是能实现这个功能的。
- 老项目集成Flutter后,每次编译都非常慢,大概需要花费两三分钟那样子,应该是每次编译都要去跑一遍flutter代码导致的。
- Flutter Boost 还有一点内存泄露问题。