JHHK

欢迎来到我的个人网站
行者常至 为者常成

flutter与原生应用⭐️

目录

flutter页面的加载

以DF应用为例

+ (instancetype)getFlutterViewControllerWithEntryPoint:(NSString *)entryPoint params:(NSDictionary *)params {
    // 初始化一个新的 FlutterEngine,并为其分配一个唯一的名称。
    // 通过这种方式,可以创建多个 Flutter 引擎实例,并在应用中根据需要进行区分。
    // 这个方法主要用于在 iOS 应用中,创建和管理多个 Flutter 引擎的场景。
    FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:entryPoint];
    
    // 启动 FlutterEngine 并设置引擎的入口点和初始路由的核心方法。
    // 通过这个方法,你可以灵活地控制 Flutter 引擎的启动行为,指定从哪个 Dart 函数开始执行以及一开始显示哪个页面。
    // entrypoint:指定 Flutter 引擎启动时需要调用的入口点函数名,默认为main。
    // initialRoute:指定 Flutter 引擎启动时的初始路由,默认使用 / 作为初始路由。
    [flutterEngine runWithEntrypoint:@"main" initialRoute:[NSString stringWithFormat:@"/%@",entryPoint]];
    
    // FlutterViewController视图控制器,负责在原生视图中呈现 Flutter 的 UI。
    // 它充当了 Flutter 与 iOS 原生界面之间的桥梁。
    //绑定engin是因为需要通过engin加载 Dart 代码、渲染Flutter 界面、处理Flutter与原生的消息通信
    FZFlutterViewController *viewController = [[FZFlutterViewController alloc] initWithEngine:flutterEngine 
                                                                               entryPoint:entryPoint 
                                                                               nibName:nil 
                                                                               bundle:nil];
    
    // 注册插件,在 iOS 中,插件是用来扩展 Flutter 框架功能的一种方式。
    // 通过注册插件,你可以让你的应用能够使用各种原生平台的特性,比如访问设备硬件、调用系统服务等。
    // 插件是对channel的包装,后面会介绍      
    [GeneratedPluginRegistrant registerWithRegistry:viewController];
    
    return viewController;
}

channel的调用流程

一、Flutter调用原生

Flutter代码如下:

// channel 初始化
_channel = MethodChannel('syncData');
 
 // 向原生传递方法名pop和参数param
 // result为调用的返回值(注意:调用原生方法时返回值是异步返回的,后面会说明原因)        
 int result await _channel?.invokeMethod('pop', param)
 
 // 还可以用下面的这种书写方式
 _channel.invokeMethod('pop', param).then((result) {
     // 处理返回值
 });

OC代码如下:


self.flutterInitChannel = [FlutterMethodChannel methodChannelWithName:@"syncData" binaryMessenger:self.engine.binaryMessenger];


// 调用后会来到原生注册的回调内
[self.flutterInitChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
    if (call.method.equals("pop")) {
        // 获取参数
        id param = call.arguments; 
        
        // 处理具体逻辑
        ....
        
        // 返回结果
        int isSuccess = 1;
        result.success(isSuccess); 
    } else {
        result.notImplemented();
    }
}];

在DF内使用了一个单例对象来处理来自Flutter的调用

[[FZFlutterChannelManager sharedInstance] dispatchChannel:call result:result context:strongSelf];


// flutterChannelDic存储的是FZFlutterChannel.plist文件内方法名与类的映射关系
// 通过 'pop' 找到 FZFlutterChannelPop 这个类,调用这个类的 【callWithChannel:result: context:]方法来处理这个调用   
- (void)dispatchChannel:(FlutterMethodCall *)call result:(FlutterResult)result context:(id)controller {
    NSString *className = [FZFlutterChannelManager sharedInstance].flutterChannelDic[call.method];
    if (className.length > 0) {
        Class class = NSClassFromString(className);
        if ([class conformsToProtocol:@protocol(FZFlutterChannelProtocol)]) {
            [class callWithChannel:call result:result context:controller];
        }
    } else {
        DDLogInfo(@"FlutterChannel 方法不存在");
    }
}

二、原生调用Flutter

原生调用flutter与上边类似

OC代码如下:

// 创建 MethodChannel
FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"com.example.flutter_channel"
                                                          binaryMessenger:self.flutterEngine.binaryMessenger];

// 调用 Flutter 端的方法
[channel invokeMethod:@"getGreeting" arguments:param result:^(id  _Nullable result) {
    // result是返回值
    if ([result isKindOfClass:[NSString class]]) {
        NSLog(@"Received greeting from Flutter: %@", result);
    } else {
        NSLog(@"Failed to get greeting from Flutter");
    }
}];

Flutter代码如下:

// 创建 MethodChannel
const MethodChannel channel = MethodChannel('com.example.flutter_channel');

// 注册 Flutter 端方法
channel.setMethodCallHandler((MethodCall call) async {
  if (call.method == 'getGreeting') {
    // 返回给原生端的响应
    return 'Hello from Flutter!';
  }
  return null;
});

三、关于channel的一些说明

一个 FlutterViewController 容器 绑定一个 FlutterEngine 它俩是一一对应的。
channel是FlutterEngine的一部分,在FlutterEngine内我们可以有很多个channel,他们有不同的名字。

const MethodChannel channel = MethodChannel('syncData');
const MethodChannel channel = MethodChannel('test');

但需要注意的是

// channel1 和 channel2 是不同的channel实例对象
// 但他们指向的是同一个通道,因为他们的名字一样
const MethodChannel channel1 = MethodChannel('syncData');
const MethodChannel channel2 = MethodChannel('syncData');

// 因为他们指向的是同一个channel,我们在设置方法回调时会发生覆盖
// channel2后设置的方法回调,他会覆盖掉channel1的回调
channel1.setMethodCallHandler((MethodCall call) async {

});

channel2.setMethodCallHandler((MethodCall call) async {

});

另外还有一点特别重要:
不同的FlutterViewController容器对应不同的FlutterEngine,不同的FlutterEngin是无法简单通讯的(isolate可以实现)。
我们在FlutterViewControllerA中通过名字叫syncData的channel调用时,它不会来到FlutterViewControllerB中名字叫syncData的channel的回调方法中。

四、总结

flutter与原生平台的交互依赖于channel而channel属于引擎的一部分。
所以引擎不仅负责了dart的代码的执行,UI的渲染,也负责了flutter与原生的数据交互。
xy:flutter与原生的数据交互是通过通用的二进制来完成的,那么二进制需要转化为对应语言支持的数据类型,在OC端是通过信使binaryMessenger完成的,这个信使就是flutterViewController

Plugin介绍

Plugin的本质是对channel的包装

Plugin注册就是完成channel与信使与当前处理类的绑定。下面代码是插件的注册。

[GeneratedPluginRegistrant registerWithRegistry:viewController];

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
  [AppSettingsPlugin registerWithRegistrar:[registry registrarForPlugin:@"AppSettingsPlugin"]];
  [AudioSessionPlugin registerWithRegistrar:[registry registrarForPlugin:@"AudioSessionPlugin"]];
  [ConnectivityPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"ConnectivityPlusPlugin"]];
  
  
  // [registry registrarForPlugin:@"FLTDeviceInfoPlusPlugin"]生成一个注册员对象
  // 注册员有两个信息,一个是registry(注册表)就是vc,一个是处理调用逻辑的类名FLTDeviceInfoPlusPlugin
  // vc是信使,在channel初始化时有用,类名猜测应该是做校验使用的    
  [FLTDeviceInfoPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTDeviceInfoPlusPlugin"]];
  
  
  [InAppWebViewFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"InAppWebViewFlutterPlugin"]];
  [ImageGallerySaverPlugin registerWithRegistrar:[registry registrarForPlugin:@"ImageGallerySaverPlugin"]];
  [JustAudioPlugin registerWithRegistrar:[registry registrarForPlugin:@"JustAudioPlugin"]];
  [NotificationPermissionsPlugin registerWithRegistrar:[registry registrarForPlugin:@"NotificationPermissionsPlugin"]];
  [PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]];
  [PermissionHandlerPlugin registerWithRegistrar:[registry registrarForPlugin:@"PermissionHandlerPlugin"]];
  [SensorsAnalyticsFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"SensorsAnalyticsFlutterPlugin"]];
  [SharedPreferencesPlugin registerWithRegistrar:[registry registrarForPlugin:@"SharedPreferencesPlugin"]];
  [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]];
  [FLTURLLauncherPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTURLLauncherPlugin"]];
  [FVPVideoPlayerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FVPVideoPlayerPlugin"]];
  [WakelockPlugin registerWithRegistrar:[registry registrarForPlugin:@"WakelockPlugin"]];
  [FLTWebViewFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTWebViewFlutterPlugin"]];
}

我们以 FLTDeviceInfoPlusPlugin 来分析下,我们在flutter项目目录下找到plugin的文件

实际的目录是:/Users/lxy/.pub-cache/hosted/pub.flutter-io.cn/device_info_plus-3.2.4/ios/Classes/FLTDeviceInfoPlusPlugin.h

源码如下

// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "FLTDeviceInfoPlusPlugin.h"
#import <sys/utsname.h>

@implementation FLTDeviceInfoPlusPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {

  // 初始化一个chanel,并指定信使
  FlutterMethodChannel *channel = [FlutterMethodChannel
      methodChannelWithName:@"dev.fluttercommunity.plus/device_info"
            binaryMessenger:[registrar messenger]];
            
  // 初始化处理调用逻辑的实例对象          
  FLTDeviceInfoPlusPlugin *instance = [[FLTDeviceInfoPlusPlugin alloc] init];
  
  // xy:将instance与channel做关联,
  // xy:方法调用时将处理逻辑转移到instance的handleMethodCall:result:这里进行处理
  // xy:这里没有看到registrar注册员生成时传入的@"FLTDeviceInfoPlusPlugin"有什么作用,猜测是对instance做互相校验使用的。    
  [registrar addMethodCallDelegate:instance channel:channel];
}


// 处理flutter的调用逻辑,并返回调用结果    
- (void)handleMethodCall:(FlutterMethodCall *)call
                  result:(FlutterResult)result {
  if ([@"getIosDeviceInfo" isEqualToString:call.method]) {
    UIDevice *device = [UIDevice currentDevice];
    struct utsname un;
    uname(&un);

    result(@{
      @"name" : [device name],
      @"systemName" : [device systemName],
      @"systemVersion" : [device systemVersion],
      @"model" : [device model],
      @"localizedModel" : [device localizedModel],
      @"identifierForVendor" : [[device identifierForVendor] UUIDString]
          ?: [NSNull null],
      @"isPhysicalDevice" : [self isDevicePhysical],
      @"utsname" : @{
        @"sysname" : @(un.sysname),
        @"nodename" : @(un.nodename),
        @"release" : @(un.release),
        @"version" : @(un.version),
        @"machine" : @(un.machine),
      }
    });
  } else {
    result(FlutterMethodNotImplemented);
  }
}

// return value is false if code is run on a simulator
- (NSString *)isDevicePhysical {
#if TARGET_OS_SIMULATOR
  NSString *isPhysicalDevice = @"false";
#else
  NSString *isPhysicalDevice = @"true";
#endif

  return isPhysicalDevice;
}

@end

鸿蒙与flutter的相互调用

一、flutter 中的UI是如何呈现出来的

二、flutter 通过channel 调用原生

以DF为例,自定义了一个继承自MethodChannel的channel,将处理逻辑封装在了类内部。

// 注意继承关系
export default class SyncDataChannel extends MethodChannel implements MethodCallHandler {

  constructor(context: Context, messenger: BinaryMessenger, name: string,
    codec: MethodCodec = StandardMethodCodec.INSTANCE, taskQueue?: TaskQueue) {
    super(messenger, name, codec);
    this.mContext = context;
    
    // 指定handler来处理调用逻辑
    this.setMethodCallHandler(this);
  }

    // flutter调用后会来到原生的这个方法
    onMethodCall(call: MethodCall, result: MethodResult): void {
        let method: string = call.method;
        switch (method) {
            case "pop":
                // 处理具体逻辑
                DfRouter.pop()
                break;
        }
    }
}

使用

    // 初始化channel需要传入的信使   
    let messager = delegate?.getFlutterEngine()?.dartExecutor.getBinaryMessenger()
    
    // 初始化一个名称为 "syncData" 的channel
    // 只要这个channel实例不释放,flutter调用原生时就会来到SyncDataChannel内的onMethodCall方法    
    this.mSyncDataChannel = new SyncDataChannel(getContext(), messager, "syncData");

如果我们不创建SyncDataChannel我们也可以在任何地方使用

    private initChannel(delegate: FlutterAbilityAndEntryDelegate | null | undefined){
        let messager = delegate?.getFlutterEngine()?.dartExecutor.getBinaryMessenger()
        if (messager) {
          this.mSyncDataChannel = new SyncDataChannel(getContext(), messager, "syncData");
          this.mSyncDataChannel.setMethodCallHandler(this);
        }
    }


    onMethodCall(call: MethodCall, result: MethodResult): void {
        let method: string = call.method;
        switch (method) {
            case "pop":
                // 处理具体逻辑
                DfRouter.pop()
                break;
        }
    } 

三、plugin 是如何实现的

可以参考下帮安装页面中的插件是如何实现的


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.