3、Android端MethodChannel实现:在MainActivity中注册MethodChannel、处理Flutter端的方法调用、返回结果给Flutter端。
好,咱们接着往下走。上一节我们把Flutter端的MethodChannel搭好了,现在轮到Android端登场了。
说白了,Android端要做的事情就三件:注册通道、处理调用、返回结果。听起来简单,但里面有不少细节。我刚开始做的时候,就因为在主线程里做了耗时操作,导致界面卡死,排查了半天……嗯,这些坑咱们今天一并说清楚。
3.1 在MainActivity中注册MethodChannel
打开你的 MainActivity.kt(或者 MainActivity.java),这是Android端的入口。Flutter引擎启动后,会回调 configureFlutterEngine 方法。我们就在这里注册通道。
我个人习惯把通道名称定义成常量,避免拼写错误。你想想看,如果Flutter端写的是 "com.example.channel",Android端写成了 "com.exmaple.channel",那调试起来多痛苦。
// MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.example.channel"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
// 这里处理Flutter端的方法调用
}
}
}
3.2 处理Flutter端的方法调用
注册好通道后,Flutter端发来的请求会通过 setMethodCallHandler 回调过来。回调参数有两个:call 和 result。
- call.method:方法名,字符串类型。Flutter端传什么,这里就收到什么。
- call.arguments:参数,可以是基本类型、Map、List等。
- result:用于返回结果给Flutter端。
来看一个完整的例子。假设Flutter端想获取设备的电池电量:
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
CHANNEL
).setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
}
"getDeviceInfo" -> {
val info = mapOf(
"model" to android.os.Build.MODEL,
"version" to android.os.Build.VERSION.RELEASE
)
result.success(info)
}
else -> {
result.notImplemented()
}
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
这里有几个关键点:
- 用
when表达式(Java里用switch)区分不同方法。 - 每个分支都必须调用
result.success()、result.error()或result.notImplemented(),否则Flutter端会一直等待。 - 参数类型要匹配。Flutter端传
int,Android端收到的是Int;传Map,收到的是HashMap。
3.3 返回结果给Flutter端
返回结果有三种方式:
| 方法 | 说明 | 使用场景 |
|---|---|---|
result.success(data) |
返回成功结果 | 正常获取到数据 |
result.error(code, message, details) |
返回错误信息 | 操作失败,比如权限被拒绝 |
result.notImplemented() |
方法未实现 | Flutter端调用了Android端没有注册的方法 |
来看一个带错误处理的例子:
"getLocation" -> {
if (checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
result.error(
"PERMISSION_DENIED",
"Location permission is required.",
null
)
return@setMethodCallHandler
}
val location = getLastKnownLocation()
if (location != null) {
result.success(mapOf(
"latitude" to location.latitude,
"longitude" to location.longitude
))
} else {
result.error(
"LOCATION_UNAVAILABLE",
"Unable to get current location.",
null
)
}
}
这里有个细节:result.error() 的三个参数分别对应Flutter端的 PlatformException 的 code、message 和 details。Flutter端可以这样捕获:
try {
final result = await channel.invokeMethod('getLocation');
} on PlatformException catch (e) {
print('Error code: ${e.code}');
print('Error message: ${e.message}');
}
3.4 避坑指南:线程问题
嗯,这里要重点说一下线程问题。MethodChannel的回调默认跑在主线程上。如果你在里面做耗时操作,比如读取文件、请求网络,UI就会卡住。
我曾经在一个项目里,直接在回调里调了一个耗时的API,结果Flutter端等了5秒才收到结果,用户体验极差。后来改成用协程处理:
// 使用协程处理耗时操作
lifecycleScope.launch(Dispatchers.IO) {
val data = fetchDataFromNetwork() // 耗时操作
withContext(Dispatchers.Main) {
result.success(data)
}
}
记住一个原则:耗时操作放后台,结果返回切主线程。
3.5 知识体系总览
为了让你更直观地理解整个流程,我画了一张图:
这张图把整个流程串起来了。Flutter端发起调用,Android端注册回调、处理逻辑、返回结果。每一步都有对应的代码实现。