Flutter与原生通信

Posted Ever69

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter与原生通信相关的知识,希望对你有一定的参考价值。

—— Flutter作为一个跨平台框架,一经问世,便受到众多开发的追捧,发展至今相信已经有很多公司或个人将其加入自己的项目,进行混合开发,那么FLutter如何与原生通信呢?

—— 本次就以android为例,介绍Flutter如何与Android之间进行通信,调用Android代码。

目录

MethodChannel

Flutter端代码

Android端代码

Pigeon

添加Pigeon依赖

在Flutter端定义通信接口

执行Pigeon命令

Android端接入

IOS端接入

Flutter端使用

@async


 

MethodChannel


Flutter与原生之间通信是通过一个叫MethodChannel的东西来实现的,它的名字英文直译为方法频道,我更愿意叫它方法通道,这样更好理解,看这个名字我们大概就能猜出来它是怎么使用的,Flutter与原生是通过方法来进行通信的,有没有一种熟悉的感觉,这个东西类似原生与JS交互,双方需要先定义好方法、参数、返回值等信息,Flutter通过MethodChannel调用这些方法,传递事先定义好的参数,然后原生就可以拿到这些信息实现自己的逻辑处理,返回Flutter需要的数据。

MethodChannel的消息和响应以异步的形式进行传递,以确保用户界面能够保持响应。

Flutter 是通过 Dart 异步发送消息的。即便如此,当你调用一个平台方法时,也需要在主线程上做调用。

可以说MethodChannel是官方为Flutter与原生通信提供的一个‘通道’,它内部已经封装好了api供Flutter与原生使用,大大方便了我们使用Flutter混合开发。

本次我就拿官方的Demo来介绍它如何使用,这个Demo展示了Flutter如何通过原生来获取手机的电量。

我们像与JS交互一样,先定义一个可以供Flutter调用的方法,这个方法名字叫做getBatteryLevel,没有参数,返回一个int类型的值

Flutter端代码

首先创建一个MethodChannel对象,它接受一个String作为参数,String可以为任意字符串,这个String的作用是用来和原生中注册的MethodChannel进行匹配,只有双方的值一致时,这个通道才起作用。

static const platform = const MethodChannel('samples.flutter.dev/battery');

然后通过MethodChannel的invokeMethod()方法调用原生,这个方法接受两个参数,第一个参数为标识,也就是原生内边的方法名字,第二个参数就是需要向原生传递的参数,可以是一个map或者jsonString,我这没有需要向原生传递的参数,所以就没填。

Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = '您的手机电量还有 $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "手机电量获取失败: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

Android端代码

Fluttrt的代码写完后,我们接着实现原生端的代码,首先需要在原生注册MethodChannel,怎么注册呢?如果你用的Activity,那么就继承FlutterActivity,如果用的Fragment,那么就继承FlutterFragment,然后复写configureFlutterEngine方法,在其内部注册。

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/battery")
                .setMethodCallHandler { call, result ->
                    ...
                }
    }

注意,第二个参数的值要和Flutter中MethodChannel的值一致。

我们在Android实例化了一个MethodChannel对象,并调用了setMethodCallHandler方法进行注册。这个setMethodCallHandler方法需要实现一个接口参数,这个接口为MethodChannel.MethodCallHandler,我们看一下这个接口。


    public interface MethodCallHandler {        
        @UiThread
        void onMethodCall(@NonNull MethodCall call, @NonNull Result result);
    }

这个接口只有一个onMethodCall方法,并且有两个参数,MethodCall和Result,其实最终我们就是通过这两个参数来与Flutter进行通信。

MethodCall

public final class MethodCall {
    
    public final String method;

    
    public final Object arguments;

    /.../
}

MethodCall里有两个参数,method和arguments,看这名字也不用我多介绍了把,method就是Flutter需要调用的方法名字,arguments就是Flutter传递过来的参数。

MethodChannel.Result

public interface Result {
        /**
         * 处理成功的结果。
         */
        @UiThread
        void success(@Nullable Object result);

        /**
         * 处理失败的结果。
         */
        @UiThread
        void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);

        /**
         * 处理对未实现方法的调用。
         */
        @UiThread
        void notImplemented();
    }

Result,为一个接口,当Android处理完Flutter调用的方法逻辑后需要向Flutter回传一些数据时,就通过Result来实现。

加上获取电量代码

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "samples.flutter.dev/battery")
                .setMethodCallHandler { call, result ->
                    when (call.method) {
                        "getBatteryLevel" -> {
                            val batteryLevel = getBatteryLevel()
                            if (batteryLevel != -1) {
                                result.success(batteryLevel)
                            } else {
                                result.error("UNAVAILABLE", "Battery level not available.", null)
                            }
                        }
                    }
                }
    }

    private fun getBatteryLevel(): Int {
        val batteryLevel: Int
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        }

        return batteryLevel
    }

成果演示

ok,我们需要编写的代码就这么多,接着运行app看看通信效果如何吧。

nice~,通信达成。

Pigeon


Pigeon是什么?我们先看看官方是怎么介绍它的。

————

Pigeon:获得类型安全的通道

在之前的样例中,我们使用 MethodChannel 在 Flutter 和 原生 之间进行通信,然而这并不是类型安全的。为了正确通信,调用/接收消息取决于 Flutter 和 原生 声明相同的参数和数据类型。 Pigeon 包可以用作 MethodChannel 的替代品,它将生成以结构化类型安全方式发送消息的代码。

在 Pigeon 中,消息接口在 Dart 中进行定义,然后它将生成对应的 Android 以及 ios 的代码。

使用 Pigeon 消除了在主机和客户端之间匹配字符串的需要消息的名称和数据类型。它支持:嵌套类,消息转换为 API,生成异步包装代码并发送消息。生成的代码具有相当的可读性并保证在不同版本的多个客户端之间没有冲突。支持 Objective-C,Java,Kotlin 和 Swift(通过 Objective-C 互操作)语言。

————

官方对Pigeon的介绍非常清楚,简单讲,Pigeon就是对MethodChannel的封装,我们只需在Flutter端定义好通信所需的方法、参数和返回值,然后再通过Pigeon自动生成Android和Ios端的接口,这些接口已完成对方法、参数和返回值的封装,并确保类型安全,随后我们在原生实现各自接口即可。

添加Pigeon依赖

在项目的pubspec.yaml中加入Pigeon依赖

dev_dependencies:
  flutter_test:
    sdk: flutter

  pigeon: any

在Flutter端定义通信接口

创建一个dart文件,命名随便,然后在里面定义通信需要的方法、参数、返回值。

这个通信接口需要满足以下规则:

  1. 该文件不应包含任何方法或函数实现,而应仅包含声明。
  2. 数据类型定义为类,并具有支持的数据类型的字段(查看支持的数据类型)。
  3. api的定义应为abstract class与任一HostApi()或 FlutterApi()元数据。前者用于在主机平台上定义的过程,而后者用于在Dart中定义的过程。
  4. Api类上的方法声明应具有一个参数和一个返回值,其类型在文件中定义或为void
import 'package:pigeon/pigeon.dart';

class Request{
  String type;
  int index;
}

class Response{
  String result;
  double count;
}

//原生需要实现的接口
@HostApi()
abstract class PigeonnApi{
  Response getSome(Request params);
}

//输出配置
void configurePigeon(PigeonOptions opts){
  //dart文件生成地址
  opts.dartOut = './lib/pigeonnImpl.dart';
  //ios文件生成地址
  opts.objcHeaderOut = './pigeons/Pigeonn.h';
  opts.objcSourceOut = './pigeons/Pigeonn.m';
  //java文件生成地址
  opts.javaOut = './pigeons/Pigeonn.java';
  //java文件的包名
  opts.javaOptions.package = 'xxx.xxx.xxx';
}

执行Pigeon命令

定义好接口后,执行Pigeon命令生成原生端需要实现的通信接口。

flutter pub run pigeon --input pigeons/pigeonn.dart

--input后面跟着的是你刚才创建的dart文件的路径

执行Pigeon命令后,其就会按照我们的配置在指定的目录下生成原生需要的接口。

Android端接入

将Pigeon生成的Java接口移动到我们的Android目录中,创建一个Java类实现它,并且注册给Flutter。

public class PigeonnApi implements Pigeonn.PigeonnApi{

    @Override
    public Pigeonn.Response getSome(Pigeonn.Request arg) {
        System.out.println(arg.getIndex()+arg.getType());
        Pigeonn.Response response = new Pigeonn.Response();
        response.setCount(2.0);
        response.setResult("hello");
        return response;
    }
}

和MethodChannel注册的位置一样

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        GeneratedPluginRegistrant.registerWith(flutterEngine)
      
 Pigeonn.PigeonnApi.setup(flutterEngine.dartExecutor.binaryMessenger,PigeonnApi());
  
    }

IOS端接入

将Pigeon生成的OC接口移动到我们的IOS目录中,实现它,并且注册给Flutter。

因为不会IOS,所以给不了代码展示了。。

Flutter端使用

    PigeonnApi pigeonnApi = PigeonnApi();

   Future<void> getSome() async{
    Request param = Request();
    param.index = 1;
    param.type = "kk";
    Response rep = await pigeonnApi.getSome(param);
    print("count = ${rep.count},result = ${rep.result}");
  }

@async

@async是Pigeon提供的一个注解

默认情况下,Pigeon将为消息生成同步处理程序,当你希望能够异步响应消息,则可以使用@async注解。

仔细看上面Pigeon生成的原生接口方法,其都是同步处理的,如果你希望在里面做一些异步操作并返回结果时,这些方法就不起作用了,这个时候我们可以利用@async来使Pigeon生成一个异步方法。

还以上面定义的接口为例。

1、在定义通信接口需要异步的方法上加上@async注解

@HostApi()
abstract class PigeonnApi{
  @async
  Response getSome(Request params);
}

2、重新执行Pigeon命令生成原生端需要实现的通信接口。

3、重新实现Pigeon生成的原生接口。

public class PigeonnApi implements Pigeonn.PigeonnApi{


    private final Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Pigeonn.Result<Pigeonn.Response>  result = (Pigeonn.Result<Pigeonn.Response>) msg.obj;
            Pigeonn.Response rep = new Pigeonn.Response();
            rep.setCount(2.0);
            rep.setResult("kk");
            result.success(rep);
        }
    };
    
    @Override
    public void getSome(Pigeonn.Request arg, Pigeonn.Result<Pigeonn.Response> result) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    Message message = handler.obtainMessage();
                    message.obj = result;
                    handler.sendMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

4、Flutter端调用不变

加了@async后生成的方法,多了一个Result参数,就是通过它向Flutter返回异步处理结果。不过有一点需要注意的是,结果需要在主线程中返回,这里我用了一个Handler来进行处理。

 

ok,Flutter与原生通信的内容总结的就这些了,希望能对你产生帮助,如果还有其他的通信方式,希望大家可以在评论区告诉我~

 

 

 

 

 

以上是关于Flutter与原生通信的主要内容,如果未能解决你的问题,请参考以下文章

Flutter与原生通信概述

原生与Flutter通信

原生与Flutter通信

原生与Flutter通信

MethodChannel 实现flutter 与 原生通信

Flutter通过MethodChannel实现Flutter 与Android iOS 的双向通信