Flutter学习笔记(29)--Flutter如何与native进行通信
如需转载,请注明出处:flutter学习笔记(29)--flutter如何与native进行通信
前言:在我们开发flutter项目的时候,难免会遇到需要调用native api或者是其他的情况,这时候就需要处理flutter与native的通信问题,一般常用的flutter与native的通信方式有3中。
1.methodchannel:flutter端向native端发送通知,通常用来调用native的某一个方法。
2.eventchannel:用于数据流的通信,有监听功能,比如电量变化后直接推送给flutter端。
3.basicmessagechannel:用于传递字符串或半结构体的数据。
接下来具体看一下每种通信方式的使用方法!
-
methodchannel
先来整体说一下逻辑思想吧,这样能更容易理解一些,如果想要实现flutter与native通信,首先要建立一个通信的通道,通过一个通道标识来进行匹配,匹配上了之后flutter端通过invokemethod调用方法来发起一个请求,在native端通过onmethodcall进行匹配请求的key,匹配上了就处理对应case内的逻辑!!!整体来看,我感觉有点eventbus的意思呢,就像是一条事件总线。。。
第一步:实现通信插件plugin-native端
由于一个项目中可能会需要很多flutter与native的通信,所以我这里是将测试的插件封装到一个类里面了,然后在mainactivity里面的oncreate进行注册
package com.example.flutter_demo; import android.content.context; import io.flutter.plugin.common.methodcall; import io.flutter.plugin.common.methodchannel; import io.flutter.plugin.common.pluginregistry; public class testplugin implements methodchannel.methodcallhandler { public static string channelname = "channel_name";//每一个通信通道的唯一标识,在整个项目内唯一!!! private static methodchannel methodchannel; private context context; public testplugin(context context) { this.context = context; } public static void registerwith(pluginregistry.registrar registrar){ methodchannel = new methodchannel(registrar.messenger(),channelname); testplugin instance = new testplugin(registrar.activity()); methodchannel.setmethodcallhandler(instance); } @override public void onmethodcall(methodcall methodcall, methodchannel.result result) { if (methodcall.method.equals("method_key")){ result.success("what is up man???"); } } }
注:channelname-->上面说过了,由于项目内会有很多的通信,所以我们定义的channel必须是唯一的!!!!
testplugin实现methodchannel.methodcallhandler,定义一个对外暴露的注册方法registerwith,因为我们需要在mainactivity进行注册,在registerwith方法内初始化methodchannel
接下来我们看一下onmethodcall方法,这个方法在flutter发起请求时被调用,方法内有两个参数,一个methodcall和一个result,我们分别来说一下这两个参数:
methodcall:其中当前请求的相关信息,比如匹配请求的key
result:用于给flutter返回数据,有3个方法,result.success(成功调用)、result.erro(失败调用)、result.notimplemented(方法没有实现调用)
第二步:注册通信插件plugin-native端
package com.example.flutter_demo; import android.os.bundle; import io.flutter.app.flutteractivity; import io.flutter.plugins.generatedpluginregistrant; public class mainactivity extends flutteractivity { @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); generatedpluginregistrant.registerwith(this); testplugin.registerwith(this.registrarfor(testplugin.channelname)); } }
注册这块我感觉作用是起到了一个桥梁的作用,通过注册将插件和flutter内定义的channel关联了起来。
第三步:flutter内发起通信请求-flutter端
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runapp(myapp()); class myapp extends statefulwidget{ @override state<statefulwidget> createstate() { // todo: implement createstate return new myappstate(); } } class myappstate extends state<myapp> { var _textcontent = 'welcome to flutter word'; future<null> _changetextcontent() async{ //channel_name每一个通信通道的唯一标识,在整个项目内唯一!!! const platfom = const methodchannel('channel_name'); try { //method_key是插件testplugin中onmethodcall回调匹配的key string resultvalue = await platfom.invokemethod('method_key'); setstate(() { _textcontent = resultvalue; }); }on platformexception catch (e){ print(e.tostring()); } } @override widget build(buildcontext context) { // todo: implement build return new materialapp( theme: new themedata( primarycolor: colors.white, ), debugshowcheckedmodebanner: false, title: 'demo', home: new scaffold( appbar: new appbar( title: new text('demo'), leading: icon(icons.menu,size: 30,), actions: <widget>[ icon(icons.search,size: 30,) ], ), body: new center( child: new text(_textcontent), ), floatingactionbutton: new floatingactionbutton(onpressed: _changetextcontent,child: new icon(icons.adjust),), ), ); } }
这里的功能就是页面*有一个text,通过点击一个按钮,发起通信请求,通信成功在就收到native返回的数据后将text的文案修改。
我们看一下最终的效果:
methodchannel通信是双向的,也就是说,flutter端可以向native发起通信,native也可以向flutter端发起通信,本质上就是反过来调用一下,原理上是同一个意思,具体的代码就不在这里写了,需要的话可以自行百度一下!
-
eventchannel
eventchannel的使用我们也以官方获取电池电量的demo为例,手机的电池状态是不停变化的。我们要把这样的电池状态变化由native及时通过eventchannel来告诉flutter。这种情况用之前讲的methodchannel办法是不行的,这意味着flutter需要用轮询的方式不停调用getbatterylevel来获取当前电量,显然是不正确的做法。而用eventchannel的方式,则是将当前电池状态"推送"给flutter。
第一步:mainactivity内注册eventchannel,并提供获取电量的方法-native端
public class eventchannelplugin implements eventchannel.streamhandler { private handler handler; private static final string channel = "com.example.flutter_battery/stream"; private int count = 0; public static void registerwith(pluginregistry.registrar registrar) { // 新建 eventchannel, channel常量的作用和 methodchannel 一样的 final eventchannel channel = new eventchannel(registrar.messenger(), channel); // 设置流的处理器(streamhandler) channel.setstreamhandler(new eventchannelplugin()); } @override public void onlisten(object o, eventchannel.eventsink eventsink) { // 每隔一秒数字+1 handler = new handler(message -> { // 然后把数字发送给 flutter eventsink.success(++count); handler.sendemptymessagedelayed(0, 1000); return false; }); handler.sendemptymessage(0); } @override public void oncancel(object o) { handler.removemessages(0); handler = null; count = 0; } }
其中oncancel代表对面不再接收,这里我们应该做一些clean up的事情。而 onlisten则代表通道已经建好,native可以发送数据了。注意onlisten里带的eventsink这个参数,后续native发送数据都是经过eventsink的。
第二步:同methodchannel一样,发起通信请求
class _myhomepagestate extends state<myhomepage> { // 创建 eventchannel static const stream = const eventchannel('com.example.flutter_battery/stream'); int _count = 0; streamsubscription _timersubscription; void _starttimer() { if (_timersubscription == null) // 监听 eventchannel 流, 会触发 native onlisten回调 _timersubscription = stream.receivebroadcaststream().listen(_updatetimer); } void _stoptimer() { _timersubscription?.cancel(); _timersubscription = null; setstate(() => _count = 0); } void _updatetimer(dynamic count) { print("--------$count"); setstate(() => _count = count); } @override void dispose() { super.dispose(); _timersubscription?.cancel(); _timersubscription = null; } @override widget build(buildcontext context) { return scaffold( appbar: appbar( title: text(widget.title), ), body: container( margin: edgeinsets.only(left: 10, top: 10), child: center( child: column( children: [ row( children: <widget>[ raisedbutton( child: text('start eventchannel', style: textstyle(fontsize: 12)), onpressed: _starttimer, ), padding( padding: edgeinsets.only(left: 10), child: raisedbutton( child: text('cancel eventchannel', style: textstyle(fontsize: 12)), onpressed: _stoptimer, )), padding( padding: edgeinsets.only(left: 10), child: text("$_count"), ) ], ) ], ), ), ), ); } }
整体说明一下:flutter端通过stream.receivebroadcaststream().listen监听native发送过来的数据,native端通过eventsink.success(++count)不断的将数据返回给flutter端,这样就实现了我们想要的实时监听的效果了!
-
basicmessagechannel
其实他就是一个简版的methodchannel,也可以说methodchannel是基于basicmessagechannel实现的,basicmessagechannel只是进行通信,更通俗的理解就是两端发通知,但是不需要进行方法匹配。
第一步:初始化及注册-native
@override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); // 省略其他代码... messagechannel = new basicmessagechannel<>(flutterview, channel, stringcodec.instance); messagechannel. setmessagehandler(new messagehandler<string>() { @override public void onmessage(string s, reply<string> reply) { // 接收到flutter消息, 更新native onflutterincrement(); reply.reply(empty_message); } }); floatingactionbutton fab = findviewbyid(r.id.button); fab.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { // 通知 flutter 更新 sendandroidincrement(); } }); } private void sendandroidincrement() { messagechannel.send(ping); } private void onflutterincrement() { counter++; textview textview = findviewbyid(r.id.button_tap); string value = "flutter button tapped " + counter + (counter == 1 ? " time" : " times"); textview.settext(value); }
第二步:flutter端发起通信-flutter
class _myhomepagestate extends state<myhomepage> { static const string _channel = 'increment'; static const string _pong = 'pong'; static const string _emptymessage = ''; static const basicmessagechannel<string> platform = basicmessagechannel<string>(_channel, stringcodec()); int _counter = 0; @override void initstate() { super.initstate(); // 设置消息处理器 platform.setmessagehandler(_handleplatformincrement); } // 如果接收到 native 的消息 则数字+1 future<string> _handleplatformincrement(string message) async { setstate(() { _counter++; }); // 发送一个空消息 return _emptymessage; } // 点击 flutter 中的 fab 则发消息给 native void _sendflutterincrement() { platform.send(_pong); } @override widget build(buildcontext context) { return scaffold( appbar: appbar( title: text('basicmessagechannel'), ), body: container( child: column( crossaxisalignment: crossaxisalignment.start, children: <widget>[ expanded( child: center( child: text( 'platform button tapped $_counter time${_counter == 1 ? '' : 's'}.', style: const textstyle(fontsize: 17.0)), ), ), container( padding: const edgeinsets.only(bottom: 15.0, left: 5.0), child: row( children: <widget>[ image.asset('assets/flutter-mark-square-64.png', scale: 1.5), const text('flutter', style: textstyle(fontsize: 30.0)), ], ), ), ], )), floatingactionbutton: floatingactionbutton( onpressed: _sendflutterincrement, child: const icon(icons.add), ), ); } }