Flutter混编(三)

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 
#import 

@interface AppDelegate : FlutterAppDelegate // Flutter 模块需要 hook 比如进程什么周期的方法
@end
#import  // 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 
#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();

混编模式下的热重载

$ 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 _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)
        }
    }
}

你可能感兴趣的:(Flutter混编(三))