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