草庐IT

【Flutter 专题】49 图解 Flutter 与 Android 原生交互 #yyds干货盘点#

阿策小和尚 2023-03-28 原文
      小菜上一篇简单学习了一下 Android 原生接入 Flutter Module,现在学习一下两者之间的数据交互;

      FlutterAndroid/iOS 之间信息交互通过 Platform Channel 进行桥接;Flutter 定义了三种不同的 Channel;但无论是传递方法还是传递事件,其本质上都是数据的传递;

1. MethodChannel:用于传递方法调用;
2. EventChannel:用于数据流信息通信;
3. BasicMessageChannel:用于传递字符串和半结构化的信息;
      每种 Channel 均包含三个成员变量;

  1. name:代表 Channel 唯一标识符,Channel 可以包含多个,但 name 为唯一的;
  2. messager:代表消息发送与接收的工具 BinaryMessenger
  3. codec:代表消息的编解码器;       小菜以上一节 Android 原生集成 Flutter Module 为基础,针对不同的 Channel 进行学习尝试;且小菜通过 View / Fragment / Activity 三种原生加载方式进行测试;

MethodChannel

      小菜在 Flutter 页面,点击右下角按钮,将消息传递给 AndroidMethodChannel 通过 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(); } } }); } }       我们分析 FlutterFragmentFlutterActivity 时会发现,依旧是一层层封装的 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 主要传递字符串和半结构化的数据交互;其编解码有多种类型,在使用时建议 AndroidFlutter 两端一致;

  1. BinaryCodec:基本二进制编码类型;
  2. StringCodec:字符串与二进制之间的编码类型;
  3. JSONMessageCodecJson 与二进制之间的编码类型;
  4. StandardMessageCodec:默认编码类型,包括基础数据类型、二进制数据、列表、字典等与二进制之间等编码类型;

Flutter -> Android

      Flutter 端向 Android 端发送 send 数据请求,Android 端接收到后通过 replayFlutter 端发送消息,从而完成一次消息交互;

// 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 通过 sendFlutter 发送数据请求,Flutter 通过 setMessageHandler 接收后向 Androidreturn 返回结果,再由 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
      小菜在从 AndroidFlutter 交互过程时,崩溃提示如下问题;

      小菜发现在 Application 中需要使用 FlutterApplicationFlutterApplication 的作用就是通过调用 FlutterMainstartInitialization 方法进行初始化;

import io.flutter.app.FlutterApplication; public class MyApplication extends FlutterApplication { }
2. 注意交互返回中内容是否为空
      小菜在测试 MethodChannel 时,invokeMethod 时尝试了一个参数和两个参数的构造,只有一个参数的 invokeMethod 是没有回调内容的,而小菜在 Android 端未判空,虽然没有报异常,但是后面的代码都没有执行,很基本的问题却困扰小菜很久,希望大家可以避免;

3. 多种 Platform Channel 共同使用
      小菜测试过程中,多种 Platform Channel 可以共同使用,可以根据业务或场景的不同配合使用,提高效率;


      小菜对 AndroidFlutter 交互还不够深入,仍需进一步学习;如有错误请多多指导!

来源:阿策小和尚 欢迎闲来吐槽~

有关【Flutter 专题】49 图解 Flutter 与 Android 原生交互 #yyds干货盘点#的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 交互式 shell 中有多行? - 2

    这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式ruby​​shell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f

  2. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  3. 【云原生】SpringCloud-Spring Boot Starter使用测试 - 2

    目录SpringBootStarter是什么?以前传统的做法使用SpringBootStarter之后starter的理念:starter的实现: 创建SpringBootStarter步骤在idea新建一个starter项目、直接执行下一步即可生成项目。 在xml中加入如下配置文件:创建proterties类来保存配置信息创建业务类:创建AutoConfiguration测试如下:SpringBootStarter是什么? SpringBootStarter是在SpringBoot组件中被提出来的一种概念、简化了很多烦琐的配置、通过引入各种SpringBootStarter包可以快速搭建出一

  4. ruby-on-rails - Rails 5 升级 :/actionpack-5. 0.0/lib/action_controller/test_case.rb:49:in `initialize':参数数量错误(0 代表 2)(ArgumentError) - 2

    我最近正在进行Rails5升级,当我尝试启动Rails控制台时遇到了这个错误:/actionpack-5.0.0/lib/action_controller/test_case.rb:49:ininitialize':wrongnumberofarguments(0for2)(ArgumentError)当前bundleupdaterails已经完成了gem依赖项的解决,足以更新到5.0.0,rspec正在运行(尽管我正在修复很多中断)。我也可以运行railss没有错误。这里是代码中断行:https://github.com/rails/rails/blob/master/action

  5. Flutter 环境变量配置和flutter doctor中的错误解决 - 2

    一、环境变量右键点击我的电脑-属性:然后找到环境变量 1.Android的SDK不在C盘的话需要额外配这个到用户环境变量:ANDROID_HOMED:\AndroidSDK2.然后在系统变量:Path中添加一条这样的值        D:\Flutter\flutter\bin             这个值写flutter包解压的实际地址即可 3.在系统变量中添加两个镜像变量:        变量名:FLUTTER_STORAGE_BASE_URL      变量值:https://storage.flutter-io.cn        变量名:PUB_HOSTED_URL      变量

  6. ruby - 如何与 Ruby 中的 Perl 程序交互? - 2

    据我了解,在Ruby和Perl之间没有“桥梁”可以让您直接从Ruby调用Perl函数。据我了解,要从Ruby调用Perl程序,只需将其放在反引号中(即result=`./helloWorld.pl`)。但是,这不允许与Perl程序交互(即您不能与提示交互或提供输入)。我的问题如下:有没有什么方法可以从Ruby向Perl程序提供输入(除了参数)?Ruby和Perl之间没有桥梁,我错了吗?在导航提示时与程序的标准输入交互似乎是错误的方式,我正在处理的程序设计良好,并且具有包含适当Perl函数的库。 最佳答案 有Inline::Ruby模

  7. “网安三人行”盘点:软件供应链安全的那些事儿 - 2

    2022年伊始,默安科技联合数世咨询举办以“软件供应链安全的时与势”为主题的访谈活动,由数世咨询创始人李少鹏主持,邀请贝壳安全研发负责人李文鹏、北京邮电大学副教授张文博、默安科技副总裁沈锡镛三位行业大咖做客网安小酒馆,从产业、企业、学术的不同维度,共同探讨软件供应链安全建设的新思路,为业界呈现了一场开年网安盛宴。随着全球软件供应链安全事件频发,软件供应链安全逐渐成为业界关注焦点,也成为影响国家重要信息系统安全与关键信息基础设施安全的重要因素,以及网络安全保障体系和能力建设的重要环节。嘉宾们围绕软件供应链安全发展的主要驱动力、关基行业中的实施现状和落地难点、产学研成果转化、软件供应链安全的重要性

  8. ruby - IRb:如何使用预加载类启动交互式 ruby​​ session - 2

    在我采用Ruby语言的过程中,我花了很多时间在IRb中。太棒了!但是,由于我不是很清楚它的功能,并且对Ruby仍然是个“笨蛋”,所以我想知道以下内容:如何在不重新启动IRb的情况下“刷新”session(或者这是不可能的)。如何配置IRb加载一堆源文件“hello.rb”和“hello_objects.rb”,即在启动时?我在这些方面投入了大量工作,如果知道加载这些类的速记,而无需再次为每个类手动键入“加载”,那就太好了。 最佳答案 我不确定是否可以“刷新”session。但是,您可以像这样加载您的类:irb-r'hello.rb'

  9. ruby - 如何让 Ruby 找到原生库? - 2

    我在/usr/local/lib中安装了一些本地库。我现在正在尝试安装一个需要这些的gem,以便正确构建,但是gem构建失败,因为它找不到图书馆。gem的extconf.rb文件试图确认它可以找到库have_library()但由于某种原因失败了。我尝试设置一堆环境变量,但似乎没有任何效果:irb(main):003:0>require'mkmf'=>trueirb(main):004:0>have_library('gecodesearch')checkingformain()in-lgecodesearch...no=>falseirb(main):005:0>ENV['LD_LI

  10. ruby - Lisp - 是否适合网络编程/应用程序(交互式)? ruby 的方式是? php的方式是? - 2

    Lisp是否适合Web编程/应用程序(交互式),就像ruby​​和php一样?需要考虑的事情是:易于使用可部署性难度(尤其是对于编程初学者而言)(编辑)在阅读PaulGraham'sessay之后,我特别提到了CommonLisp.将是我的第一门编程语言。在这方面。这样做合适吗?我听说Clojure的宏功能不如CommonLisp的强大,这就是我尝试学习Clojure的原因。它教授编程并且非常强大。 最佳答案 Lisp是一个语系,而不是单一的语言。为了稍微回答您的问题,是的,存在用于各种Lisp方言的Web框架,例如用于Common

随机推荐