博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Flutter如何和Native通信-Android视角
阅读量:7107 次
发布时间:2019-06-28

本文共 9094 字,大约阅读时间需要 30 分钟。

前言

我们都知道Flutter开发的app是可以同时在iOS和Android系统上运行的。显然Flutter需要有和Native通信的能力。比如说,你的Flutter app要显示手机的电量,而电量只能通过平台的系统Api获取。这时就需要有个机制使得Flutter可以通过某种方式来调用这个系统Api并且获得返回值。那么Flutter是如何做到的呢?答案是Platform Channels。

Platform Channels

先来看张图

上图来自Flutter官网,表明了Platform Channels的架构示意图。有细心的同学就要问了,你不是说Flutter和Native通信是通过Platform Channels吗?怎么架构图里面连接他们的是MethodChannel? 其实呢,MethodChannel是Platform Channels中的一种,顾名思义,MethodChannel用起来应该和方法调用差不多。那么还有别的channel?有的,还有EventChannel,BasicMessageChannel等。如果你需要把数据从Native平台发送给Flutter,推荐你使用EventChannel。Flutter framework也是在用这些通道和Native通信,具体可以参考一下
FlutterView.java,在这里能看到Platform Channels的更多用法。

这里需要注意一点,为了保证UI的响应,通过Platform Channels传递的消息都是异步的。

在Platform Channels上传递的消息都是经过编码的,编码的方式也有几种,默认的是用StandardMethodCodec。其他的还有BinaryCodec(二进制的编码,其实啥也没干,直接把入参给返回了), JSONMessageCodec(JSON格式的编码),StringCodec(String格式的编码)。这些编解码器允许的只能是以下这些类型:

所以如果你想把你自己定义的
com.yourmodule.YourObject类型的一个实例直接扔给Platform Channels传送是不行滴。

Platform Channels 怎么用

前面大概介绍了Flutter和Native通信的Platform Channels。那么我们用具体的例子来说说Platform Channels的使用。这里使用Flutter官方出的获取手机电量的Demo。相关源代码可以从下载。

Platform Channels是连接Flutter和Native的通道,那么我们如果要建立这样的通道显然要在两端都要写代码喽。

MethodChannel

先看Native 端怎么写

MethodChannel-Native 端

为简单起见,本例的Android端代码都直接写在MainActivity中。Android平台下获取电量是通过调用BatteryManager来获取的,所以我们先在MainActivity中增加一个获取电量的函数:

private int getBatteryLevel() {  int batteryLevel = -1;  if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {    BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);    batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);  } else {    Intent intent = new ContextWrapper(getApplicationContext()).        registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));    batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /        intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);  }  return batteryLevel;}复制代码

这个函数需要能被Flutter app调用,此时就需要通过MethodChannel来建立这个通道了。 首先在MainActivityonCreate函数中加入以下代码来新建一个MethodChannel

public class MainActivity extends FlutterActivity {    //channel的名称,由于app中可能会有多个channel,这个名称需要在app内是唯一的。    private static final String CHANNEL = "samples.flutter.io/battery";    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        GeneratedPluginRegistrant.registerWith(this);                // 直接 new MethodChannel,然后设置一个Callback来处理Flutter端调用        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(                new MethodCallHandler() {                    @Override                    public void onMethodCall(MethodCall call, Result result) {                        // 在这个回调里处理从Flutter来的调用                    }                });    }}复制代码

注意,每个MethodChannel需要有唯一的字符串作为标识,用以互相区分,这个名称建议使用package.module...这样的模式来命名。因为所有的MethodChannel都是保存在以通道名为Key的Map中。所以你要是设了两个名字一样的channel,只有后设置的那个会生效。

接下来我们来填充onMethodCall

@Overridepublic void onMethodCall(MethodCall call, Result result) {    if (call.method.equals("getBatteryLevel")) {        int batteryLevel = getBatteryLevel();        if (batteryLevel != -1) {            result.success(batteryLevel);        } else {            result.error("UNAVAILABLE", "Battery level not available.", null);        }    } else {        result.notImplemented();    }}               复制代码

onMethodCall有两个入参,MethodCall里包含要调用的方法名称和参数。Result是给Flutter的返回值。方法名是两端协商好的。通过if语句判断MethodCall.method来区分不同的方法,在我们的例子里面我们只会处理名为“getBatteryLevel”的调用。在调用本地方法获取到电量以后通过result.success(batteryLevel)调用把电量值返回给Flutter。 Native端的代码就完成了。是不是很简单?

MethodChannel-Flutter 端

接下来看Flutter端代码怎么写: 首先在 State中创建Flutter端的MethodChannel

import 'dart:async';import 'package:flutter/material.dart';import 'package:flutter/services.dart';...class _MyHomePageState extends State
{ static const platform = const MethodChannel('samples.flutter.io/battery'); // Get battery level.}复制代码

channel的名称要和Native端的一致。 然后是通过MethodChannel调用的代码

String _batteryLevel = 'Unknown battery level.';  Future
_getBatteryLevel() async { String batteryLevel; try { final int result = await platform.invokeMethod('getBatteryLevel'); batteryLevel = 'Battery level at $result % .'; } on PlatformException catch (e) { batteryLevel = "Failed to get battery level: '${e.message}'."; } setState(() { _batteryLevel = batteryLevel; }); }复制代码

final int result = await platform.invokeMethod('getBatteryLevel');这行代码就是通过通道来调用Native方法了。注意这里的await关键字。前面我们说过MethodChannel是异步的,所以这里必须要使用await关键字。 在上面Native代码中我们把获取到的电量通过result.success(batteryLevel);返回给Flutter。这里await表达式执行完成以后电量就直接赋值给result变量了。剩下的就是怎么展示的问题了,就不再细说了,具体可以去看代码。

需要注意的是,这里我们只介绍了从Flutter调用Native方法,其实通过MethodChannel,Native也能调用Flutter的方法,这是一个双向的通道

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

_channel.setMethodCallHandler(platformCallHandler);Future
platformCallHandler(MethodCall call) async { switch (call.method) { case "getName": return "Hello from Flutter"; break; }}复制代码

在Native端,只需要让对应的的channel调用invokeMethod就行了

channel.invokeMethod("getName", null, new MethodChannel.Result() {          @Override          public void success(Object o) {            // 这里就会输出 "Hello from Flutter"            Log.i("debug", o.toString());          }          @Override          public void error(String s, String s1, Object o) {          }          @Override          public void notImplemented() {          }        });复制代码

至此,MethodChannel的用法就介绍完了。可以发现,通过MethodChannelNative和Flutter方法互相调用还是蛮直接的。这里只是做了个大概的介绍,具体细节和一些复杂用法还有待大家的探索。

MethodChannel提供了方法调用的通道,那如果Native有数据流需要传送给Flutter该怎么办呢?这时候就要用到EventChannel了。

EventChannel

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

EventChannel - Native端

先看我们熟悉的Native端怎么来创建EventChannel, 还是在MainActivity.onCreate中,我们加入如下代码:

new EventChannel(getFlutterView(), "samples.flutter.io/charging").setStreamHandler(        new StreamHandler() {          // 接收电池广播的BroadcastReceiver。          private BroadcastReceiver chargingStateChangeReceiver;          @Override         // 这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体。          public void onListen(Object arguments, EventSink events) {            chargingStateChangeReceiver = createChargingStateChangeReceiver(events);            registerReceiver(                chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));          }          @Override          public void onCancel(Object arguments) {            // 对面不再接收            unregisterReceiver(chargingStateChangeReceiver);            chargingStateChangeReceiver = 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端。

EventChannel - Flutter端

首先还是在State内创建EventChannel

static const EventChannel eventChannel =      const EventChannel('samples.flutter.io/charging');复制代码

然后在initState的时候打开这个channel:

@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。好了,Flutter端的代码也贴完了,是不是感觉EventChannel用起来也很简单?

收尾

至此,本文对Flutter和Native之间互相通信的方式的讲解也要告一段落了。Flutter的出发点就是跨平台,而真正要做到跨平台则取决于Flutter是否能通过简单的方式与Native高效通信。Platform Channels能否实现这个目标还有待大规模应用的检验。对于Flutter开发者来讲,由于众多的Native平台API需要暴露给Flutter,还有很多用Native实现的组件/业务逻辑也可能需要暴露给Flutter。这需要写大量的通道代码,也就是说我们必须掌握使用Platform Channels的技能,才能体会到Flutter真正的跨平台能力。本文中对Platform Channels的应用只是非常简单的demo。在大型app中还存在两大挑战,一个是大量的通道我们如何组织,如何维护。另一个是通道协议如何设计才能抹平Android和iOS之间的平台差异,这就需要开发这对两个平台都非常熟悉,这个貌似更加困难。

当然了,如果你做出来了完美的通道,将平台的某个功能(比如蓝牙,GPS什么的)包装成了优美的Flutter API,并且希望世界上其他Flutter开发者也能使用。那么你可以把你智慧的结晶通过发布Flutter插件(plugin)的方式开放给别人。下篇文章我会介绍一下如何来开发一个Flutter插件,敬请期待。

转载地址:http://yxvhl.baihongyu.com/

你可能感兴趣的文章
从Eclipse插件中读取资源
查看>>
[BZOJ1040][P2607][ZJOI2008]骑士[树形DP+基环树]
查看>>
明天回家了
查看>>
linux之SQL语句简明教程---INSERT INTO
查看>>
实时监控远程用户防问服务器的IP所属位置
查看>>
我的友情链接
查看>>
Java函数之Split的用法
查看>>
mysql 的CLOSE_WAIT 的问题
查看>>
1Python全栈之路系列之Django初体验
查看>>
获取资源的“手段”
查看>>
Freebsd9.0安装Nginx+PHP-FPM+MySQL+eAccelerator+Memcached
查看>>
更新 Exchange 2013 CU22后的问题
查看>>
[shell]线上环境puppet证书异常 重新进行认证脚本
查看>>
【读书笔记】数据库逻辑结构
查看>>
洛谷——1616 疯狂的采药(完全背包)
查看>>
Nginx脚本方式切割日志
查看>>
朋友圈H5营销怎么玩?又见情感营销
查看>>
$(window).scroll不能在样式内写overflow属性
查看>>
centos 6.0 dhcp 报错及解决
查看>>
查看linux 版本
查看>>