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