1、原因

刚接触flutter的时候,以为flutter是一个全新开发app的语言,独立于Android原生之外的操作,入坑之后发现不是的。因为Flutter不能完成所有Native的功能,比如不同平台的底层服务如电量变化、网络连接变化以及最近项目中使用的直接拨号功能以及地图功能都无法用flutter实现其功能,因此需要借助Native层的接口来实现flutter的开发,所以Flutter提供了一套Platform Channel的机制,来满足Flutter与Native通信的需求。

2、PlatformChannel架构

图中可以看到,Flutter是Client端,Native是Host,Client和host通信是通过PlatformChannel,Client通过PlatformChannel向Host发送消息,Host监听PlatformChannel并接收消息,然后将响应结果发送给Client。即Flutter 调用 Native 方法时,需要传递的信息是通过平台通道传递到宿主端的,Native 收到调用的信息后方可执行指定的操作。消息和响应以异步方式传递,以确保UI不阻塞。另外,PlatformChannel是双工的,这意味着Flutter和Native可以交替做Client和Host。

2.1 flutter与原生通信方式

1、EventChannel是一种native向flutter发送数据的单向通信方式,flutter无法返回任何数据给native。主要用于native向flutter发送手机电量变化、网络连接变化、陀螺仪、传感器等。

2、BaseMessageChannel :用于传递字符串和半结构化的信息(在大内存数据块传递的情况下使用)

3、通过MethodChannel来实现,MethodChannel支持数据双向传递,有返回值。本文只讲这个,因为项目中只用了这个。

总的来说就是:这三种方式均各有适用的场景:MethodChannel用于native与flutter的方法调用,EventChannel用于native单向的向flutter发送广播消息,BasicMessageChannel用于native与flutter之间的消息互发。

3、通道如何建立

3.1 项目结构

首先了解下flutter创建的项目结构

android 就是我们开发安卓部分的位置

iOS 就是我们开发 iOS 的位置

lib 是与 Android 、iOS 联调的位置。也可以理解为Flutter 实现的位置

因此flutter与android原生的通信代码就写在这2个目录文件下,iOS的就让iOS的同学去写就完事了。

3.2 建立数据通信通道

Android原生数据与flutter通道建立的基础是在实现FlutterPlugin之上的,看下FlutterPlugin代码注释

public interface FlutterPlugin {/ * *@code FlutterPlugin与@link FlutterEngine实例相关联。*
{@code FlutterPlugin}可能需要的相关资源是通过{@code提供的
*绑定}。{@code binding}可以被缓存和引用,直到{@link
#onDetachedFromEngine(FlutterPluginBinding)}被调用并返回。
* /void onAttachedToEngine(@NonNull FlutterPluginBinding binding);/ * *
*这个{@code FlutterPlugin}已从{@link FlutterEngine}实例中删除。
* <p>传递给这个方法的{@code binding}与传递给{@link的实例相同
* # onAttachedToEngine (FlutterPluginBinding)}。在此方法中,它作为
*方便。{@code绑定}可能在这个方法的执行过程中被引用,但是它
*不能在此方法返回后被缓存或引用。
<p>{@code FlutterPlugin}s应该释放该方法中的所有资源。
* /void onDetachedFromEngine(@NonNull FlutterPluginBinding binding);}

显而易见,onAttachedToEngine是FlutterPlugin与FlutterEngine实例关联,主要实现了发送二进制消息和设置消息处理回调的方法,其底层实现的目的就是让flutter层与android原生的交互进行绑定,而onDetachedFromEngine这个是释放资源。

3.3 消息信使:BinaryMessenger

onAttachedToEngine是绑定双向的通道后,数据的通信则需要BinaryMessenger来处理,因为BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。可以看下FlutterPlugin目录结构组成如下

可以明显看到 onAttachedToEngine方法里面传入参数FlutterPluginBinding,而FlutterPluginBinding里面可以获取到BinaryMessenger对象,因此目前流程应该是实现FlutterPlugin接口,重写onAttachedToEngine方法,通过FlutterPluginBinding获取BinaryMessenger对象,通过BinaryMessenger对象是实现与flutter的数据通信。

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
class MyFlutterPlugins : FlutterPlugin {override fun onAttachedToEngine(binding: FlutterPluginBinding) {val binaryMessenger = binding.binaryMessenger}override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}

3.4 建立插件调用唯一通道

flutter与Android原生不可能只存在调用一个调用的插件,所以建立通道后,需要创建双方可以识别的唯一标识。在native和flutter之间,数据的交互是双向的。我们可以从Native层调用flutter层的dart代码,也可以在flutter层调用Native的代码。而作为通讯桥梁就是MethodChannel了,这个类在初始化的时候需要注册一个渠道值。这个值必须是唯一的,并且在使用到的Native层和Flutter层互相对应。看下MethodChannel的注释

/**
创建一个与指定的{@link BinaryMessenger}关联的新通道
*指定的名称和标准{@link MethodCodec}。
*
@param messenger a {@link BinaryMessenger}。
@param name一个通道名字符串。
*/
public MethodChannel(BinaryMessenger messenger, String name) {this(messenger, name, StandardMethodCodec.INSTANCE);
}

可以看到MethodChannel方法里面有name字段为一个通道名字符串。而BinaryMessenger这个上面已经获取到了,现在就是要传入双方约定的唯一标识传进去,比如传入“myFlutterPlugin”作为唯一标识

  val methodChannel=MethodChannel(binaryMessenger, "myFlutterPlugin")

拿到MethodChannel对象后,我们需要通过setMethodCallHandler回调接口对flutter调用Android中的方法进行监听,通过回调中的MethodCall对象方法名判断、获取方法参数,并且返回调用结果。至此我们的通道代码可以写到如下地步

import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
import io.flutter.plugin.common.MethodChannel
class MyFlutterPlugins : FlutterPlugin {override fun onAttachedToEngine(binding: FlutterPluginBinding) {//获取binaryMessenger对象val binaryMessenger=binding.binaryMessenger//创建methodChannel对象val methodChannel=MethodChannel(binaryMessenger, "myFlutterPlugin")//使用setMethodHandle对对方调用自己的方法进行监听,// 通过回调中的MethodCall对象方法名判断、获取方法参数,并且返回调用结果。methodChannel?.setMethodCallHandler { call, result ->if (call.method == "printLog") {printLogFromAndroid();}}}override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}private fun printLogFromAndroid(){Log.e("TAG","调用了android原生的数据")}

其中回调接口中的call和result参数是MethodCallHandler接口的方法体

 public interface MethodCallHandler {@UiThreadvoid onMethodCall(@NonNull MethodCall call, @NonNull Result result);}
前者判断调用的方法,后面返回处理的结果,这个后面会提下。现在FlutterPlugin里面的代码已经撸完了,然后就是注册这个插件了

3.5 FlutterPlugin注册

应用创建时,MainActivity 默认继承FlutterActivity ,

class MainActivity: FlutterActivity() {}

而FlutterActivity里提供了注册引擎的接口

void configureFlutterEngine(@NonNull FlutterEngine flutterEngine)

所以我们在MainActivity里重写configureFlutterEngine方法,然后添加我们的插件就行了

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity: FlutterActivity() {override fun configureFlutterEngine(flutterEngine: FlutterEngine) {flutterEngine.plugins.add(MyFlutterPlugins())}
}

至此,android原生端的代码已经完成了。

以上是我们手动编写通道插件手动注册的,但是flutter也提供了一些第三方调用原生的插件,那它们是如何自动注册插件的呢?

3.6 三方插件通道自动注册

创建flutter项目的时候 android目录下会自动新建一个io.flutter.plugins目录下GeneratedPluginRegistrant类里面会有个registerWith方法,然后自动添加一些三方调用原生的插件,举个栗子:

在flutter目录里pubspec.yaml里添加高德定位调用原生插件,如下

执行flutter pub get后会在android的目录下去注册这个通道,如下

以上代码不用手动撸它,撸了也会被自动覆盖。这个操作是flutter项目自动生成的且运行的时候自动注册的,之前说过flutter创建项目后,Android项目里的MainActivity默认是继承flutterActivity的,所以启动MainActivity的时候,其父类flutterActivity的时候会通过FlutterActivityAndFragmentDelegate这个代理类去配置通信通道的调用。

这里便是自动注册的入口,通过在FlutterActivityAndFragmentDelegate的onAttach里配置,

configureFlutterEngine是Host接口的一个方法,因为项目继承activity 所以其接口方法的具体实现在FlutterActivity里寻找,

顺藤摸瓜式看源码,就能找到在FlutterActivity类中找到configureFlutterEngine的实现体,里面可以看到内部调用了GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);而该方法就是通过反射机制去调用android目录下 io.flutter.plugins下GeneratedPluginRegistrant类下的默认方法registerWith(),继续顺藤摸瓜看

所以说为什么我们在flutter里面添加一些三方调用原生的插件的时候不需要手动配置通道的注册的原因就是在这里,然后需要注意的一点就是 我们自定义flutter与android原生通道的时候,需要去重写configureFlutterEngine方法并去注入我们的自己插件 这样会覆盖了第三方的插件,所以我们需要手动添加第三方的插件注册

4、flutter调用

4.1 flutter如何调用android的方法

首先一样的需要建立通道 ,还是用MethodChannel方法,

注册渠道:在两端同时创建一个MethodChannel对象,注册相同的字符串值

class MethodChannel {const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ]): assert(name != null),assert(codec != null),_binaryMessenger = binaryMessenger;
}

可以看到flutter里MethodChannel需要传入name,而android原生定义MethodChannel里也需要传入唯一标识name,所以此处name就是android原生传入的myFlutterPlugin,so 如下定义

var _channel = MethodChannel('myFlutterPlugin');

MethodChannel对象实例有了,接着就是实现调用的具体方法了

Future print() async{_channel.invokeMethod("printLog");
}

invokeMethod源码注释很长,自行查看,

Future<T?> invokeMethod<T>(String method, [ dynamic arguments ]) {return _invokeMethod<T>(method, missingOk: false, arguments: arguments);
}

两个参数,method就是要调用的方法名,arguments 就是传入的参数,这里没有传,因为前面原生我没有调用传参的方法。直接写个按钮调用下

ElevatedButton(onPressed: ()=>print(), child: Text("调用原生方法")),

打印结果:

 E/TAG: printLog: 调用了android原生的方法

4.2 调用原生有参方法

还是拿上面的例子说明

之前讲MethodChannel的时候 没有具体说明它的回调接口里的参数,现在look下

MethodChannel->setMethodCallHandler->MethodCallHandler->onMethodCall->
MethodCall

主要看下这个MethodCall

public final class MethodCall {/** The name of the called method. */public final String method;/*** Arguments for the call.** <p>Consider using {@link #arguments()} for cases where a particular run-time type is expected.* Consider using {@link #argument(String)} when that run-time type is {@link Map} or {@link* JSONObject}.*/public final Object arguments;/*** Creates a {@link MethodCall} with the specified method name and arguments.** @param method the method name String, not null.* @param arguments the arguments, a value supported by the channel's message codec.*/public MethodCall(String method, Object arguments) {if (BuildConfig.DEBUG && method == null) {throw new AssertionError("Parameter method must not be null.");}this.method = method;this.arguments = arguments;}

method则是我们调用的方法,而arguments就是我们接收flutter传过来的参数类型

所以结合上面的例子,传入的参数和方法名都是通过MethodCall来实现的,那么改下上面的例子

import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
import io.flutter.plugin.common.MethodChannel
class MyFlutterPlugins : FlutterPlugin {var methodChannel: MethodChannel?=nulloverride fun onAttachedToEngine(binding: FlutterPluginBinding) {methodChannel=MethodChannel(binding.binaryMessenger, "myFlutterPlugin")methodChannel?.setMethodCallHandler { call, result ->if (call.method == "printLog") {val key=  call.argument<String>("key")!!printLogFromAndroid(key);}}}override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}private fun printLogFromAndroid(key:String){Log.e("TAG","调用了android原生的数据 $key")}
}

传入参数的标识码为“key",通过“key”获取flutter传入的值,同时也修改下flutter的代码

其它都不变,就改下调用的方法

Future print() async{_channel.invokeMethod("printLog",{"key":"华锐捷"});
}

最后打印下:

E/TAG: 调用了android原生的数据 华锐捷

4.3 调用android原生方法返回数据给flutter

实现setMethodCallHandler回调接口的时候,目前我们只使用了MethodCall,result还有动过,现在看下源码里Result的注释

public interface Result {/*** Handles a successful result.** @param result The result, possibly null. The result must be an Object type supported by the*     codec. For instance, if you are using {@link StandardMessageCodec} (default), please see*     its documentation on what types are supported.*/@UiThreadvoid success(@Nullable Object result);/*** Handles an error result.** @param errorCode An error code String.* @param errorMessage A human-readable error message String, possibly null.* @param errorDetails Error details, possibly null. The details must be an Object type*     supported by the codec. For instance, if you are using {@link StandardMessageCodec}*     (default), please see its documentation on what types are supported.*/@UiThreadvoid error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);/** Handles a call to an unimplemented method. */@UiThreadvoid notImplemented();
}

很显然,就是flutter调用原生接口是否成功或失败 的返回接口,就是把flutter调用android原生的结果包括调用android原生方法的返回结果返回给flutt,evoid success(@Nullable Object result);返回的是object类型的

修改下 android原生调用的方法,带有返回参数

private fun printLogFromAndroid(key:String):String{Log.e("TAG","调用了android原生的数据 $key")return "大华"
}

并且 添加setMethodCallHandler result回调接口

result.success( Object result))

代码最后撸成以下:

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
import android.util.Log
import io.flutter.plugin.common.MethodChannel
class MyFlutterPlugins : FlutterPlugin {var methodChannel: MethodChannel?=nulloverride fun onAttachedToEngine(binding: FlutterPluginBinding) {methodChannel=MethodChannel(binding.binaryMessenger, "myFlutterPlugin")methodChannel?.setMethodCallHandler { call, result ->if (call.method == "printLog") {val key=  call.argument<String>("key")!!result.success(printLogFromAndroid(key))}}}override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}private fun printLogFromAndroid(key:String):String{Log.e("TAG","调用了android原生的数据 $key")return "大华"}
}

调用原生接口printLogFromAndroid 传入参数key 并返回“大华”,setMethodCallHandler的MethodCallHandler的中result返回该数据给flutter ,下面实现下flutter的实现

final _channel = const MethodChannel('myFlutterPlugin');
void  printMsg() async{String result= await  _channel.invokeMethod("printLog",{"key":"华锐捷"}) ;print("来自Android原生返回的数据;$result");
}

传入key为“华锐捷”的字段给原生接口,原生接口返回“大华”给flutter ,然后flutter调用该方法

说明 flutter端传给android原生的数据发送成功,并成功接收到android原生返回的数据了。

至此,关于flutter与Android通信的代码已经撸完了,下面说下一些常见的需求

5、flutter 如何跳转android原生界面

原理都和之前的调用的方法一样,主要是如何获取界面跳转的上下文对象,这里分2种,一种是context.startActivity进行界面跳转,另外一种就是activity对象进行跳转

5.1 获取context对象

实现FlutterPlugin的接口的时候,有onAttachedToEngine方法里面的参数FlutterPluginBinding 里面有applicationContext参数,所以context可以从此处获取

override fun onAttachedToEngine(binding: FlutterPluginBinding) {mContext= binding.applicationContext
}

获取到上下文后界面直接走android原生 intent跳转逻辑了,

private fun intentTest(context: Context) {val intent=Intent()intent.flags=Intent.FLAG_ACTIVITY_NEW_TASKintent.setClass(context.applicationContext, MainActivity2::class.java)context.startActivity(intent)
}

注意此处需要添加 intent.flags=Intent.FLAG_ACTIVITY_NEW_TASK、否则会报Calling startActivity() from outside of an Activity context异常,不晓得你们报不报错,反正我的是崩了。Context中有一个startActivity方法,Activity继承自Context,重载了startActivity方法。如果使用Activity的startActivity方法,不会有任何限制,而如果使用Context的startActivity方法的話,就需要开启一个新的的task,遇到这个异常,是因为使用了Context的startActivity方法。解决办法是,加一个flag。intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );这样就可以在新的task里面启动这个Activity了。然后调用的方法还是一样的 在setMethodCallHandler 判断调用的方法名,然后调用跳转界面的代码。其次,也可以在application里获取全局的context对象,然后拿来用就行了。

5.2 获取activity对象

一个新的接口ActivityAware ,主要注意

void onAttachedToActivity(@NonNull ActivityPluginBinding binding);
void onDetachedFromActivity();

前者绑定activity时调用,后者与activity分离时回调 ,而前者ActivityPluginBinding参数中则有包含当前activity的实例对象

所以要获取activity实例对象,先实现ActivityAware接口,然后实现onAttachedToActivity方法,从ActivityPluginBinding 获取到activity对象

class MyFlutterPlugins : ActivityAware{var activity: Activity?=nulloverride fun onAttachedToActivity(binding: ActivityPluginBinding) {activity=binding.activity}}

然后修改下界面跳转的代码逻辑,此处不需要添加flag了

private fun intentTest(activity: Activity) {val intent=Intent()intent.setClass(activity, MainActivity2::class.java)activity.startActivity(intent)
}

调用的逻辑和之前的一样,不多BB了

另外onDetachedFromActivity接口可以做一些回收操作,比如activity回收,免得内存泄漏

说到内存泄漏,还有个application类的Application.ActivityLifecycleCallbacks接口

可以在ActivityAware中的activity绑定回调中注册,获取activity的生命周期,更好地去维护activity的生命周期状态,

override fun onAttachedToActivity(binding: ActivityPluginBinding) {activity = binding.activity// 注册生命周期回调, 保证界面初始化的时候对应的是正确的activity状态activity?.application?.registerActivityLifecycleCallbacks(this)}

往往能坚持到最后的人不是靠着最初的三分激情,而是恰到好处的喜欢和投入!

flutter与android原生通信相关推荐

  1. flutter调用android 原生TextView

    https://blog.csdn.net/zl18603543572/article/details/95983215 本文链接:https://blog.csdn.net/zl1860354357 ...

  2. Flutter与Android原生交互

    记录一下小白的学习之路,图片都是走过的坑 文末附demo地址 文章目录 创建project Android端(native) Flutter端 Android调用flutter 创建project 首 ...

  3. Flutter 和 Android 原生的区别

    什么是Flutter ? Flutter 是一个软件开发工具包 (SDK),用于构建适用于 iOS 和 Android 的现代移动应用程序,可帮助开发人员和设计人员.Flutter 被归类为" ...

  4. Flutter中嵌入Android 原生TextView

    更多文章请查看 flutter从入门 到精通 本篇文章 中写到的是 flutter 调用了Android 原生的 TextView 案例 添加原生组件的流程基本上可以描述为: 1 android 端实 ...

  5. Flutter与原生通信

    -- Flutter作为一个跨平台框架,一经问世,便受到众多开发的追捧,发展至今相信已经有很多公司或个人将其加入自己的项目,进行混合开发,那么FLutter如何与原生通信呢? -- 本次就以Andro ...

  6. android studio放置在函数上面看_像写Flutter一样开发Android原生应用

    要问到Flutter和Android原生App,在开发时有何区别,编程方式是绕不开的话题.Flutter采用声明式编程,Android原生开发则采用命令式编程. 声明式编程 VS. 命令式编程 我们首 ...

  7. 像写Flutter一样开发Android原生应用

    要问到Flutter和Android原生App,在开发是有何区别,编程方式是绕不开的话题.Flutter采用声明式编程,Android原生开发则采用命令式编程. 声明式编程 VS. 命令式编程 我们首 ...

  8. android蓝牙通信_Flutter通过BasicMessageChannel实现Flutter 与Android iOS 的双向通信

    题记: --不到最后时刻,千万别轻言放弃,无论结局成功与否,只要你拼博过,尽力过,一切问心无愧. 通过 Flutter 来进行移动应用开发,打包 Android .iOS 双平台应用程序,在调用如相机 ...

  9. RN 与原生通信(Android篇)

    一.RN 调用安卓代码(简单的实现方式) RN 调用 android 代码大致分为以下几个步骤: 1.用android studio 打开一个已经创建好的 RN 项目: 2.创建一个类 CommonM ...

最新文章

  1. 多项式输出(NOIP2009 普及组第一题)
  2. java中factory_JAVA工厂方法模式(Factory Method)
  3. OTA江湖浪潮再起,世界邦的出境定制自由行之路难以亨通?
  4. 使用tensorflow训练数据时遇到的问题总结
  5. hashCode()方法的性能优化
  6. DISCUZ x2.5 插件实现DIY功能,让页面也可以自由设计
  7. [vue] ajax、fetch、axios这三都有什么区别?
  8. Apache安装问题:configure: error: APR not found . Please read the documentation
  9. python2和3语法区别_python2和3语法区别
  10. 为什么很多人C语言学不下去
  11. ashx在web.config中如何配置_如何在 Istio 1.6 中配置 Prometheus-Operator 和抓取指标
  12. mysql不显示默认密码_免安装版mysql不出现默认密码状况(基于mysql8.0)
  13. 雷达的正交波形设计matlab源码,雷达系统设计MATLAB仿真
  14. sicktim571操作手册_TIM中文操作手册.PDF
  15. java.library.path设置无效
  16. kvm使用virsh iface-bridge ens33 br0命令建立桥接网卡br0报错error:Failed to start bridge interface br0
  17. 用数据告诉你,哪位导演是漫威影片中的票房收割机?
  18. 多元函数的极限,连续,偏导数,全微分之间的关系(学习笔记)
  19. 洛谷 P2327 [SCOI2005] 扫雷
  20. 区块链产物 的安全是否真的无懈可击!

热门文章

  1. php仿苹果,关于8个超炫酷仿苹果应用的HTML5动画的图文详解
  2. 12.寻光集后台管理系统-库存信息(后端)
  3. VGA线 1080P之伤 中秋节篇
  4. 屏幕录像软件有哪些?操作简单的屏幕录像方法推荐
  5. wordpress新留言微信提醒
  6. 磁带储存拥有的4大优势
  7. CSR867x — 实现SPP数据收发
  8. 名人养生贴网络疯转 跟李嘉诚们学做长寿优等生
  9. Python 把几张图片拼接成一张图片,并且写上文字
  10. 日常使用计算机出现的小问题(一)win10操作系统设备和驱动器栏目中出现空白图标该如何删除?删除一次刷新又重新出现