Flutter跟Native相互通信Platform Channels

96
六_六
0.8 2018.12.16 21:10* 字数 1902

前言

最近在在学习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那边提示超时,我是已经挂了代理的,不知道为何??有知道的朋友请留言告诉我。

App性能总结