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

 

 

 

 

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Flutter 是一个跨平台的移动应用开发框架,它可以让开发者使用一套代码同时开发 iOSAndroid 应用。而在某些情况下,我们可能需要集成原生 SDK 实现一些功能。本文将介绍如何在 Flutter 中集成原生 SDK。 首先,我们需要在 Flutter 中创建一个平台通道(Platform Channel)来实现 Flutter原生代码的通信。 1.创建一个平台通道 在 Flutter 中,平台通道定义了 Dart 代码和原生代码之间的通信方式。我们可以通过 MethodChannel、EventChannel 或 BasicMessageChannel 等方式来创建平台通道。 以 MethodChannel 为例,我们可以在 Flutter 中创建一个 MethodChannel: ``` final MethodChannel platformChannel = MethodChannel('com.example.platform_channel'); ``` 这里的 com.example.platform_channel 是一个字符串,用来标识 Flutter原生代码之间的通道。 2.在原生代码中实现方法 在原生代码中,我们需要实现与 Flutter 中定义的 MethodChannel 对应的方法。例如,我们可以在 Android 中创建一个名为 MyPlugin 的类来实现这个方法: ``` public class MyPlugin implements MethodCallHandler { private static final String CHANNEL = "com.example.platform_channel"; public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL); channel.setMethodCallHandler(new MyPlugin()); } @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("getDeviceInfo")) { String deviceInfo = getDeviceInfo(); result.success(deviceInfo); } else { result.notImplemented(); } } private String getDeviceInfo() { // 获取设备信息的代码 return "device info"; } } ``` 这里的 getDeviceInfo 方法用来获取设备信息,并将结果返回给 Flutter。 3.在 Flutter 中调用方法 在 Flutter 中,我们可以通过 MethodChannel 来调用 MyPlugin 中实现的方法: ``` String deviceInfo = await platformChannel.invokeMethod('getDeviceInfo'); ``` 这里的 invokeMethod 方法用来调用 getDeviceInfo 方法,并将结果返回给 Flutter。 以上就是在 Flutter 中集成原生 SDK 的基本流程。在实际使用中,我们还需要注意一些细节,例如方法参数和返回值的类型转换等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值