前言

Flutter中已经具体提到了Flutter与iOS,Android的通信原理。Flutter通信是靠MethodChannel进行通信的。

假设Flutter需要一个第三方的授权登录,而第三方目前没有支持到Flutter的版本,只支持Android,iOS版本,这个时候就需要自己开发Flutter与原生的通信插件。

通信插件的开发过程

1、创建Flutter_Plugin项目

新建flutter_plugin工程,选择工程类型,选项语言类型,并点击finish完成工程的创建。

flutter swift 通信 flutter 通信机制_android

2、Flutter_Plugin目录介绍

Android 就是我们开发安卓部分的位置

iOS 就是我们开发 iOS 的位置

lib 是与 Android 、iOS 联调的位置。也可以理解为Flutter 实现通信的位置

example 是测试的位置,当我们写完插件 可以直接运行 插件,example 可以理解为一个Flutter项目,只不过这个项目只给你写的插件服务  。

 

flutter swift 通信 flutter 通信机制_objective-c_02

Flutter部分开发

1、添加原生、Flutter交互渠道

我们打开插件,找到lib ,在lib下面会有一个文件 test_flutter_plugin_demo.dart,在这个基础上我们进行扩展,更加灵活

import 'dart:async';

import 'package:flutter/services.dart';

typedef void TestViewCreatedCallback(TestFlutterPluginDemo controller);
class TestFlutterPluginDemo {

  late MethodChannel _channel;
  TestFlutterPluginDemo.init(int id){
    // 原生与Flutter 交互渠道
    _channel = new MethodChannel('test_flutter_plugin_demo');
    _channel.setMethodCallHandler(platformCallHandler);///设置原生参数监听
  }

  ///Flutter 调用原生
  ///这里我传了一个 字符串 当然也可以传Map
  Future<List<dynamic>?> changeNativeTitle(String str) async{
    return _channel.invokeListMethod('changeNativeTitle',str);
  }

  ///实现监听原生方法回调
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "clickAciton":
        print('收到原生回调 ---- $call.arguments');
        return ;
        break;
    }
  }
}

这样就实现了 原生与Flutter之间的交互,等后续iOS,Android端代码完成就可以进行两端调试。

2、Flutter界面开发以及调用

在example/lib下创建view.dart 文件,并在example/lib/main.dart中引入视图view.dart并调用。

view.dart全部代码

import 'package:flutter/cupertino.dart';
import 'package:test_flutter_plugin_demo/test_flutter_plugin_demo.dart';
import 'package:flutter/services.dart';
import 'dart:io';

/// @Author wywinstonwy
/// @Date 2022/4/15 10:58 上午
/// @Description: 

///我是使用的 StatefulWidget 使用StatelessWidget 也是一样
class TestView extends StatefulWidget {
  ///根据自己的需求创建初始化参数
  final TestViewCreatedCallback ? onCreated; ///是上面创建的回调
  final String ? titleStr;

  TestView({
    required Key key,
    this.onCreated,
    this.titleStr,
  });

  @override
  _TestViewState createState() => _TestViewState();
}

class _TestViewState extends State<TestView> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: _loadNativeView(),
    );
  }
  ///加载原生视图
  Widget _loadNativeView(){
    ///根据不同的平台显示相应的视图
    if(Platform.isAndroid){ ///加载安卓原生视图
      return AndroidView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else if(Platform.isIOS){///加载iOS原生视图
      return UiKitView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else{
      return Text('这个平台老子不支持');
    }
  }
  ///这个基本上是固定写法 
  Future<void> onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
    }
    widget.onCreated!(new TestFlutterPluginDemo.init(id));
  }
}

example/lib/main.dart中调用view.dart

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:test_flutter_plugin_demo/test_flutter_plugin_demo.dart';
import 'package:test_flutter_plugin_demo_example/view.dart';
void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testFlutterPluginDemo;

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion ='是是是';

    // try {
    //   platformVersion =
    //       await TestFlutterPluginDemo.platformVersion ?? 'Unknown platform version';
    // } on PlatformException {
    //   platformVersion = 'Failed to get platform version.';
    // }


    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    ///初始化 测试视图的类
    TestView testView =  TestView(
      onCreated: onTestViewCreated, key: ValueKey('testView'),
      titleStr: 'flutter给原生的参数',
    );
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(children: [
          Container(height: 200,width: 400,child: testView,),
          FloatingActionButton(onPressed: onNativeMethon)
        ],),
      ),
    );
  }
  void onNativeMethon(){
    this.testFlutterPluginDemo.changeNativeTitle('Flutter 调用原生成功了');
  }
  void onTestViewCreated(testFlutterPluginDemo){
    this.testFlutterPluginDemo = testFlutterPluginDemo;
  }
}

4、iOS、Android介绍

1、iOS部分代码开发

iOS 找到 ios 目录,选择Reveal in Finder,因为现在这个ios 部分还没有pod install,我们这要先进行pod install,成功后直接打开项目即可,效果如下

flutter swift 通信 flutter 通信机制_objective-c_03

  在这里我们找到TestFlutterPluginDemoPlugin,这个类隐藏的很深,他是Flutter 与原生交互的核心,在这了我们可以接收到Flutter的内容。

在此目录下分别创建TestFlutterPluginViewTestFlutterPluginViewFactory

//
//  TestFlutterPluginView.h
//  Pods
//
//  Created by wangyun on 2022/4/15.
//

#import <Foundation/Foundation.h>
#include <Flutter/Flutter.h>

@interface TestFlutterPluginView : NSObject<FlutterPlatformView>
- (id)initWithFrame:(CGRect)frame
             viewId:(int64_t)viewId
               args:(id)args
           messager:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
//
//  TestFlutterPluginView.m
//  Pods
//
//  Created by wangyun on 2022/4/15.
//

#import "TestFlutterPluginView.h"

@interface TestFlutterPluginView ()
/** channel*/
@property (nonatomic, strong)  FlutterMethodChannel  *channel;
@property (nonatomic, strong)  UIButton  *button;
@property (nonatomic, strong)  UITextField  *textField;
@property (nonatomic, strong)  UILabel  *lblText;
@property (nonatomic, assign)  NSInteger  count;
@end

@implementation TestFlutterPluginView
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
   
}

- (id)initWithFrame:(CGRect)frame
  viewId:(int64_t)viewId
    args:(id)args
messager:(NSObject<FlutterBinaryMessenger>*)messenger
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
        NSLog(@"%@",args[@"titleStr"]);
        
        
        ///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
        _channel = [FlutterMethodChannel methodChannelWithName:@"test_flutter_plugin_demo" binaryMessenger:messenger];
        
        __weak __typeof__(self) weakSelf = self;

        [_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            [weakSelf onMethodCall:call result:result];
        }];
       
    }
    return self;
}

- (UIView *)view{
    UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
        nativeView.backgroundColor = [UIColor redColor];
        
    _button = [UIButton buttonWithType:UIButtonTypeSystem];
    [_button setTitle:@"我是按钮" forState:UIControlStateNormal];
    [_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_button setBackgroundColor:[UIColor blueColor]];
    _button.frame = CGRectMake(10, 20, 100, 44);
    [nativeView addSubview:_button];
    
    self.lblText = [[UILabel alloc] initWithFrame:CGRectMake(140, 20, 200, 44)];
    self.lblText.text =_args[@"titleStr"];
    self.lblText.backgroundColor =[UIColor blueColor];
    self.lblText.textColor =[UIColor whiteColor];
    [nativeView addSubview:self.lblText];
    
    [_button addTarget:self action:@selector(flutterMethod) forControlEvents:UIControlEventTouchUpInside];
    
    _textField = [[UITextField alloc] initWithFrame:CGRectMake(100, 125, 200, 44)];
    [_textField setText: [NSString stringWithFormat:@"这个数据是原生控制 %d",_count] ];
    
    
    [nativeView addSubview:_textField];
    return nativeView;
     
}
#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    //监听Fluter
    if ([[call method] isEqualToString:@"changeNativeTitle"]) {
        [_button setTitle:call.arguments forState:UIControlStateNormal];
    }
    
}
//调用Flutter
- (void)flutterMethod{
    self.count = self.count+1;
    NSString *str = [NSString stringWithFormat:@"原生给flutter的参数 %ld",(long)self.count];
    [self.channel invokeMethod:@"clickAciton" arguments:str];
    [_textField setText: [NSString stringWithFormat:@"这个数据是原生控制 %ld",(long)_count] ];
    if(_count%2==0){
        self.lblText.backgroundColor =[UIColor blueColor];
        self.lblText.textColor =[UIColor whiteColor];

    }else{
        self.lblText.backgroundColor =[UIColor orangeColor];
        self.lblText.textColor =[UIColor blackColor];

    }

}
@end
//
//  TestFlutterPluginViewFactory.h
//  Pods
//
//  Created by wangyun on 2022/4/15.
//

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN

@interface TestFlutterPluginViewFactory : NSObject<FlutterPlatformViewFactory>
/// 重写一个构造方法 来接收 Flutter 相关蚕食
/// @param messenger Flutter类 包含回调方法等信息
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
@end

NS_ASSUME_NONNULL_END
//
//  TestFlutterPluginViewFactory.m
//  Pods
//
//  Created by wangyun on 2022/4/15.
//

#import "TestFlutterPluginViewFactory.h"
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginViewFactory ()

@property(nonatomic)NSObject<FlutterBinaryMessenger>* messenger;

@end

@implementation TestFlutterPluginViewFactory

- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
    self = [super init];
    if (self) {
        self.messenger = messenger;
    }
    return self;
}

#pragma mark -- 实现FlutterPlatformViewFactory 的代理方法
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
    return [FlutterStandardMessageCodec sharedInstance];
}

/// FlutterPlatformViewFactory 代理方法 返回过去一个类来布局 原生视图
/// @param frame frame
/// @param viewId view的id
/// @param args 初始化的参数
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
    
    TestFlutterPluginView *testFlutterPluginView = [[TestFlutterPluginView alloc] initWithFrame:frame viewId:viewId args:args messager:self.messenger];
    return testFlutterPluginView;
    
}

@end

2、Android 部分代码开发

Android 这部分和iOS 是同一个道理,没有丝毫区别,Android 我们也右键在工具中打开,然后如下图找到位置,Android 所有的代码都在这里进行

flutter swift 通信 flutter 通信机制_android_04


Android TestFlutterPluginView全部代码


package com.example.test_flutter_plugin_demo;

import android.content.Context;
import android.graphics.Color;
import android.graphics.SurfaceTexture;
import android.provider.CalendarContract;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.platform.PlatformView;

/**
 * Created by sunyd on 1/25/22
 */
public class TestFlutterPluginView extends TextView implements PlatformView, MethodChannel.MethodCallHandler, TextureView.SurfaceTextureListener{
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

    public  Context context;
    /**
     * 通道
     */
    private  MethodChannel methodChannel = null;

    public TestFlutterPluginView(Context context, int viewId, Object args, BinaryMessenger messenger) {
        super(context);
        this.context = context;
        Toast.makeText(context, "创建关联成功", Toast.LENGTH_SHORT).show();
        setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        setBackgroundColor(Color.argb(255,79,79,79));  //0完全透明  255不透明
        //注册
        methodChannel = new MethodChannel(messenger, "test_flutter_plugin_demo");
        methodChannel.setMethodCallHandler(this);
    }

    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        handleCall(call, result);
    }
    private void handleCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {
            //开始预览
            case "changeNativeTitle":
                Toast.makeText(context, (String)methodCall.arguments, Toast.LENGTH_SHORT).show();
                break;
            default:
        }
    }

    @Override
    public View getView() {
        return this;
    }

    @Override
    public void dispose() {

    }
}


Android TestFlutterPluginViewFactory全部代码


package com.example.test_flutter_plugin_demo;
import android.content.Context;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;


public class TestFlutterPluginViewFactory extends PlatformViewFactory {
    private BinaryMessenger messenger = null;
    public TestFlutterPluginViewFactory(BinaryMessenger messenger) {
        super(StandardMessageCodec.INSTANCE);
        this.messenger = messenger;
    }

    /**
     * @param createArgsCodec the codec used to decode the args parameter of {@link #create}.
     */
    public TestFlutterPluginViewFactory(MessageCodec<Object> createArgsCodec) {
        super(createArgsCodec);
    }

    @Override
    public PlatformView create(Context context, int viewId, Object args) {
        return new TestFlutterPluginView(context, viewId, args, this.messenger);
    }
}

到此,插件的开发就算是完事了。实现的效果(iOS,Android)如下:

flutter swift 通信 flutter 通信机制_flutter_05

flutter swift 通信 flutter 通信机制_ios_06

下面就是使用这个插件了,我们如何集成到 别的项目里,在这里 我们只介绍 本地 使用

其实本地使用非常简单。

1、打开我们的项目

 2、打开pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
 
  test_flutter_plugin_demo:
    path: /Users/yunwang/Documents/flutterStudy/flutter_plugin_demo

 flutter_plugin_demo 位插件的名称,就是我们创建插件时候的文件名称,path就是路径了,我们找到插件位置 将路径 粘贴到这里即可

3、pub get

到此就引用完成了。

4、使用我们就和example 里面一摸一样就可以了。

源码demo地址:test_flutter_plugin_demo: flutter插件开发,Android,iOS端通信。