Flutter 说到底只是一个 UI 框架,很多功能都需要通过原生的 Api 来实现,那么就会涉及到 Flutter 和 Native 的交互,因为本人不懂 iOS 开发,所以只能讲下 Flutter 同 Android 的交互。
Android 项目配置 Flutter 依赖
既然是互相交互,那么需要准备一个 Android 项目。接着就需要创建 flutter module,让 Android 项目依赖,创建的方法可以参考官网 Flutter Wiki,虽然是官网提供的方法,但是完全按照这个步骤来,还是会有坑的,这边就慢慢一步步解决坑。
如果你用的是 Android Studio 进行开发的话,直接打开底部的 Terminal,直接创建 flutter module 依赖
flutter create -t module flutter_native_contact 至于 module 名可以随意填写,module 创建完后结构大概是这样的

接着切换到 module 下的 .android 文件夹,接着有坑来了,官网提供的方法是 ./gradlew flutter:assembleDebug 可能会提示命令不存在,那么直接通过 gradlew flutter:assembleDebug 来运行,等它自动跑完后,打开根目录下的 settings.gradle 文件,加入官网提供的 gradle 代码
1  | setBinding(new Binding([gradle: this])) // new  | 
你以为这里没坑,真是图样图森破,没坑是不可能的,编译器大爷可能会给你甩这么个错误

很明显可以看出是找不到我们的文件,所以把文件名路径给补全
1  | evaluate(new File( // new  | 
接着打开原有项目下,原有项目下,原有项目下的 app 中的 build.gradle 文件,在 android 下加上如下代码
1  | compileOptions {  | 
这个必须要加,不要问为什么,我也不知道为什么,最后在项目下添加 flutter module 的依赖就完成了。这个过程告诉我们一个什么道理呢?*不要以为官网的都对,官网讲的也不是完全可信的,时不时给你来个坑就能卡你老半天。
原生界面加载 Flutter 页面
那么如何在原生界面显示 Flutter 界面呢,这个就需要通过 FlutterView 来实现了,Flutter 这个类提供了 createView 和 createFragment 两个方法,分别用于返回 FlutterView 和 FlutterFragment 实例,FlutterFragment 的实现原理也是通过 FlutterView 来实现的,可以简单看下 FlutterFragment 的源码
1  | /**  | 
createFragment 方式加载
在原生页面显示 Flutter 界面的第一种方式就是加载 FlutterFragment,看个比较简单的例子吧
1  | <?xml version="1.0" encoding="utf-8"?>  | 
在 Activity 可以直接通过返回 FlutterFragment 加载到 FrameLayout 即可
1  | class MainActivity : AppCompatActivity() {  | 
这样就把 Flutter 页面加载到原生界面了,会通过传递的路由值在 dart 层进行查找,所以接着就需要编写 Flutter 界面
1  | /// runApp 内部值也可以直接传入 _buildWidgetForNativeRoute 方法  | 
运行后可以看到页面加载出来了,不过会有一段时间的空白,这个在正式打包后就不会出现,所以不必担心。最后的页面应该是这样的

createView 方式加载
接着看下 createView 方法,说白了,第一种方法最后还是会通过该方式实现
1  | 
  | 
通过 createView 方法返回的 FlutterView,通过设置 Layoutparams 参数就可以添加到相应的布局上,还有一种直接通过 addContentView 方式进行加载,这里直接修改原有代码,
1  | override fun onCreate(savedInstanceState: Bundle?) {  | 
但是通过这样加载的话,那么整个页面都是 flutter 的页面。那么之前的效果的 FAB 则不会被加载出来了,即使没有省略 setContentView(R.layout.activity_main) 方法,这个页面的 xml 布局也会被覆盖。
PlantformChannel
那么能够在原生界面显示 flutter 页面了,如何互相交互呢,这就需要通过 PlantformChannel 来执行了,PlantformChannel 主要有三种类型,BasicMessageChannel,MethodChannel,EventChannel。通过查看源码可以发现,三个 Channel 的实现机制类似,都是通过 BinaryMessenger 进行信息交流,每个 Channel 通过传入的 channel name 进行区分,所以在注册 Channel 的时候必须要保证 channel name 是唯一的,同时需要传入一个 BinaryMessageHandler 实例,用于传递信息的处理,当 Handler 处理完信息后,会返回一个 result,然后通过 BinaryMessenger 将 result 返回到 Flutter 层。如果需要深入理解这边推荐一篇文章深入理解Flutter PlatformChannel
接下来直接看例子吧,在创建 PlatformChannel 的时候需要传入一个 BinaryMessenger 实例,通过查看 FlutterView 的源码可以发现,FlutterView 就是一个 BinaryMessenger 在 Android 端的实现,所以呢,可以直接通过前面介绍的 Flutter.createView 方法获取注册 Channel 时的 BinaryMessenger 实例了,真是得来全部费工夫~因为通信的方法可能在多个界面会使用,所以还是封装一个通用类来处理会比较合理
BasicMessageChannel
BasicMessageChannel 用于传递字符串和半结构化的信息。
1  | class FlutterPlugin(private val flutterView: FlutterView) :BasicMessageChannel.MessageHandler<Any>{  | 
接着就需要有个 FlutterView 用来注册,新建一个 Activity,用于加载 Flutter 页面
1  | class ContactActivity : AppCompatActivity() {  | 
那么我们就要在 Flutter 界面的 _buildWidgetForNativeRoute 方法加入新路由值对应的界面
1  | Widget _buildWidgetForNativeRoute(String route) {  | 
最后的效果小伙伴可以自行执行,点击按钮后会弹出吐司,吐司内容就是 Flutter 传递的信息,同时在控制台可以看到从原生层返回的信息。
MethodChannel
MethodChannel 用于传递方法调用(method invocation)
直接在上述例子中进行修改,例如在 Flutter 页面中实现 Activity 的 finish 方法,并传递参数到前一个界面,先做 Flutter 页面的修改,在 AppBar 上增加一个返回按钮,用于返回上层页面
1  | class FlutterContactPage extends StatelessWidget {  | 
同时,我们需要在 FlutterPlugin 这个类中,做些必要的修改,首先需要实现 MethodCallHandler 接口,该接口中需要实现 onMethodCall 方法,通过获取调用的方法名和参数值,进行相应的处理
1  | class FlutterPlugin(private val flutterView: FlutterView) :  | 
最终的效果,当点击返回按钮的时候,会将 Flutter 层通过 invokeMethod 传递的 arguments 属性吐司出来,同时,控制台会打印出 “has finish” 的信息
EventChannel
EventChannel 用于数据流(event streams)的通信
EventChannel 的实现方式也类似,EventChannel 可以持续返回多个信息到 Flutter 层,在 Flutter 层的表现就是一个 stream,原生层通过 sink 不断的添加数据,Flutter 层接收到数据的变化就会作出新相应的处理。在 Android 端实现状态的监听可以通过广播来实现。直接看例子,还是修改上述代码
1  | class FlutterPlugin(private val flutterView: FlutterView) :  | 
在 Flutter 层,通过对 stream 的监听,对返回的数据进行处理,为了体现出变化,这边修改成 SatefulWidget 来存储状态
1  | class FlutterContactPage extends StatefulWidget {  | 
同时,需要在 Activity 层调用一个定时任务不断的发送广播
1  | class ContactActivity : AppCompatActivity() {  | 
最后的实现效果大概是这样的

Flutter 同 Android 端的交互到这讲的差不多了,和 iOS 的交互其实也类似,只不过在 Android 端通过 FlutterNativeView 来作为 Binarymessenger 的实现,在 iOS 端通过 FlutterBinaryMessenger 协议实现,原理是一致的。至于 Flutter 插件,其实现也是通过以上三种交互方式来实现的,可能我们目前通过 FlutterView 来作为 BinaryMessenger 实例,插件会通过 PluginRegistry.Registrar 实例的 messenger() 方法来获取 BinaryMessenger 实例。
最后贴上 demo 的地址:ContactDemo
需要了解插件的写法也可以直接查看官方提供的检测电量插件:Flutter Battery Plugin