Flutter跟Native相互通信Platform Channels

前言

最近在在学习flutter语言,发现flutter需要跟原生混合开发,混合开发中其实原生很多框架代码都已经写好了,比如网络框架,或者想要调用原生系统的东西,比如获取手机电量啊,其实flutter是直接可以调用native端的代码,可以跟原生通信,通信的名词称为:Platform Channels(平台通道)。Flutter平台特定的API支持不依赖于代码生成,而是依赖于灵活的消息传递的方式:应用的Flutter部分通过平台通道(platform channel)将消息发送到其应用程序的所在的宿主(iOS或Android)。
宿主监听的平台通道,并接收该消息。然后它会调用特定于该平台的API(使用原生编程语言) - 并将响应发送回客户端,即应用程序的Flutter部分

框架概述: 平台通道

使用平台通道在客户端(Flutter UI)和宿主(平台)之间传递消息,如下图所示:

image

上图中用到了MethodChannel,其实flutter是有三种通信类型,分别是:

* BasicMessageChannel:用于传递字符串和半结构化的信息,这个用的比较少

* MethodChannel:用于传递方法调用(method invocation)通常用来调用native中某个方法

* EventChannel: 用于数据流(event streams)的通信。有监听功能,比如电量变化之后直接推送数据给flutter端。

三种Channel之间互相独立,各有用途,但它们在设计上却非常相近。每种Channel均有三个重要成员变量:

* name: String类型,代表Channel的名字,也是其唯一标识符。

* messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。

* codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器。

注意:以上图片箭头是双向的,也就是flutter和native是可以相互调用通信。

具体原理深度理解可以看这篇文章:channel原理篇

channel通信的数据类型

双方通信传递数据只能是如下类型数据,你不能传递一个自己建立的javabean过去。

具体使用

1.MethodChannel的使用

1.1-Native端写法

  • 这个代码是我封装了一个HttpMethodChannel类,用于调用原生网络框架的,实现MethodCallHandler,重写onMethodCall方法,在构造方法里面new一个MethodChannel类,需要传递两个参数,一个是FlutterView,以为底层原理其实就是通过FlutterNativeView调用JNI方法把数据转换成二进制数据传递给flutter的。第二个参数是一个唯一的字符串HTTP_CHANNEL,到时flutter端也需要使用这个字符串。可以直接考过去。

  • 接着httpChannel.setMethodCallHandler(this)把httpMethodChannel传进去。

/**
 * 构造网络通信渠道
 * Created by wuminjian on 2018/12/13.
 */
public class HttpMethodChannel implements MethodChannel.MethodCallHandler {
    private static final String TAG = "HttpMethodChannel";

    private static final String HTTP_CHANNEL = "com.lingan.seeyou/http";

    private MethodChannel httpChannel;

    private Context context;

    private HttpMethodChannel(Context context, FlutterView flutterView) {
        this.context = context;
        httpChannel = new MethodChannel(flutterView, HTTP_CHANNEL);
        httpChannel.setMethodCallHandler(this);

    }

    /**
     * 暴露到外面的静态create类
     */
    public static HttpMethodChannel create(Context context, FlutterView flutterView) {
        return new HttpMethodChannel(context, flutterView);
    }

    @Override
    public void onMethodCall(MethodCall methodCall,MethodChannel.Result result) {
        String url = null;
        String param;

        if (methodCall.hasArgument(HttpConstant.HTTP_URL)) {
            url = methodCall.argument(HttpConstant.HTTP_URL);
        }
        if (methodCall.hasArgument(HttpConstant.HTTP_PARAMS)) {
            param = methodCall.argument(HttpConstant.HTTP_PARAMS);
        }
        if (TextUtils.isEmpty(url)) {
            result.success("");
            return;
        }
        switch (methodCall.method) {
            //get请求
            case HTTP_GET:
                HttpController.getInstance().httpGet(url, null,result);
                break;
            //post请求
            case HTTP_POST:
                ToastUtils.showToast(context, "post网络请求");
                result.success("我准备开始post请求了");
                break;
        }

}
  • 接下来我们看看onMethodCall方法,在Flutter发送请求时, onMethodCall
    方法会执行。onMethodCall有两个入参,
    • MethodCall 中有关当前请求的信息,例如调用方法的名字HTTP_GET这个变量“get”其实就是flutter那边传过来的方法名字告诉我它需要请求的是网络的get请求操作,methodCall.argument是获取flutter那边传递过来的其他参数数据,比如网络请求中的url、headers、boday等数据。

    • Result对象是回调数据给flutter中使用的,有三个方法:

      1. result.success(data);     成功的时候,返回数据调用
      2. result.error()            失败的情况下,调用
      3. result.notImplemented();  这个是代表比如:我上面的列子中HTTP_GET方法没有实现的话,可以告诉flutter方法没有实现
      
      

1.2Flutter端写法


class ListWigetState extends State<ListWiget> {

  List subjects = [];
  MethodChannel platform = const MethodChannel(ChannelUtils.HTTP_CHANNEL);

  @override
  void initState() {
    loadNativeData();
  }

  • 首页也是构造一个MethodChannel对象,里面的参数就是我们在原生那边定个的字符串,记住一定是要一样的。

  • 接下来就是用MethodChannel对象调用的代码:

    /**
    * 加载原生网络请求
    */
    loadNativeData() async {
    var responseBody;
    try {
      var params = {"a": 1};
      responseBody = await platform.invokeMethod("GET",{"url": HttpApi.HTTP_HOME_URL, "params": params.toString(),"headers":null});
    
      print("zzzz: "+responseBody);
    
      var convertDataToJson = jsonDecode(responseBody)["subjects"];
      setState(() {
        subjects = convertDataToJson;
      });
    } on PlatformException catch (e) {
      print(e.toString());
    }
    }
    
  • responseBody = await platform.invokeMethod("GET",{"url": HttpApi.HTTP_HOME_URL, "params": params.toString(),"headers":null});

  • 这行代码就是通过通道来调用Native方法了。注意这里的await关键字是异步的,所以这里必须要使用await关键字,"GET"就是我们之前提的传递的方法名字,代表是get请求还是post请求,{"url": HttpApi.HTTP_HOME_URL, "params": params.toString(),"headers":null}这个是我们需要传递过去的参数。
    在上面Native代码中我们把获取到的数据是通过result.success();返回给Flutter。这里await表达式执行完成以后就直接赋值给responseBody变量了。

1.3双向调用通信

上面我们说的是flutter调用Native端的用法,看最上面的图我们知道其实都双向的调用,即Native端也可以调用flutter的.

  • 举个例子,我们想从Native端请求Flutter端的一个getFlutterName方法获取一个字符串。在Flutter端你需要给MethodChannel设置一个MethodCallHandler:

    platform.setMethodCallHandler(platformCallHandler);
    
    Future<dynamic> platformCallHandler(MethodCall call) async {
        switch (call.method) {
                case "getFlutterName":
                return "Flutter name flutter";
                break;
        }
    }
    
    
  • 在Native端,只需要让对应的的channel调用invokeMethod就行了:

    channel.invokeMethod("getFlutterName", null, new MethodChannel.Result() {
          @Override
          public void success(Object o) {
            // 这里就会输出 "Flutter name flutter"
            Log.i("debug", o.toString());
          }
          @Override
          public void error(String s, String s1, Object o) {
          }
          @Override
          public void notImplemented() {
          }
        });
    
  • 上图中的三个方法就是我前面提到的三个方法“成功”、“失败”、“没有实现”的三个方法的回调。其实说白了就是反过来调用而已。

2.EventChannel的使用

EventChannel的使用我们也以官方获取电池电量的demo为例,手机的电池状态是不停变化的。我们要把这样的电池状态变化由Native及时通过EventChannel来告诉Flutter。这种情况用之前讲的MethodChannel办法是不行的,这意味着Flutter需要用轮询的方式不停调用getBatteryLevel来获取当前电量,显然是不正确的做法。而用EventChannel的方式,则是将当前电池状态"推送"给Flutter.

2.1EventChannel-Native端写法

先看我们熟悉的Native端怎么来创建EventChannel, 还是封装一个FlutterEventChannel类,然后在MainActivity.onCreate中调用FlutterEventChannel的create方法把FlutterView传进来,代码如下:

public class FlutterEventChannel implements EventChannel.StreamHandler {

    private static final String TAG = "FlutterEventChannel";
    private static final String EVENT_CHANNEL_NAME = "com.meetyou.flutter/event";

    private FlutterEventChannel(FlutterView flutterView) {
        EventChannel eventChannel = new EventChannel(flutterView, EVENT_CHANNEL_NAME);
        eventChannel.setStreamHandler(this);
    }

    public static FlutterEventChannel create(FlutterView flutterView) {
        return new FlutterEventChannel(flutterView);
    }

    private EventChannel.EventSink eventSink;

    /**
     * 暴露出去供界面传数据到Flutter
     */
    public void sendEvent(Object data) {
        if (eventSink != null) {
            eventSink.success(data);
        } else {
            LogUtils.e(TAG, "===== FlutterEventChannel.eventSink 为空 需要检查一下 =====");
        }
    }

    @Override
    public void onListen(Object o, EventChannel.EventSink eventSink) {
        this.eventSink = eventSink;
    }

    @Override
    public void onCancel(Object o) {
        eventSink = null;
    }
}

和MethodChannel类似,我们也是直接new一个EventChannel实例,并给它设置了一个StreamHandler类型的回调。其中onCancel代表对面不再接收,这里我们应该做一些clean up的事情。而 onListen则代表通道已经建好,Native可以发送数据了。注意onListen里带的EventSink这个参数,后续Native发送数据都是经过EventSink的。看代码:

private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) {
    return new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

        if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
          events.error("UNAVAILABLE", "Charging status unavailable", null);
        } else {
          boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                               status == BatteryManager.BATTERY_STATUS_FULL;
          // 把电池状态发给Flutter
          events.success(isCharging ? "charging" : "discharging");
        }
      }
    };
  }

上面的代码就是监听了点亮改变的广播,在onReceive函数内,系统发来电池状态广播以后,在Native这里转化为约定好的字符串,然后通过调用events.success();发送给Flutter。Native端的代码就是这样,接下来看Flutter端。

2.2EventChannel-Flutter端写法

首先还是在State内创建EventChannel,然后在initState的时候打开这个channel:

static const EventChannel eventChannel =
      const EventChannel('com.meetyou.flutter/event');
      
@override
  void initState() {
    super.initState();
    eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

收到event以后的处理是在_onEvent函数里:

void _onEvent(Object event) {
    setState(() {
      _chargingStatus =
          "Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
    });
  }

  void _onError(Object error) {
    setState(() {
      _chargingStatus = 'Battery status: unknown.';
    });
  }

从Native端传过来的"charging"/"discharging"字符串直接就是入参event.

以上就是整个channel的基本用法,其实整个代码可以做成一个plugin插件打包上传到pub上面,供大家使用,目前我在发布插件包的时候,最后一步上传的时候,google那边提示超时,我是已经挂了代理的,不知道为何??有知道的朋友请留言告诉我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容

  • 前言 我们都知道Flutter开发的app是可以同时在iOS和Android系统上运行的。显然Flutter需要有...
    HowHardCanItBe阅读 20,596评论 12 27
  • 开篇 开局一张图,其他全靠_? 目前flutter框架还比较新,又是谷歌家的东西,所以网上的文章基本都是讲安卓和f...
    华南犀牛阅读 17,826评论 22 47
  • 少点套路,发现自己的核心价值 2017-2-19星期六野狼魂(东莞)晴 早上的时候,有朋友在微信给我发信息:“一直...
    野狼周高祥阅读 257评论 0 0
  • 时隔半年许,未见初心安。 夜深人静时,忽想初心梦。 深知梦移位,今愿捡梦回。 望吾心不乱,方能唤初心。
    薄荷加冰要多心凉阅读 131评论 2 1
  • 不是每一个鬼魂都能顺利走出迷魂谷,就像不每一个鬼魂都能投胎到一个好人家一样。 (一) “喂,你们需不需要帮助啊?”...
    星宿海阅读 541评论 0 1