V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
winlee28
V2EX  ›  Android

Flutter 混合开发(三): Android 与 Flutter 之间通信详细指南

  •  
  •   winlee28 · 2019-12-13 16:18:49 +08:00 · 9816 次点击
    这是一个创建于 1807 天前的主题,其中的信息可能已经有所发展或是发生改变。

    欢迎大家扫描下方二维码关注公众号,获取更多技术干货。

    为防止文章中的图片链接失效或者显示失败,这里贴下原文地址

    Flutter 混合开发(三):Android 与 Flutter 之间通信详细指南

    本文首发于微信公众号「 Android 开发之旅」,欢迎关注 ,获取更多技术干货

    通信场景

    我们在做 Flutter 混合开发的时候通常需要进行 Flutter 和 Native 之间的通信。 比如 Dart 调用 Native 的相册选择图片,Native 将电量、GPS 信息主动传递给 Dart 等等。在混合开发中通信通常有以下几种:

    • 初始化 Flutter 时 Native 向 Dart 传递数据。
    • Native 发送数据给 Dart。
    • Dart 发送数据给 Native。
    • Dart 发送数据给 Native,然后 Native 回传数据给 Dart。

    第一种通信方式我们在讲解原生项目接入 Flutter 时已经讲解过,有兴趣的同学可以移步到Flutter 混合开发(一):Android 项目集成 Flutter 模块详细指南看下。

    通信机制

    Flutter 与 Native 端之间的通信机制是通过 Platform Channel 来完成。消息使用 Channel 在 Flutter 端和 Native 端进行传递。具体如下图所示:

    从图中可以看出,两端之间的通信都是双向的,而且是完成异步传递。Flutter 定义了三种不同类型的 Channel:

    • BasicMessageChannel:用于传递字符串或者半结构化的信息,持续通信,收到信息后可以进行回复。
    • MethodChannel:用于传递方法调用,一次性通信。通常用于 Dart 调用 Native 的方法。
    • EventChannel:用于数据流的通信,持续通信,收到消息后无法回复此次消息。通常用于 Native 向 Dart 的通信。

    下面我们就来看看这三种 Channel 通信方式的具体使用和介绍。

    BasicMessageChannel

    Android 端的相关方法:
    BasicMessageChannel(BinaryMessenger messenger, String name, MessageCodec<T> codec)
    
    • messenger 参数是消息信使( FlutterView ),是消息发送和接受的工具。
    • name 参数是 channel 的名字也是其唯一标识,要和 Dart 端统一。
    • codec 是消息编解码器,也需要和 Dart 端统一。它的作用就是将消息在发送的时候进行加密,dart 端收到消息后在进行解密,传递的都是二进制数据。它有四种类型:BinaryCodec、StringCodec、JSONMessageCodec、StandardMessageCodec,四种类型均属于 MessageCodec 范畴。如不指定默认是 StandardMessageCodec。

    当我们需要接受来自 Dart 端发送的消息时使用 setMessageHandler 方法:

    void setMessageHandler(BasicMessageChannel.MessageHandler<T> handler)
    

    参数 handler 是消息处理器,配合 BinaryMessenger 来完成对消息的处理。它是一个接口,具体的实现在 onMessage 方法中:

    public interface MessageHandler<T> {
        void onMessage(T message, BasicMessageChannel.Reply<T> reply);
    }
    

    参数 message 即为 Dart 发送的数据, reply 是用于回复消息的回调函数,提供 reply.reply("")设置回复的内容。

    上面讲的是接受 Dart 端的消息,那么 Native 端主动发送消息则是使用 send 方法,它有两个重载方法:

    void send(T message)
    void send(T message, BasicMessageChannel.Reply<T> callback)
    

    参数 message 即为要发生给 Dart 的数据,callback 回调则是用于接收 Dart 端收到消息后的回复信息。

    Dart 端相关方法:
    const BasicMessageChannel(this.name, this.codec);
    

    这里的 name 和 codec 和 Android 端的构造方法参数是一样的,那么是 channel 的名字也是唯一标识,codec 是消息编解码器,两个参数在两端必须统一。

    Dart 端如果要接受 Native 端的消息则要设置 setMessageHandler 方法:

    void setMessageHandler(Future<T> handler(T message))
    

    参数 handler 为消息处理器,配合 BinaryMessenger 来完成对消息的处理。

    通过 send 方法向 Native 端发送消息:

     Future<T> send(T message)
    

    参数 message 为要传递的参数。Future<t>为发送消息后等待 Native 回复的回调函数。</t>

    BasicMessageChannel 实战:Android 端和 Flutter 端相互发送消息,并且在收到消息后返回对方信息
    Android 端代码:
     //初始化 BasicMessageChannel
    BasicMessageChannel<String> basicMessageChannel = new BasicMessageChannel<>(flutterView,
    								"BasicMessageChannelPlugin",StringCodec.INSTANCE);
                    
    //接受消息
    basicMessageChannel.setMessageHandler((message, reply) -> {
        mTvDart.setText(message);
        reply.reply("收到 dart 数据:接受成功");
    });
    
    //发送消息
    basicMessageChannel.send(message, reply -> mTvDart.setText(reply));
    
    Dart 端代码:
    //初始化 BasicMessageChannel
    static const BasicMessageChannel<String> _basicMessageChannel =
          BasicMessageChannel("BasicMessageChannelPlugin", StringCodec());
    
    // 接受消息
    void handleBasicMessageChannel() {
        _basicMessageChannel
            .setMessageHandler((String message) => Future<String>(() {
                  setState(() {
                    showMessage = message;
                  });
                  return "收到 Native 的消息:接受成功";
                }));
      }
      
    //发送消息
    response = await _basicMessageChannel.send(_controller.text);
        setState(() {
          showMessage = response;
        });
    

    最后效果为下图,红色分割线上部分为 Native 页面,下部分为 Flutter 页面。

    MethodChannel

    使用 MethodChannel 相关方法的参数类型及含义和 BasicMessageChannel 的参数含义都是相同的,下面就不一一解释了。

    Androd 端相关方法:
    MethodChannel(BinaryMessenger messenger, String name)
    MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)
    

    第一个构造函数会构造一个 StandardMethodCodec.INSTANCE 类型的 MethodCodec。MethodCodec 定义了两种类型:JSONMethodCodec 和 StandardMethodCodec。

    如果想接受来自 Dart 端的消息则使用:

    setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) 
    

    MethodCallHandler 为接口,回调方法为:

    public interface MethodCallHandler {
        void onMethodCall(MethodCall call, MethodChannel.Result result);
    }
    

    call 参数有两个成员变量,String 类型的 call.method 表示调用的方法名,Object 类型的 call.arguments 表示调用方法所传递的入参。result 是回复此消息的回调函数提供了 result.success,result.error,result.notImplemented 方法调用。

    发送消息主动调用 Dart 代码则使用 invokeMethod 方法

    invokeMethod(@NonNull String method, @Nullable Object arguments)
    invokeMethod(String method, @Nullable Object arguments, Result callback) 
    

    第二个方法多了一个 callback,它是用来接受 Dart 端收到消息后的回复信息。

    public interface Result {
    
        void success(@Nullable Object result);
    
        void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);
    
        void notImplemented();
    }
    
    Dart 端相关方法:
    const MethodChannel(this.name, [this.codec = const StandardMethodCodec()])
    

    构造函数默认是使用 StandardMethodCodec 编解码器。

    通过 setMethodCallHandler 方法接受来自 Native 的方法调用,通过 invokeMethod 方法调用 Native 端的方法。

     void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) 
    
     Future<T> invokeMethod<T>(String method, [ dynamic arguments ])
    
    MethodChannel 实战:Native 端调用 Dart 端的 getPlatform 方法返回当前的 os 平台,Dart 端调用 Native 端的 getBatteryLevel 方法获取当前手机电量。
    Android 端代码:
    //初始化 MethodChannel
    MethodChannel methodChannel = new MethodChannel(flutterView, "MethodChannelPlugin");
    
    mBtnTitle.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //调用 dart 端 getPlatform 方法
            methodChannel.invokeMethod("getPlatform", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object result) {
                    mTvDart.setText(result.toString());
                }
    
                @Override
                public void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails) {
                    mTvDart.setText(errorCode + "==" + errorMessage);
                }
    
                @Override
                public void notImplemented() {
                    mTvDart.setText("未实现 getPlatform 方法");
                }
            });
        }
    });
    
    //接受 dart 的调用
    methodChannel.setMethodCallHandler((call, result) -> {
        switch (call.method) {
            case "getBatteryLevel":
                int batteryLevel = getBatteryLevel();
                if (batteryLevel != -1) {
                    result.success("电量为:" + batteryLevel);
                } else {
                    result.error("1001", "调用错误", null);
                }
                break;
            default:
                result.notImplemented();
                break;
        }
    });
    
    Dart 端代码:
    // receive
    void handleMethodChannelReceive() {
    	Future<dynamic> platformCallHandler(MethodCall call) async {
    	  switch (call.method) {
    	    case "getPlatform":
    	      return getPlatformName(); //调用 success 方法
    	//          return PlatformException(code: '1002',message: "出现异常"); //调用 error
    	      break;
    	  }
    	}
    	
    	_methodChannel.setMethodCallHandler(platformCallHandler);
    	//    _methodChannel.setMethodCallHandler(null); //调用 notImplemented
    }
    
    //send
    void handleMethodChannelSend() async {
    	try {
    	  response = await _methodChannel.invokeMethod("getBatteryLevel");
    	  print(response);
    	  setState(() {
    	    showMessage = response;
    	  });
    	} catch (e) {
    	  //捕获 error 和 notImplemented 异常
    	  setState(() {
    	    showMessage = e.message;
    	  });
    	}
    }
     
    

    当我们在使用 setMethodCallHandler 接受到 native 的消息时,直接调用相关方法即可调用 Native 端的 success 回调。

    如果直接抛异常如 PlatformException,那么就调用 Native 端的 error 回调。

    PlatformException(code: '1002',message: "出现异常")
    

    如果我们直接设置 handler 为 null

    _methodChannel.setMethodCallHandler(null);
    

    那么就会调用 Native 端的 notImplemented 方法回调。

    同理我们在 Dart 端使用 invokeMethod 方法是,需要进行异常捕获以便于我们接受到 Native 端调用的 error 和 notImplemented 方法回调。

    最后效果为下图,红色分割线上部分为 Native 页面,下部分为 Flutter 页面。

    EventChannel

    EventChannel 内部实现原理其实也是通过 MethodChannel 来完成的。

    Android 端相关代码:
    EventChannel(BinaryMessenger messenger, String name)
    EventChannel(BinaryMessenger messenger, String name, MethodCodec codec)
    

    同样的,也是两个构造,默认 codec 为 StandardMethodCodec,EventChannel 和 MethodChannel 的 codec 都属于 MethodCodec 范畴。

    通过 setStreamHandler 来监听 Dart 端发送的消息,

    void setStreamHandler(EventChannel.StreamHandler handler)
    

    其中 handler 是一个接口:

    public interface StreamHandler {
        void onListen(Object args, EventChannel.EventSink eventSink);
    
        void onCancel(Object o);
    }
    

    args 为 dart 端初始化监听流的参数,eventSink 设置了三个回调,分别是 success、error 和 endofStream。分别对应 Dart 端的 ondata、error 和 onDone 回调。

    Dart 端相关代码:
    const EventChannel(this.name, [this.codec = const StandardMethodCodec()]);
    

    通过 EventChannel 初始化一个 channel 对象。如果从 Native 中接受数据需要定义一个广播流:

    Stream<dynamic> receiveBroadcastStream([ dynamic arguments ]) 
    

    通过调用 Stream 的 listen 方法来完成注册。

    EventChannel 实战:Native 端主动发送电量信息给 Dart 端,Dart 端收到信息后进行展示。
    Android 端代码:
    EventChannel eventChannel = new EventChannel(flutterView, "EventChannelPlugin");
    eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
        @Override
        public void onListen(Object arguments, EventChannel.EventSink events) {
            events.success(arguments.toString() + getBatteryLevel());
            //events.error("111","出现错误","");
            //events.endOfStream();
        }
    
        @Override
        public void onCancel(Object arguments) {
        }
    });
    
    Dart 端代码:
    //init
    static const EventChannel _eventChannel = EventChannel("EventChannelPlugin");
    
    //receive
    void handleEventChannelReceive() {
    streamSubscription = _eventChannel
        .receiveBroadcastStream() //可以携带参数
        .listen(_onData, onError: _onError, onDone: _onDone);
    }
    
    void _onDone() {
    setState(() {
      showMessage = "endOfStream";
    });
    }
    
    _onError(error) {
    setState(() {
      PlatformException platformException = error;
      showMessage = platformException.message;
    });
    }
    
    void _onData(message) {
    setState(() {
      showMessage = message;
    });
    }
    

    通过 event.success 方法发送信息,dart 端通过监听 Stream 流来获取信息。当 Native 端调用 events.error 时在 Dart 端的 onError 回调中需要将 error 转换为 PlatformException 才能获取到异常的相关信息。

    最后效果为下图,红色分割线上部分为 Native 页面,下部分为 Flutter 页面。

    总结

    主要是讲解了 Android 端和 Dart 的三种通信方式。详细分析了方法构成和具体的实例使用。每一种方式都对应不同的使用场景,大家可以按需选择,多加练习做到熟能生巧。

    文中都是贴的一些代码片段,全部 Demo 源码已经上传到后台,关注公众号回复「混合开发」即可获取下载链接。

    扫描下方二维码关注公众号,获取更多技术干货。

    推荐阅读

    Flutter 开发必备 Dart 基础:Dart 快速入门

    Flutter 混合开发(一):Android 项目集成 Flutter 模块详细指南

    Flutter 混合开发(二):iOS 项目集成 Flutter 模块详细指南

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2713 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 00:06 · PVG 08:06 · LAX 16:06 · JFK 19:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.