Flutter 混编-iOS
配置
一、 cd APP/ (进入workspace所在目录)
二、 flutter create -t module my_flutter (my_flutter为flutter模块文件夹名,可自行定义,在后边配置flutter路径的时候需要用到)
三、 在 podfile 中添加如下代码,将 flutter 模块引入到工程
flutter_application_path = 'path/to/flutter_app/'
eval(File.read(File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')), binding)
注意:正确填写路径
番外:
1、podfile 由 Ruby 编写。
2、上述代码的作用,是在当前上下文中,执行引入文件中的 Ruby 代码
3、引入的 Flutter 模块先的 podhelper.rb 文件,在
每次 执行 flutter packages get 之后,都会被重写。
4、podhelper.rb 文件文件中包含如下代码,如果在当前 iOS 工程 podfile 中包含 post_install 会导致 duplicate 错误,由于上述第三条原因,最佳的解决方案,是将 podhelper.rb 文件相关核心代码合并到当前文件。
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
xcconfig_path = config.base_configuration_reference.real_path
File.open(xcconfig_path, 'a+') do |file|
file.puts "#include \"#{File.realpath(File.join(framework_dir, 'Generated.xcconfig'))}\""
end
end
end
end
四、完成上述配置后,执行 pod install
注意:每次修改完 APP/my_flutter/pubspec.yaml 文件后
需要执行 flutter packages get ,Flutter 模块会更新依赖的模块,
这个时候你需要重新执行 pod install 来同步 Flutter 的更改。
五、为 Dart code 添加 build phase
TARGET->Build Phases->左上“+”->New Run Script Phase
添加如下脚本
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed
具体位置如下图示
六、command + B build工程,完成后,Flutter 和 iOS工程混编的基础配置就完成了。
七、如果混编工程中,Flutter 模块用的了其他 plugin ,需要在添加如下代码
Objective-C
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate // Flutter 模块需要 hook 比如进程什么周期的方法
@end
#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 {
[GeneratedPluginRegistrant registerWithRegistry:self];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
Swift
import UIKit
import Flutter
import FlutterPluginRegistrant // Only if you have Flutter Plugins.
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
// Only if you have Flutter plugins.
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
GeneratedPluginRegistrant.register(with: self);
return super.application(application, didFinishLaunchingWithOptions: launchOptions);
}
}
八、示例
完成以上配置之后你就可以实现 iOS 和 Flutter 的混编了。
Objective-C
#import <Flutter/Flutter.h>
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:@selector(handleButtonAction)
forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"Press me" forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor blueColor]];
button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
[self.view addSubview:button];
}
- (void)handleButtonAction {
FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
[self presentViewController:flutterViewController animated:false completion:nil];
}
@end
Swift
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 flutterViewController = FlutterViewController()
self.present(flutterViewController, animated: false, completion: nil)
}
}
FlutterViewController
,是 Flutter 模块的容器,同时也是一个完整的 UIController ,你可以在项目中任意组合,需要注意的是,用 Flutter 展示不同页面的时候,需要配置好路由。
Objective-C
[flutterViewController setInitialRoute:@"route1"];
Swift
flutterViewController.setInitialRoute("route1")
Flutter 混编-Android
一、 cd APP/
二、 flutter create -t module my_flutter (my_flutter为flutter模块文件夹名,可自行定义,在后边配置flutter路径的时候需要用到)
三、在建立的Android工程的settings.gradle中加入以下代码:
setBinding(new Binding([gradle: this])) // new
evaluate(new File( // new
settingsDir.parentFile, // new
'my_flutter/.android/include_flutter.groovy' // new
)) // new
注意:settingsDir.parentFile表示当前目录的父级目录,
my_flutter是前面所建立的Flutter Module目录。
四、Sync now,会创建一个Flutter的library module
五、在Application Module的Build.gradle中依赖刚刚引入的library
implementation project(':flutter')
注意:如果由于 gradle 版本问题,导致某些方法找不到,请参考如下链接
[gradle 版本太低 issue]
(https://blog.csdn.net/qq_15653601/article/details/80236728)
六、至此,Android 和 Flutter 的混编配置,就完成了。
配置参考文档
七、 混编示例
在Android项目默认生成的MainActivity中,我们来展示一个页面,随后你可以在 main.dart 中修改页面布局。
flutterView = Flutter.createView(
MainActivity.this,
getLifecycle(),
"route"//传给 flutter 的参数,可用于打开指定页面
);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(500,800);
layout.leftMargin = 100;
layout.topMargin = 100;
addContentView(flutterView, layout);
FlutterView不是唯一的使用方式,还有一种通过FlutterFragment来调用Flutter的代码方式,如下代码所示
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fl_flutter_view, Flutter.createFragment("route"));
fragmentTransaction.commit();
<FrameLayout
android:id="@+id/fl_flutter_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
混编模式下的热重载
$ cd some/App/my_flutter
$ flutter attach
Waiting for a connection from Flutter on iPhone X...
执行上述命令后,打开安装在手机上的混编 APP ,进入到项目中的 Flutter 页面,即可与调试器建立连接,终端会输出如下信息
Done.
Syncing files to device iPhone X... 4.7s
🔥 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".
然后,你就可以编写 Dart 代码,使用热重载来调试 UI 了。
Flutter 与 Native 通信工具 —— Channel
关于 channel 的详细解析,请参考以下链接
闲鱼-深入理解Flutter Platform Channel
官方文档
闲鱼大佬对 Flutter Channel 做了深入剖析,我针对实战中的几个关键点做个简单总结。
Channel-Native 通信示意
Channel Name
一个Flutter应用中可能存在多个Channel,每个Channel在创建时必须指定一个独一无二的name,用来绑定对应的Channel,Flutter和Native通信的时候,根据其传递过来的channel name找到该Channel对应的Handler。
Channel 架构
BinaryMessenger是Native端与Flutter端通信的接口对象,在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。
Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。
Platform Channel是否线程安全
Platform Channel并非是线程安全的,这一点在官方的文档也有提及。Flutter Engine中多个组件是非线程安全的,故跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread。故我们在将Platform端的消息处理结果回传到Flutter端时,需要确保回调函数是在Platform Thread(也就是Android和iOS的主线程)中执行的。
是否支持大内存数据块的传递
Platform Channel实际上是支持大内存数据块的传递,当需要传递大内存数据块时,需要使用BasicMessageChannel以及BinaryCodec。而整个数据传递的过程中,唯一可能出现数据拷贝的位置为native二进制数据转化为Dart语言二进制数据。若二进制数据大于阈值时(目前阈值为1000byte)则不会拷贝数据,直接转化,否则拷贝一份再转化。
Channel 示例
官方示例核心代码分析
Dart部分
// 方法 Channel ,平台侧会立即收到通道同步的信息,可用作方法调用
static const MethodChannel methodChannel =
MethodChannel('samples.flutter.io/battery');
// 事件 Channel ,可类比为通知对象,可用于监听
static const EventChannel eventChannel =
EventChannel('samples.flutter.io/charging');
// Dart 方法,触发methodChannel想Native同步信息
Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await methodChannel.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level: $result%.';
} on PlatformException {
batteryLevel = 'Failed to get battery level.';
}
// 更新 UI
setState(() {
_batteryLevel = batteryLevel;
});
}
@override
void initState() {
super.initState();
// 注册 eventChannel
eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
}
// eventChannel回调
void _onEvent(Object event) {
setState(() { /*更新 UI*/ });
}
// eventChannel回调
void _onError(Object error) {
setState(() { /*更新 UI*/ });
}
Native(Swift)
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
// FlutterMethodChannel
let batteryChannel = FlutterMethodChannel(name: "samples.flutter.io/battery",
binaryMessenger: controller)
batteryChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getBatteryLevel" else {
// 给Flutter端的回调
result(FlutterMethodNotImplemented)
return
}
self.receiveBatteryLevel(result: result)
})
// FlutterEventChannel
let chargingChannel = FlutterEventChannel(name: ChannelName.charging,
binaryMessenger: binaryMessenger)
chargingChannel.setStreamHandler(self)
}
// 监听方法
public func onListen(withArguments arguments: Any?,
eventSink: @escaping FlutterEventSink) -> FlutterError? {
self.eventSink = eventSink
UIDevice.current.isBatteryMonitoringEnabled = true
sendBatteryStateEvent()
NotificationCenter.default.addObserver(
self,
selector: #selector(self.onBatteryStateDidChange),
name: NSNotification.Name.UIDeviceBatteryStateDidChange,
object: nil)
return nil
}
// 监听方法
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
NotificationCenter.default.removeObserver(self)
eventSink = nil
return nil
}
public func sendBatteryStateEvent() {
guard let eventSink = eventSink else {
return
}
// 发送通知给Flutter端
eventSink(BatteryState.charging)
}
}
}
网友评论