【Flutter学习】通过MethodChannel调用本地库和安卓库

2024-05-01 1945阅读

Flutter学习系列

文章目录

  • Flutter学习系列
  • 前言
  • 一、MethodChannel 是什么?
  • 二、举例说明
    • 1.在工程中创建so库的package
    • 2.导入so库
    • 3.在flutter中创建方法通道
    • 4.在安卓中调用本地库
    • 5.在flutter中调用方法
    • 总结

      前言

      在flutter开发中,有时会用到本地动态库和安卓库,比如:对一些GPIO、串口等底层硬件资源调用;有时还需要混合一些原生安卓的功能。这时就要用到MethodChannel(方法通道)。

      一、MethodChannel 是什么?

      MethodChannel 实现 Flutter 代码(Dart)和平台代码(如 iOS 的 Objective-C/Swift 或 Android 的 Java/Kotlin)之间的通信。这是一种通过特定的通道(channels)传递消息的机制,允许开发者调用平台级的API、服务或进行平台特定的操作,这些在纯 Dart 环境中是无法直接实现的。

      MethodChannel 主要用于传递方法调用。其基本流程是这样的:

      1.在 Dart 端,你会创建一个 MethodChannel 的实例,并指定一个通道名称。这个名称需要在平台端(iOS/Android)与 Dart 端匹配,以确保通信的正确性。

      2.通过这个 MethodChannel,你可以发送方法调用到平台端,并可以异步接收回复。

      3.在平台端(iOS/Android),你需要监听相同名称的 MethodChannel,并响应 Dart 端发送的方法调用,执行相应的平台代码,然后将结果回传到 Dart 端。

      详细流程示意图如下:

      【Flutter学习】通过MethodChannel调用本地库和安卓库

      二、举例说明

      以libgpio.so库为例,该库提供了芯片IO引脚读电平和配置电平的方法。

      开发环境:

      Android studio-2023.2.1.23

      Flutter 3.19.2

      Dart 3.3.0

      硬件:armeabi-v7a

      1.在工程中创建so库的package

      首先要知道so库中的方法签名是什么,这个非常重要,在dart中实例的方法签名要和so库中的方法签名保持一致,不然找不到方法。方法签名前缀和目录框架有关,比较常见的前缀:Java_com_example_fun();这意味着安卓工程有如下目录:

      (…省略…)app\src\main\java\com\example***.java

      可以从提供so库的第三方得知方法签名,若无法得知方法签名,可以通过IDA_Pro_v7反编译工具查看方法签名。

      IDA_Pro_v7工具下载:链接:https://pan.baidu.com/s/1Tp9O69Nf1uTKwtehKcyMrA

      提取码:1645

      以libgpio.so为例,得知so库中的方法为:

      Java_cn_r_g_MainActivity_writeGpio(JNIEnv *env, jclass type, jstring num_, jstring value_)
      

      因此建立如下目录和java文件:

      (...省略...)app\src\main\java\cn\r\g\MainActivity.java
      

      在MainActivity.java中创建class:

      package cn.r.g;
      public class MainActivity {
          static {
              System.loadLibrary("gpio");
          }
          public native int readGpio(String num);
          public native int writeGpio(String num, String value);
      }
      

      在默认的flutter工程中,app\src\main(你的签名)\中已经存在MainActivity.java,这是app启动的默认入口,上面so库的例子中已经存在MainActivity.java,因此需要更改默认的Activity入口名称。

      在app\src\main\AndroidManifest.xml中作如下修改:

      ......
       
      ......
      }
      
        // 与MainActivity中定义的通道名相同
        static const MethodChannel _channel =
            MethodChannel('channel/gpio');//可以任意取名,和Activity中保持一致
        // 调用原生的writeGpio方法
        static Future
          try {
            // 调用原生代码的writeGpio方法,并传递num和value作为参数
            final int result = await _channel.invokeMethod('writeGpio', {
              'num': num,
              'value': value,
            });
            // 返回原生代码返回的结果
            return result;
          } on PlatformException catch (e) {
            // 如果调用失败,你可以在这里处理异常
            // 可以打印错误信息、返回一个默认值或者抛出一个异常
            print("Failed to write GPIO: '${e.message}'.");
            return null; // 或者可以选择抛出异常
          }
        }
      }
      
          private static final String CHANNEL = "channel/gpio";
          
          @Override
          public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
              super.configureFlutterEngine(flutterEngine);
              new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
                      .setMethodCallHandler(
                              (call, result) - {
                                  switch (call.method) {
                                      case "writeGpio":
                                          MainActivity mainActivity = new MainActivity();
                                          String num = call.argument("num");
                                          String value = call.argument("value");
                                          int write_io_response = mainActivity.writeGpio(num, value);
                                          result.success(write_io_response);
                                          break;
                                          default:
                                          result.notImplemented();
                                          break;
                                  }
                              }
                      );
          }        
      }
      

      5.在flutter中调用方法

      在一个StatefulWidget初始化中,调用initGpio方法,该方法通过异步操作执行so库中设置芯片引脚电平的功能,并返回电平的值:

      @override
        void initState() {
          super.initState();
          initGpio();
       
        }
      void initGpio() async {
          try {
            // 注意,这里我们使用了 async 和 await
            int? result1 = await LocalAndroidMethod.writeGpio("a1", "1");
            int? result2 = await LocalAndroidMethod.writeGpio("a2", "1");
            // 在这里处理结果,例如更新UI或状态
            print('GPIO write result: $result1 $result2');
          } catch (e) {
            // 处理任何可能出现的错误
            print(e);
          }
        }
      

      至此实现flutter工程调用本地so库中的方法,so库会打包一起编译进app中。

      总结

      MethodChannel 是 Flutter 与原生平台之间进行通信的一种方式,它具有其特定的优点和局限性。

      1.MethodChannel 的优点

      直接与原生代码通信:允许 Dart 代码调用原生平台的API,访问那些不可或难以用 Dart 实现的功能。

      简单易用:MethodChannel 提供了一套简明的API,使得实现跨语言调用变得非常直接。

      异步支持:支持异步操作,使得可以在不阻塞 UI 线程的情况下执行耗时的原生操作。

      平台透明:对于 Flutter 开发者来说,他们不需要关心底层平台的差异,可以使用统一的方法调用接口。

      2.MethodChannel 的不足

      平台相关的代码:需要编写平台特定的代码,这可能导致代码库的膨胀和增加维护难度。

      调试困难:跨语言和平台调试可能比在单一语言/框架中调试更加复杂。

      性能开销:每次通过 MethodChannel 通信都涉及到跨语言和跨平台的消息传递,这可能导致性能开销。

      类型安全性:在 MethodChannel 中传递数据时,数据类型需要在发送和接收端之间手动保持一致。

      3.原生平台交互的其它方案:

      EventChannel:

      用于数据流(如,传感器数据)的通信。如果你需要从原生端向 Dart 端发送连续的数据流,EventChannel 可能更合适。

      BasicMessageChannel:

      用于传递字符串和半结构化的信息。如果你只需要一个简单的通信渠道来传递数据或消息,并且不需要调用方法,这可能是一个更好的选择。

      PlatformView:

      如果你需要在 Flutter 应用中嵌入原生视图组件(比如地图、Web视图),PlatformView 可以直接在 Flutter 布局中集成原生视图。

      Pigeon:

      一个 Flutter 插件生成器,可以生成类型安全的 Dart 和原生代码的互操作层,减少手动编码的 MethodChannel 通信代码。

      Foreign Function Interface (FFI):

      对于一些性能要求极高的操作,你可以使用 FFI 在 Dart 代码中直接调用 C/C++ 函数库。

      这部分学习过程中,最大的坑或者说困惑在于so库中的方法签名不同导致出错,这个库的方法签名和MainActivity重名。还有就是学了dart中使用FFI调用本地动态库,产生了混淆。如果读者发现上面有问题,或者不需要创建相应的packa目录让方法签名保持一致,欢迎指教。

      (对于调用aar等安卓库的方法类似,不在赘述,上述的so文件和工程目录皆为虚构)

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]