第三章 MethodChannel详解:Android端实战

好,咱们接着聊MethodChannel。上一章我们把Flutter端的通道搭好了,这一章轮到Android端上场了。说白了,MethodChannel就像一根电话线,Flutter那边拨号,Android这边接听,然后告诉对方结果。

我个人习惯先把通道注册好,再写业务逻辑。这样思路清晰,不容易乱。你想想看,如果代码写了一半才发现通道没注册,那得多尴尬。

3.1 创建MethodChannel(Android端)

在Android端,我们需要在MainActivity或者FlutterActivity里做这件事。嗯,这里要注意:通道的名字必须和Flutter端完全一致,大小写、拼写都不能错。我在项目中遇到过好几次,因为名字少了个下划线,结果调了半天才发现。

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "com.example.app/channel";

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        super.configureFlutterEngine(flutterEngine);
        
        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
            .setMethodCallHandler(
                (call, result) -> {
                    // 这里处理Flutter发来的请求
                }
            );
    }
}

这段代码看着简单,但有几个坑我得提醒你:

  • configureFlutterEngine 这个方法在Flutter引擎初始化时调用,时机刚刚好
  • BinaryMessenger 必须从flutterEngine获取,不能自己new一个
  • 通道名建议用常量定义,别到处写死字符串
⚠️ 注意: 如果你用的是Flutter 2.0之前的版本,注册通道的方式不一样。老版本是在onCreate里做的,但现在推荐用configureFlutterEngine。我建议新项目直接用新方式。

3.2 处理Flutter端的方法调用

接下来就是重头戏了——处理Flutter发来的方法调用。MethodCallHandler接口里只有一个方法:onMethodCall。它会收到两个参数:call(方法调用信息)和result(结果回调)。

new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
    .setMethodCallHandler((call, result) -> {
        if (call.method.equals("getBatteryLevel")) {
            int batteryLevel = getBatteryLevel();
            
            if (batteryLevel != -1) {
                result.success(batteryLevel);
            } else {
                result.error("UNAVAILABLE", "Battery level not available.", null);
            }
        } else {
            result.notImplemented();
        }
    });

这里有几个关键点:

  • call.method 就是Flutter端传过来的方法名,用字符串匹配
  • call.arguments 可以拿到参数,可以是基本类型、Map、List等
  • result.success() 返回成功结果
  • result.error() 返回错误信息
  • result.notImplemented() 告诉Flutter这个方法没实现
💡 小技巧: 我个人习惯用switch-case代替if-else,当方法多了以后代码更清晰。不过要注意,switch-case不支持字符串?嗯,Java 7以上是支持的。

3.3 返回结果给Flutter

返回结果这块,说白了就是三种情况:成功、失败、没实现。咱们一个一个说。

成功返回:

result.success("操作成功");
result.success(42);
result.success(Arrays.asList("a", "b", "c"));

可以返回任何可序列化的类型。我在项目中遇到过一个问题:返回了一个自定义对象,结果Flutter那边解析不了。后来发现,必须用Map或者基本类型才行。

错误返回:

result.error("ERROR_CODE", "错误描述", details);

三个参数:错误码(字符串)、错误描述(字符串)、详细信息(任意类型)。错误码建议用大写字母加下划线,比如"NETWORK_ERROR"、"PERMISSION_DENIED"。

没实现:

result.notImplemented();

这个最简单,告诉Flutter端:你调的方法我没写。Flutter那边会收到MissingPluginException。

3.4 错误处理与异常传递

错误处理这块,我踩过不少坑。最典型的就是:Android端抛了个异常,Flutter端直接崩溃了。为什么会这样?因为异常没有正确传递。

正确的做法是:

new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
    .setMethodCallHandler((call, result) -> {
        try {
            if (call.method.equals("getBatteryLevel")) {
                int batteryLevel = getBatteryLevel();
                result.success(batteryLevel);
            } else {
                result.notImplemented();
            }
        } catch (Exception e) {
            // 捕获所有异常,返回给Flutter
            result.error("INTERNAL_ERROR", e.getMessage(), null);
        }
    });

我曾经在一个项目里忘了加try-catch,结果用户手机上的某个ROM有bug,getBatteryLevel方法抛了个RuntimeException。Flutter端直接白屏了,用户反馈说"一打开就闪退"。嗯,从那以后我再也不敢省略异常处理了。

🔑 核心原则: 永远不要在MethodChannel的回调里抛出未捕获的异常。要么用try-catch包起来,要么在方法内部处理好。Flutter端是异步的,异常不会自动传递过去。

另外,错误码的设计也很重要。我建议:

错误码 含义 使用场景
UNAVAILABLE 功能不可用 硬件不支持、权限未授予
INVALID_ARGUMENTS 参数错误 传入了空值、类型不对
INTERNAL_ERROR 内部错误 系统异常、未知错误
NOT_IMPLEMENTED 未实现 方法不存在

最后说一个细节:result对象只能调用一次。你不能先调success再调error,也不能调两次success。如果重复调用,Flutter端会收到异常。我在代码审查时经常看到这种问题,新手容易犯。

好了,Android端的MethodChannel就这些内容。下一章我们聊聊iOS端,思路差不多,但细节上有些差异。到时候我会重点说说Swift和Objective-C两种语言下的实现区别。