Flutter 49: 图解 Flutter 与 Android 原生交互
小菜上一篇简单学习了一下 Android 原生接入 Flutter Module,现在学习一下两者之间的数据交互;
Flutter 与 Android/iOS 之间信息交互通过 Platform Channel 进行桥接;Flutter 定义了三种不同的 Channel;但无论是传递方法还是传递事件,其本质上都是数据的传递;
1. MethodChannel:用于传递方法调用;
2. EventChannel:用于数据流信息通信;
3. BasicMessageChannel:用于传递字符串和半结构化的信息;
每种 Channel 均包含三个成员变量;
- name:代表 Channel 唯一标识符,Channel 可以包含多个,但 name 为唯一的;
- messager:代表消息发送与接收的工具 BinaryMessenger;
- codec:代表消息的编解码器;
小菜以上一节 Android 原生集成 Flutter Module 为基础,针对不同的 Channel 进行学习尝试;且小菜通过 View / Fragment / Activity 三种原生加载方式进行测试;
MethodChannel
小菜在 Flutter 页面,点击右下角按钮,将消息传递给 Android;MethodChannel 通过 invokeMethod 进行消息发送,固定的第一个 name 参数是必须存在且唯一的,与 Android 原生中匹配;第二个参数为传送的数据,类似于 Intent 中的 ExtraData,只是支持的数据类型偏少;第三个可隐藏的参数为编解码器;
class _MyHomePageState extends State<MyHomePage> { static const methodChannel = const MethodChannel('ace_demo_android_flutter'); String _result = ''; Future<Null> _getInvokeResult() async { try { _result = await methodChannel .invokeMethod('ace_demo_user', {'name': '我不是老猪', 'gender': 1}); } on PlatformException catch (e) { _result = "Failed: '${e.message}'."; } setState(() {}); } void _incrementCounter() { setState(() { _getInvokeResult(); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Text('${_result}', style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: Icon(Icons.arrow_back))); } }
1. FlutterView
在 Android 集成 Flutter Module 中时,官方建议使用 View / Fragment 方式,在使用 View 时,建议 Activity 继承 FlutterActivity 或 FlutterFragmentActivity,通过 FlutterView 进行 MethodChannel 绑定监听;
public class MyFlutterViewActivity extends FlutterFragmentActivity { private static final String CHANNEL = "ace_demo_android_flutter"; private static final String TAG = "MyFlutterViewActivity"; private static final int REQUEST_CODE = 1000; FlutterView flutterView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_flutter); DisplayMetrics outMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(outMetrics); int widthPixels = outMetrics.widthPixels; int heightPixels = outMetrics.heightPixels; flutterView = Flutter.createView(MyFlutterViewActivity.this, getLifecycle(), "/"); FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(widthPixels, heightPixels); addContentView(flutterView, layout); new MethodChannel(flutterView, CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { if (call.method.equals("ace_demo_user")) { if (call.arguments != null) { Log.e(TAG, "Flutter -> Android 回调内容:" + call.arguments.toString()); } else { Log.e(TAG, "Flutter -> Android 回调参数为空!"); } result.success("Android -> Flutter 接收回调后返回值:" + TAG); Intent intent = new Intent(); intent.putExtra("data", call.arguments!=null?call.arguments.toString():""); setResult(REQUEST_CODE, intent); MyFlutterViewActivity.this.finish(); } else { result.notImplemented(); } } }); } }
2. FlutterFragment
使用 Fragment 方式时与 View 方式均需要获取 FlutterView 进行绑定,此时 Fragment 继承 FlutterFragment 较易获取;
public class MyFlutterFragment extends FlutterFragment { private static final String CHANNEL = "ace_demo_android_flutter"; private static final String TAG = "MyFlutterFragment"; @SuppressWarnings("unchecked") @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); new MethodChannel((FlutterView) getView(), CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, final MethodChannel.Result result) { if (call.method.equals("ace_demo_user")) { if (call.arguments != null) { Log.e(TAG, "Flutter -> Android 回调内容:" + call.arguments.toString()); } else { Log.e(TAG, "Flutter -> Android 回调参数为空!"); } result.success("Android -> Flutter 接收回调后返回值:" + TAG); Toast.makeText(getActivity(), (call.arguments != null) ? "回调内容为:" + call.arguments.toString() : "回调参数为空!", Toast.LENGTH_SHORT).show(); } else { result.notImplemented(); } } }); } }
3. FlutterActivity
使用 Activity 方式同样需要获取 FlutterView 此时直接继承 FlutterActivity 或 FlutterFragmentActivity 即可;
public class MyFlutterActivity extends FlutterActivity { private static final String CHANNEL = "ace_demo_android_flutter"; private static final String TAG = "MyFlutterActivity"; private static final int REQUEST_CODE = 1000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { if (call.method.equals("ace_demo_user")) { if (call.arguments != null) { Log.e(TAG, "Flutter -> Android 回调内容:" + call.arguments.toString()); } else { Log.e(TAG, "Flutter -> Android 回调参数为空!"); } result.success("Android -> Flutter 接收回调后返回值:" + TAG); Intent intent = new Intent(); intent.putExtra("data", call.arguments!=null?call.arguments.toString():""); setResult(REQUEST_CODE, intent); MyFlutterActivity.this.finish(); } else { result.notImplemented(); } } }); } }
我们分析 FlutterFragment 和 FlutterActivity 时会发现,依旧是一层层封装的 FlutterView;
小菜测试 onMethodCall 中若有与 Flutter 中传递的相同 method name 时可以尝试获取传递参数;若此时需要向 Flutter 返回传递参数可以通过 result.success() 方法进行数据传递,若无需传递则可不设置当前方法;
小菜理解,MethodChannel 主要是由 Flutter 主动向 Android 原生发起交互请求,小菜理解相对于于原生为被动式交互较多;
EventChannel
EventChannel 可以由 Android 原生主动向 Flutter 发起交互请求,小菜理解相对于原生为主动式交互,类似于 Android 发送一个广播在 Flutter 端进行接收;其使用方式与 MethodChannel 类似,根据 FlutterView 进行绑定监听,与上述相似,小菜不分开写了;
EventChannel 是对 Stream 流的监听,通过 onListener 进行消息发送,通过 onCancel 对消息取消;
new EventChannel(flutterView, CHANNEL).setStreamHandler(new EventChannel.StreamHandler() { @Override public void onListen(Object arguments, final EventChannel.EventSink events) { events.success("我来自 " + TAG +" !! 使用的是 EventChannel 方式"); } @Override public void onCancel(Object arguments) { } });
Flutter 端通过 receiveBroadcastStream 进行数据流监听;分析源码得知,其内部同样是通过 invokeMethod 方法进行发送;listen 方法中,onData 为必须参数用作收到 Android 端发送数据的回调;onError 为数据接收失败回调;onDone 为接收数据结束回调;
StreamSubscription<T> listen(void onData(T event), {Function onError, void onDone(), bool cancelOnError});
class _MyHomePageState extends State<MyHomePage> { static const eventChannel = const EventChannel('ace_demo_android_flutter'); String _result = ''; StreamSubscription _streamSubscription; @override void initState() { super.initState(); _getEventResult(); } @override void dispose() { super.dispose(); if (_streamSubscription != null) { _streamSubscription.cancel(); } } _getEventResult() async { try { _streamSubscription = eventChannel.receiveBroadcastStream().listen((data) { setState(() { _result = data; }); }); } on PlatformException catch (e) { setState(() { _result = "event get data err: '${e.message}'."; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.title)), body: Center( child: Text('${_result}', style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, child: Icon(Icons.arrow_back))); } }
BasicMessageChannel
BasicMessageChannel 主要传递字符串和半结构化的数据交互;其编解码有多种类型,在使用时建议 Android 与 Flutter 两端一致;
- BinaryCodec:基本二进制编码类型;
- StringCodec:字符串与二进制之间的编码类型;
- JSONMessageCodec:Json 与二进制之间的编码类型;
- StandardMessageCodec:默认编码类型,包括基础数据类型、二进制数据、列表、字典等与二进制之间等编码类型;
Flutter -> Android
Flutter 端向 Android 端发送 send 数据请求,Android 端接收到后通过 replay 向 Flutter 端发送消息,从而完成一次消息交互;
// Flutter 端 static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec()); @override void initState() { super.initState(); _getBasicResult(); } _getBasicResult() async { final String reply = await basicChannel.send('ace_demo_user'); setState(() { _result = reply; }); } // Android 端 final BasicMessageChannel channel = new BasicMessageChannel<String> (flutterView, CHANNEL, StringCodec.INSTANCE); channel.setMessageHandler(new BasicMessageChannel.MessageHandler() { @Override public void onMessage(Object o, BasicMessageChannel.Reply reply) { reply.reply("我来自 " + TAG +" !! 使用的是 BasicMessageChannel 方式"); } });
Android -> Flutter
根据上述继续由 Android 端主动向 Flutter 端发送数据,Android 通过 send 向 Flutter 发送数据请求,Flutter 通过 setMessageHandler 接收后向 Android 端 return 返回结果,再由 Android 回调接收,从而完成一次数据交互;
public void send(T message) { this.send(message, (BasicMessageChannel.Reply)null); } public void send(T message, BasicMessageChannel.Reply<T> callback) { this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback)); }
分析源码 send 有两个构造函数,有两个参数的构造方法用来接收 Flutter 回调的数据;
// Flutter 端 static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec()); @override void initState() { super.initState(); _getBasicResult(); } _getBasicResult() async { final String reply = await channel.setMessageHandler((String message) async { print('Flutter Received: ${message}'); setState(() { _result = message; }); return "{'name': '我不是老猪', 'gender': 1}"; }); } // Android 端 channel.setMessageHandler(new BasicMessageChannel.MessageHandler() { @Override public void onMessage(Object o, BasicMessageChannel.Reply reply) { reply.reply("我来自 " + TAG +" !! 使用的是 BasicMessageChannel 方式"); channel.send("ace_demo_user"); //channel.send("ace_demo_user", new BasicMessageChannel.Reply() { // @Override // public void reply(Object o) { // Intent intent = new Intent(); // intent.putExtra("data", o!=null?o.toString():""); // setResult(REQUEST_CODE, intent); // MyFlutterViewActivity.this.finish(); // } //}); } });
注意事项
1. ensureInitializationComplete must be called after startInitialization
小菜在从 Android 到 Flutter 交互过程时,崩溃提示如下问题;
小菜发现在 Application 中需要使用 FlutterApplication,FlutterApplication 的作用就是通过调用 FlutterMain 的 startInitialization 方法进行初始化;
import io.flutter.app.FlutterApplication; public class MyApplication extends FlutterApplication { }
2. 注意交互返回中内容是否为空
小菜在测试 MethodChannel 时,invokeMethod 时尝试了一个参数和两个参数的构造,只有一个参数的 invokeMethod 是没有回调内容的,而小菜在 Android 端未判空,虽然没有报异常,但是后面的代码都没有执行,很基本的问题却困扰小菜很久,希望大家可以避免;
3. 多种 Platform Channel 共同使用
小菜测试过程中,多种 Platform Channel 可以共同使用,可以根据业务或场景的不同配合使用,提高效率;
小菜对 Android 与 Flutter 交互还不够深入,仍需进一步学习;如有错误请多多指导!
来源:阿策小和尚
欢迎闲来吐槽~
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Android接入支付宝和微信支付
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 前言 很多APP都需要支付功能,国内一般就是支付宝和微信了。目前这2种接入方式对于APP端来说都已经比较方便了,因为大部分的安全校验之类的逻辑都在服务端。 APP端总结起来就是三步走: 接入支付的库 接受服务端的订单信息,发起调用支付宝和微信 接收支付宝和微信的回调 支付宝接入 首先是接入支付宝的aar文件 比较坑的是支付宝还需要下载aar文件导入,而不是gradle里面一行依赖就能搞定的。 我们需要去官网下载最新的DEMO和SDK,官网地址在这里 然后把下载下来的aar包,放到项目目录下面的libs目录下,通过下面的gradle依赖进来 // 支付宝 SDK AAR 包所需的配置 compile(name: 'alipaySdk-15.6.0-20190226104104-noUtdid', ext: 'aar') 调用支付宝SDK的方法发起支付 调用支付宝SDK发起支付,只需要一个参数,就是服务端返回的订单信息。所以这里的支付顺序是先要我们调用服务端的接口创建一个...
- 下一篇
2019最新iOS面试题及答案
1. Object-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-C的类不可以多重继承;可以实现多个接口,通过实现多个接口可以完成C++的多重继承;Category是类别,一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。 2. import 跟#include 又什么区别,@class呢, #import<> 跟 #import“”又什么区别? 答:#import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入,相当于#include和#pragma once;@class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含;#import<>用来包含系统的头文件,#import“”用来包含用户头文件。 3. 属性readwrite,readonly,assign,retain,copy,n...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Red5直播服务器,属于Java语言的直播服务器
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,CentOS7官方镜像安装Oracle11G
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8安装Docker,最新的服务器搭配容器使用