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端的方法调用
        }
    }
}
小提示:通道名称必须和Flutter端完全一致,大小写、点号都不能错。我建议你把这个字符串提取到公共常量文件里,两边共用。

3.2 处理Flutter端的方法调用

注册好通道后,Flutter端发来的请求会通过 setMethodCallHandler 回调过来。回调参数有两个:callresult

  • 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
注意:千万不要在回调里直接做网络请求或数据库操作。这些耗时任务会阻塞UI线程。我曾经就犯过这个错,结果Flutter界面直接卡死。正确的做法是启动一个协程或线程来处理。

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端的 PlatformExceptioncodemessagedetails。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端 MethodChannel invokeMethod() 等待结果 await 接收结果 success/error 调用 返回 Android端 setMethodCallHandler 注册回调 处理调用 when(call.method) 返回结果 result.success() 核心要点 ① 通道名称必须一致 ② 每个调用都要返回 ③ 耗时操作放后台 ④ 返回结果切主线程 ⑤ 错误处理要完整 常见错误 • 忘记调用result • 主线程做耗时操作 • 通道名称拼写错误

这张图把整个流程串起来了。Flutter端发起调用,Android端注册回调、处理逻辑、返回结果。每一步都有对应的代码实现。

总结一下:Android端实现MethodChannel就三步——注册通道、处理调用、返回结果。注意线程问题,别忘了每个分支都要调用result方法。把这些基础打牢,后面学EventChannel就轻松多了。

专注资料整理