Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)


  • Flutter混合开发中,一些基于原生开发的功能无法实现,需要编写原生Android和iOS的功能插件才能实现,这里就涉及到Flutter与原生代码之间的通信,本人也是刚从Android开发转向Flutter混合开发,所以编写此文记录一下Flutter与Android之间互相通信的学习,顺便记录一些错误以免以后再犯


  • 新建项目
    配置好Flutter环境以后,打开Android Studio,New -> New Flutter Project -> Flutter Application -> Next -> “flutter_plugin_demo” -> Next -> Finish
    等待AS构建好Flutter项目后,修改android文件下的build.gradle文件:
    Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第1张图片
    因为Flutter默认配置的仓库依赖是国外的,要是在国内运行起来会很慢甚至运行失败,这里修改为阿里云的maven仓库依赖

		maven { url 'https://maven.aliyun.com/repository/google' }
        maven { url 'https://maven.aliyun.com/repository/jcenter' }
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }

连接虚拟机或手机,点击运行即可。

  • 新建插件

New -> New Moudle -> Flutter Plugin -> “flutter_plugin_test” -> Next -> Finish

Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第2张图片
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第3张图片
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第4张图片
或者可以直接使用命令行Terminal执行:

flutter create --template=plugin flutter_plugin_test
//或者  其中  com.example为包名
flutter create --org com.example  --template=plugin flutter_plugin_test

等待构建完成后,项目的目录结构如下(注:这里插件test文件夹标红不用管,不影响运行):

Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第5张图片

  • 调用插件

    调用插件之前,先修改一下主程序的main.dart文件,方便后期调用的操作

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  String _msg = "这是内容";
  
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "这是标题",
      home: new Scaffold(
          appBar: new AppBar(
              title: new Center(
                child: new Text("这是居中标题"),
              )),
          body: new Center(
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new Text(_msg),
                  new MaterialButton(
                    onPressed: () {
                      _getPluginMsg();
                    },
                    child: new Text("获取插件信息"),
                    color: Colors.blue,
                    textColor: Colors.white,
                  )
                ],
              )
          )
      ),
    );
  }

  _getPluginMsg() {
    print("点击了按钮");
  }
}




然后用Android Studio新窗口打开插件的android端项目,项目视图切换为Project,
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第6张图片
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第7张图片
接着从

flutter sdk安装路径\bin\cache\artifacts\engine

其中的任何一个文件夹找到flutter.jar,在刚才打开的android项目根目录下新建lib文件夹,并将flutter.jar拷贝到该目录下并右键点击Add As Library,导入之后 FlutterPluginTestPlugin.kt文件报错消失,我们就可以在该文件编写插件内容了。


首先分析下FlutterPluginTestPlugin.kt文件下的内容:

package com.example.flutter_plugin_test

import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar

class FlutterPluginTestPlugin: MethodCallHandler {
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      //初始化MethodChannel通信通道  
      //定义通道唯一名称 这个名称在flutter调用插件时要使用到 所以必须唯一 若有两个通道定义同一个名称  则后一个会覆盖前一个
      val channel = MethodChannel(registrar.messenger(), "flutter_plugin_test")
      channel.setMethodCallHandler(FlutterPluginTestPlugin())
    }
  }
	
  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      //当flutter端调用方法getPlatformVersion时,返回当前系统的版本号:Android + 系统版本号
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      //没有实现flutter调用的方法时的回调
      result.notImplemented()
    }
  }
}

  • 主项目引入插件并调用
    根目录下找到pubspec.yaml文件并在里面编写:
flutter_plugin_test:
    path: flutter_plugin_test

然后点击右上角 Packages get 获取刚才添加的插件Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第8张图片
最后修改主程序main.dart内容调用插件方法:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  String _msg = "这是内容";
  //定义通信通道  名称与插件中的一致
  static const platform = const MethodChannel("flutter_plugin_test");
  //异步调用插件方法 获取返回内容
  Future<void> _getMsg() async{
    String msg ;
    try{
      msg = await platform.invokeMethod("getPlatformVersion");
    }on PlatformException{
      msg = "获取插件信息错误";
    }
    if(!mounted){
      return;
    }
    setState(() {
      _msg = "这是重插件获取的信息:"+msg;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "这是标题",
      home: new Scaffold(
          appBar: new AppBar(
              title: new Center(
                child: new Text("这是居中标题"),
              )),
          body: new Center(
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new Text(_msg),
                  new MaterialButton(
                    onPressed: () {
                      _getPluginMsg();
                    },
                    child: new Text("获取插件信息"),
                    color: Colors.blue,
                    textColor: Colors.white,
                  )
                ],
              )
          )
      ),
    );
  }

  _getPluginMsg() {
    print("点击了按钮");
    //点击按钮是  获取插件返回值
    _getMsg();
  }
}



最后运行程序,此时会报错:

D8: Program type already present: io.flutter.app.FlutterActivityDelegate$1

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> com.android.builder.dexing.DexArchiveMergerException: Error while merging dex archives: D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\13.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\0.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\1.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\12.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\7.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\6.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\5.jar, D:\Android\workspace\flutter_plugin_demo\flutter_plugin_demo\build\app\intermediates\transforms\dexBuilder\debug\4.jar
  Program type already present: io.flutter.app.FlutterActivityDelegate$1
  Learn how to resolve the issue at https://developer.android.com/studio/build/dependencies#duplicate_classes.

这是因为刚才我们在插件的android项目中引入flutter.jar包,而主程序当中已经有了自己的jar包,才导致运行时两者冲突出错,只要将插件端android项目的flutter.jar包依赖注释掉再点击Sync Now,再重新运行一下主项目即可(注意:这里在android项目中引入flutter.jar是为了方便编写插件,实际导入插件或发布插件时记得把flutter.jar引入注释掉):
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第9张图片
点击一下按钮:
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第10张图片
OK,成功获取到插件中方法getPlatformVersion返回的内容,这里可以尝试修改一下插件FlutterPluginTestPlugin.kt里方法的返回值,然后重新运行程序,看看是否有改变

override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      //修改返回值  也可以在这里进行其他操作  比如获取电量值等等
      result.success("这是返回值")
    } else {
      //没有实现flutter调用的方法时的回调
      result.notImplemented()
    }
  }
  • 插件与Flutter通信

    Flutter获取插件的内容成功了,那插件回传数据给Flutter(调用Flutter的方法,主动传值给Flutter)怎么办呢?
    这里就要用到EventChannel了,MethodChannel和EventChannel具体原理大家可以到Flutter中文网更详细了解一下。
    修改FlutterPluginTestPlugin.kt文件,加入EventChannel实现:

package com.example.flutter_plugin_test

import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar

/**
 * 实现MethodCallHandler和EventChannel.StreamHandler
 */
class FlutterPluginTestPlugin: MethodCallHandler,EventChannel.StreamHandler {

  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      //初始化MethodChannel通信通道
      //定义通道唯一名称 这个名称在flutter调用插件时要使用到 所以必须唯一 若有两个通道定义同一个名称  则后一个会覆盖前一个
      val channel = MethodChannel(registrar.messenger(), "flutter_plugin_test")
      channel.setMethodCallHandler(FlutterPluginTestPlugin())
      //初始化EventChannel通信通道 定义唯一名称
      val eventChannel = EventChannel(registrar.messenger(),"flutter_plugin_event")
      eventChannel.setStreamHandler(FlutterPluginTestPlugin())
    }
  }
  /**
   * EventChannel通道接通 onListen方法
   * 可以在此通过EventChannel.EventSink主动传值给Flutter
   */
  override fun onListen(p0: Any?, p1: EventChannel.EventSink?) {
    print("---onListen---")
    p1?.success("这是插件主动传值的内容")
  }
  /**
   * EventChannel通道取消后会执行该方法
   */
  override fun onCancel(p0: Any?) {
    print("---onCancel---")
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      //当flutter端调用方法getPlatformVersion时,返回当前系统的版本号:Android + 系统版本号
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      //没有实现flutter调用的方法时的回调
      result.notImplemented()
    }
  }
}

然后在main.dart中注册EventChannel的监听:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  String _msg = "这是内容";
  //定义MethodChannel通信通道  名称与插件中的一致
  static const platform = const MethodChannel("flutter_plugin_test");
  //定义EventChannel通信通道  名称与插件中的一致
  static const eventChannel = const EventChannel("flutter_plugin_event");
  //异步调用插件方法 获取返回内容
  Future<void> _getMsg() async{
    String msg ;
    try{
      msg = await platform.invokeMethod("getPlatformVersion");
    }on PlatformException{
      msg = "获取插件信息错误";
    }
    if(!mounted){
      return;
    }
    setState(() {
      _msg = "这是重插件获取的信息:"+msg;
    });
  }
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "这是标题",
      home: new Scaffold(
          appBar: new AppBar(
              title: new Center(
                child: new Text("这是居中标题"),
              )),
          body: new Center(
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  new Text(_msg),
                  new MaterialButton(
                    onPressed: () {
                      _getPluginMsg();
                    },
                    child: new Text("获取插件信息"),
                    color: Colors.blue,
                    textColor: Colors.white,
                  ),
                  new MaterialButton(
                    onPressed: () {
                      _registerEventChannel();
                    },
                    child: new Text("注册EventChannel监听"),
                    color: Colors.blue,
                    textColor: Colors.white,
                  )
                ],
              )
          )
      ),
    );
  }
  _registerEventChannel() {
    //注册接收EventChannel传递的数据
    eventChannel.receiveBroadcastStream().listen(_onDone,onError: _onError);
  }
  _onDone(data){
    //接收成功
    print("EventChannel success:"+data);
  }
  _onError(data){
    //接收失败
    print("EventChannel error:"+data);
  }
  _getPluginMsg() {
    print("点击了按钮");
    //点击按钮是  获取插件返回值
    _getMsg();
  }

}



重新运行一下程序,然后再点击 “注册EventChannel监听” 按钮,控制台打印信息如下:
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第11张图片
不过这样好像看不到直观效果,我们再修改一下插件中的onListen方法,用定时器给Flutter传值:

override fun onListen(p0: Any?, p1: EventChannel.EventSink?) {
        print("---onListen---")
        var count = 1
        //设置定时器给Flutter传值
        val scheduledExecutorService = ScheduledThreadPoolExecutor(1)
        val timerTask = timerTask {
            //在主线程中运行  否则接收不到数据
            activity.runOnUiThread {
                p1?.success("插件主动传值 count:$count")
                count++
            }
        }
        scheduledExecutorService.scheduleWithFixedDelay(timerTask, 10, 1000, TimeUnit.MILLISECONDS)
    }

再运行一下,点击按钮查看效果:
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第12张图片
EventChannel可以用于android用电量广播或蓝牙广播接收主动传递给Flutter,感兴趣的同学可以自己试试,在onListen方法中注册广播并传递EventChannel.EventSink作为参数即可。

还有需要注意每一次修改插件后运行主程序时,点击热重载按钮是没有效果的,若程序仍在运行,则需先点击停止按钮再点击运行按钮,这样插件的修改才会生效。
Flutter笔记---Flutter与Android之间的相互通信MethodChannel与EventChannel(小白教程)_第13张图片

你可能感兴趣的:(Flutter)