美文网首页
Flutter混编(三)

Flutter混编(三)

作者: 念_夕 | 来源:发表于2019-01-11 19:49 被阅读43次

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

相关文章

网友评论

      本文标题:Flutter混编(三)

      本文链接:https://www.haomeiwen.com/subject/yqgarqtx.html