(原创)Flutter与Native通信的方式:MethodChannel

前言

随着Flutter混合开发的项目越来越多,我们也有了实际的一个场景,
那就是Flutter如何与原生(Native)端进行通信
目前看来,大概有三种方式,分别是:
MethodChannel
EventChannel
MessageChannel
这三种方式实现起来其实大同小异,基本的代码都差不多
但为了让大家更清楚的知道三者的区别,所以有了这篇博客
本篇主要讲MethodChannel方式
这也是实际开发中最常用的方式。
其他的两种方式可以看下一篇博客:
EventChannel和BasicMessageChannel
那么现在正式开始

实现

FlutterActivity

首先我们要知道,所谓的Flutter的一个个page页面,其实归根结底也是跑在Activity上面的
这有点像我们的WebView,最终承载页面的,还是一个Activity
而在Flutter中,承载页面的,其实是一个叫做FlutterActivity的类
于是我们的交互,其实是Flutter的Page和这个FlutterActivity进行交互
首先我们新建一个普通的Activity,也就是首页

class MainActivity : AppCompatActivity() {


  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    findViewById<TextView>(R.id.textview).setOnClickListener {
      startActivity(MyFlutterActivity.withCachedEngine("your_engine_id").build(this))
    }
  }

}

这个首页就是我们最基础的AppCompatActivity
它不是重点。
点击按钮进入我们承载Flutter的页面:MyFlutterActivity
它继承的是一个叫做FlutterFragmentActivity的类
这个页面就可以正式和他自己页面里的Flutter页面交互了
相关交互的代码也是写在这里

Flutter端调用Android端

先看Flutter端我们如何实现
首先我们可以新建一个ForNativePlugin.dart的文件
这个文件用来定义要调用Android端的方法

class ForNativePlugin {
  MethodChannel methodChannel = const MethodChannel("ForNativePlugin");

  /// 调用安卓,得到当前时间
  Future<String> getTime() async {
    var nowTime = await methodChannel.invokeMethod("getTime");
    return nowTime;
  }


  /// 显示安卓土司
  showAndroidToast(String msg) async {
    try {
      await methodChannel.invokeMethod('toast', {'msg': msg});
    } on PlatformException catch (e) {
      print(e.toString());
    }
  }
}

可以看到,创建了一个MethodChannel类
这个就是交互的关键类
MethodChannel的name属性要双端开发约定好
这里约定的是一个ForNativePlugin的字符串
然后定义了两个方法
方法的实现其实就是用methodChanneld.invokeMethod去触发安卓端的方法
invokeMethod的一个参数写安卓端的方法名,第二个写入参的名字,入参可以有多个

写好后,我们Flutter就可以创建这个类

ForNativePlugin forNativePlugin = ForNativePlugin();

然后在需要的时候使用我们写的这个工具类去触发Android端的方法了
比如:

forNativePlugin.showAndroidToast("当前时间为:${forNativePlugin.getTime()}");

这样就可以调用安卓端,获取时间,然后再让安卓端帮我们把时间返回后
通过Toast的方法弹出来
那么Android端要怎么写呢?
也很简单,Android端也要定义一个类
实现MethodCallHandler接口的onMethodCall方法

class MyMethodCallHandler(var context: Context) : MethodCallHandler {
  override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
    when (methodCall.method) {
      "getTime" -> {
        result.success(SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(System.currentTimeMillis()))
      }
      "toast" -> {
        if (methodCall.hasArgument("msg") && !TextUtils.isEmpty(
            methodCall.argument<Any>("msg").toString()
          )
        ) {
          Toast.makeText(context, methodCall.argument<Any>("msg").toString(), Toast.LENGTH_LONG)
            .show()
        } else {
//          result.error()
          Toast.makeText(context, "msg 不能为空", Toast.LENGTH_SHORT).show()
        }
      }
      else -> {
        // 表明没有对应实现
        result.notImplemented()
      }
    }
  }
}

onMethodCall方法就用来处理我们要被触发的逻辑了
然后来到我们继承了FlutterFragmentActivity的类下面
首先也定义一个MethodChannel

private lateinit var flutterMethodChannel: MethodChannel

然后让我们Activity去实现configureFlutterEngine方法

  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    //通过MethodChannel与原生通信,ForNativePlugin为约定字段
    flutterMethodChannel = MethodChannel(flutterEngine.dartExecutor, "ForNativePlugin")
    flutterMethodChannel.setMethodCallHandler(MyMethodCallHandler(this))
  }

可以看到,这里其实就是一个注册环节
把我们的MyMethodCallHandler类注册到MethodChannel里面
至此,Flutter端调用Android端代码就完成了

补充一点:
在Android端被触发时
可以通过result类告知Flutter请求结果是否成功
result.success() 成功
result.error() 失败
result.notImplemented() 该方法没有对应实现

Android端调用Flutter端

Android端调用Flutter端其实也差不多
首先是Android端:
我们可以通过调用getFlutterEngine方法获得一个FlutterEngine
然后拿这个FlutterEngine的getDartExecutor进行操作

  fun toFlutter(num1: Int, num2: Int) {
    flutterEngine?.dartExecutor?.let {
      nativeMethodChannel = MethodChannel(it, "ForFlutterPlugin")
      nativeMethodChannel.invokeMethod(
        "callFlutterSum",
        listOf<Int>(num1, num2),
        object : MethodChannel.Result {
          override fun success(result: Any?) {
            Log.d("MyFlutterActivity", "计算结果为 = $result")
          }

          override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
            Log.d("MyFlutterActivity", "出错了:$errorCode, $errorMessage, $errorDetails")
          }

          override fun notImplemented() {
            Log.d("MyFlutterActivity", "notImplemented")
          }
        })
    }
  }

这里可以看到,也是定义了一个MethodChannel,约定字段为:ForFlutterPlugin
然后调用Flutter端的callFlutterSum方法获取计算的返回值
这里需要注意一点,调用Fluter代码要在主线程

然后来到Flutter端
定义好Flutter端的MethodChannel:

MethodChannel methodChannel = const MethodChannel("ForFlutterPlugin");

并执行注册和回调监听,可以在initState方法中注册:

  
  void initState() {
    // TODO: implement initState
    super.initState();
    methodChannel.setMethodCallHandler(
      (call) {
        switch (call.method) {
          case 'callFlutterSum':
            print('call callMe : arguments = ${call.arguments}');
            List<Object?> numbers = call.arguments;
            var num1=numbers[0] as int;
            var num2=numbers[1] as int;
            //触发Android端的success方法
            return Future.value(num1+num2);
            //触发Android端的error方法,errorCode为error,errorMessage为:调用出错,不能传details
            // return Future.error("调用出错");
            //触发Android端的error方法,可以额外传details对象
            throw PlatformException(code: 'errorCode', message: 'Error occurred',details: "出错了");
          default:
            print('Unknowm method ${call.method}');
            //触发Android端的notImplemented方法
            throw MissingPluginException();
        }
      },
    );
  }

至此,Flutter端的工作也已完成
可以看到,两端的互相调用,基本分为三个步骤:
1:被调用端先写好被调用的处理逻辑
2:被调用端创建好MethodChannel,调用setMethodCallHandler进行注册
3:调用端创建MethodChannel,约定好字段,然后使用invokeMethod主动去调用方法

注意

在你写双端通信的时候,如果发现不触发调用,可以关注以下几个原因:
1:是否是继承了FlutterFragmentActivity或者FlutterActivity
2:约定字段是否一致
3:方法名称和参数是否一致

源码

本篇博客源码地址:
MethodChannel

你可能感兴趣的:(flutter)