【Flutter学习】通过MethodChannel调用本地库和安卓库
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 端。
详细流程示意图如下:
二、举例说明
以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文件和工程目录皆为虚构)