【Flutter 问题系列第 69 篇】详细说明 Flutter 与 Android 原生交互(通信)方式之 MethodChannel(保姆级图文教程)

这是【Flutter 问题系列第 69 篇】,如果觉得有用的话,欢迎关注专栏。

博文所用 Flutter SDK:2.2.3,Dart SDK:2.13.4。

因为 Flutter 一直在更新,但一些参考资料已经比较老了,部分 API 已经被遗弃,所以整理了一些最新的,扩展性高的代码和资料写到了博客中,希望可以帮到大家。

一:简述 Flutter 与原生通信的三种方式

Flutter 做为一个跨平台的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。

不过工作中做 Flutter 总是要和 Android/iOS 原生打交道的,那么 Flutter 提供了哪些和原生通信的方式呢?

Flutter 定义了三种不同的 Channel(通道)用来和原生桥接,我简单整理了一下,如下表格所示。

Flutter 与原生通信的三种方式

交互方式 用途 交互方向 返回值
MethodChannel 传递方法调用 双端通信
EventChannel 数据流信息通信 原生发送信息到 Flutter
BasicMessageChannel 传递字符串和半结构化信息 双端通信

这三种方式,无论是传递方法还是事件,本质上都是传递的数据。

具体使用哪种通信方式,要看使用场景。也不是说这三种方式的差别很大,同一种功能可以通过不同的方式来实现。

比如获取手机电量,网络变化等可以通过 EventChannel,也能用 MethodChannel。

二:Flutter 与 Android 原生通信

因为我在工作中用到最多的通信方式是 MethodChannel,后面我将以该方式为例,说下 Flutter 与 Android 原生是如何通信的。

既然是通信和通道,那肯定要在 Flutter 端和 Android 端写好发送或者接收的代码,下面我们分端进行描述。

三:Flutter 端

设置 Flutter 端的通道比较简单,一共需要两步

  • 第一步:生成一个 Flutter 和 Android 原生通信的通道 MethodChannel;
  • 第二步:通过通道发起一次方法的调用 invokeMethod。

在 Flutter 应用中,一般不会只有一个通道,且一般一个通道对应着一种类型的功能,所以我们会将通道封装在一个静态工具类中。

第 1 步:创建通道 MethodChannel

假如我想封装一个根据指定包名「安装」、「卸载」手机上 App 的工具,如何在 Flutter 中定义通道呢?

在 Flutter 中,使用 MethodChannel 创建通道,如下代码所示

import 'dart:convert';
import 'package:flutter/services.dart';

// ------------------------------------------------------
// author:Allen Su
// date  :2022/6/18 16:52
// usage :包管理器 - Flutter 端
// ------------------------------------------------------

class PackageManager {
    
    
	// _channel 是通道的实例,package_manager 是自定义的通道名称
	static const MethodChannel _channel = const MethodChannel("package_manager"); 
}

先看下方法通道类 MethodChannel 的源码,如下图所示

在这里插入图片描述

(注:源码释义是个人通过翻译软件整理的,仅供参考)

可以看出有一个必传参数 name ,其实就是你为这个通道起的名称,一个通道对应一个唯一的名称,不同的通道用了相同的名称会彼此干扰。

第 2 步:发起通道方法的调用 invokeMethod

通道的实例已经有了,在 Flutter 端我们使用 MethodChannel 类中的方法 invokeMethod 发起一次调用,如下代码所示

class PackageManager {
    
    
	// _channel 是通道的实例,package_manager 是自定义的通道名称
	static const MethodChannel _channel = const MethodChannel("package_manager"); 

	// 根据传入的包名安装 App
	// 注:方法名install和invokeMethod中的参数install不是要一定相同,这里相同是为了方便一眼看出函数的功能
	Future<bool> install() async {
    
    
      // install 调用方法的名称,com.allensu 应用包名
  	  bool res = await _channel.invokeMethod("install", "com.allensu"); 
  	  return res;
    }
}

invokeMethod 的源码如下图所示(注释太多我就不翻译了,大概 168 行,大家自行在 platform_channel 文件中查看吧)

在这里插入图片描述

invokeMethod 是一个返回值为泛型的异步方法(返回值根据需要设置,可有可无),有一个必传参数 method ,是要调用的方法的名称,还有一个可传参数 arguments ,是方法调用时传递的数据,类型是 dynamic 。

调用的大致过程是 invokeMethod 将传递的数据封装成一个 MethodCall 对象,然后使用 MethodCodec 方式将传递的数据编码成二进制类型的数据,最后通过 BinaryMessages 将通信的消息发出。

ok,Flutter 端的通道代码已经写好了,下面开始说 Android 端。

四:Android 端

设置 Android 端的通道相对麻烦一点点,详细来说一共需要六步

  • 第一步:实现接口 FlutterPlugin;
  • 第二步:重写 FlutterPlugin 类中的 onAttachedToEngine 方法;
  • 第三步:在 onAttachedToEngine 方法中,生成一个 Android 原生和 Flutter 通信的通道;
  • 第四步:在通道上注册方法,调用将要处理的功能(函数);
  • 第五步:根据实际需要决定是否回调返回值;
  • 第六步:在 MainActivity 中注册插件的实例。

前四步其实可以看作是一大步,所以也可以说设置 Android 端的通道一共需要三步。

为了方便在 Android Studio 中写 Java 代码,这里我单独打开 Flutter 中的 android 项目。

第 1 步:创建通道 MethodChannel

和 Flutter 端创建通道大同小异,如下代码所示

package com.package.manager.package_demo;

import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;

// ------------------------------------------------------
// author:suxing
// date  :2022/6/18 19:51
// usage :包管理器 - Android 端
// ------------------------------------------------------

public class PackageManagerPlugin implements FlutterPlugin {
    
    

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
    
    
        // 声明通道,第二个参数 name 要和 Flutter 中定义的通道名称保持一致
        MethodChannel channel = new MethodChannel(binding.getBinaryMessenger(), "package_manager");
	}
}

也就是实现接口 FlutterPlugin ,重写 onAttachedToEngine ,并在 onAttachedToEngine 中创建通道。

第 2 步:在通道上注册方法,调用将要处理的功能(函数) setMethodCallHandler

如下代码所示

public class PackageManagerPlugin implements FlutterPlugin {
    
    

    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
    
    
        // 声明通道,第二个参数 name 要和 Flutter 中定义的通道名称保持一致
        MethodChannel channel = new MethodChannel(binding.getBinaryMessenger(), "package_manager");
        // 在此通道上注册方法调用将要处理的功能(函数)
        channel.setMethodCallHandler(this::onMethodCall);
	}
}

查看 channel.setMethodCallHandler 方法的源码可知,该方法需要一个参数,参数类型是接口 MethodCallHandler。

查看 MethodCallHandler 源码可知

@UiThread
void onMethodCall(@NonNull MethodCall call, @NonNull Result result);

所以自定义一个方法 onMethodCall ,参数和上面的代码保持一致,如下代码所示

/// 处理不同名称方法的回调
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    
    
    // call.method 回调中的方法名,要和在 Flutter 中定义的保持一致
    switch (call.method) {
    
    
        case "install":
            // 这里的 packageName 是在 Flutter 中定义的 com.allensu
            String packageName = call.arguments();
            // 执行安装功能,并返回安装结果
            boolean res = install(packageName); 
            // 将安装结果回调给 Flutter
            result.success(res); 
            break;
        case "other":
        	// 这里的 other 是一个测试,意思是如果方法名是 other,则执行 doSomething 函数
            doSomething();
            // 如果不需要回调数据,则参数填 null
            result.success(null); 
            break;
        default:
        	// 如果回调异常,则设置相关信息
        	result.error("错误码","错误信息","错误详情");
            break;
        }
    }

通过 call.method 获取当前通信的方法名,通过 result.success 和 result.error 设置回调状态。

代码都有注释,不再进行重复说明。

其中 success 和 error 的参数类型如下代码所示

void success(@Nullable Object result);

void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);

特别注意:
如果你没有要回调的数据,则在 result.success 中设置参数为 null,如果你没设置 result.success ,则 Flutter 端处于一直等待通信的状态,因为 Android 端没有给 Flutter 反馈通信的信息。

至此,插件的功能已经实现完毕,剩下的就是如何让该 plugin 和 Android 绑定,请看下一步。

第 3 步:MainActivity 中注册插件的实例

最后一步就是在 MainActivity 中注册插件的实例,在 MainActivity 中需要做两步

  • 第一步:让 MainActivity 继承 FlutterActivity。
  • 第二步:重写 FlutterActivity 类中的 configureFlutterEngine 方法,并添加要注册插件的实例。

MainActivity 文件中,添加如下代码

package com.package.manager.package_demo;

import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngin e;

// ------------------------------------------------------
// author:Allen Su
// date  :2022/6/18 20:36
// usage :Flutter 与 Android 通信的入口
// ------------------------------------------------------

public class MainActivity extends FlutterActivity {
    
    

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
    }

    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    
    
        // 注册插件的实例(如果有多个需要册插件的实例,依次增加即可)
        flutterEngine.getPlugins().add(new PackageManagerPlugin());
        super.configureFlutterEngine(flutterEngine);
    }
}

代码中的 PackageManagerPlugin 就是自定义的包管理器工具类,关于插件具体功能的实现都是在这个类中写的。

ok,关于 Flutter 与 Android 原生交互(通信)之 MethodChannel,就说到这里。

能写完真的是不容易,当然我希望我写的你能看懂,看的舒心。

其实交互一点也不难,就那几行代码,但要是写出来然后让别人懂为什么是这个流程,又为什么要这样写,就是另外一回事了。

你的问题得到解决了吗?欢迎在评论区留言。

赠人玫瑰,手有余香,如果觉得文章不错,希望可以给个一键三连,感谢。


结束语

Google 的 Flutter 越来越火,截止 2022年6月19日 GitHub 标星已达 142K,Flutter 毅然是一种趋势,所以作为前端开发者,没有理由不趁早去学习。

无论你是 Flutter 新手还是已经入门了,不妨先点个关注,后续我会将 Flutter 中的常用组件(含有源码分析、组件的用法及注意事项)以及可能遇到的问题写到 CSDN 博客中,希望自己学习的同时,也可以帮助更多的人。

猜你喜欢

转载自blog.csdn.net/qq_42351033/article/details/125355509