Flutter 和原生间相互通讯 MethodChannel Pigeon
作者:互联网
目录
目录Flutter 和原生间相互通讯
数据类型及映射关系
Dart | Java | Kotlin |
---|---|---|
null | null | null |
bool | java.lang.Boolean | Boolean |
int | java.lang.Integer | Int |
int, if 32 bits not enough | java.lang.Long | Long |
double | java.lang.Double | Double |
String | java.lang.String | String |
Uint8List | byte[] | ByteArray |
Int32List | int[] | IntArray |
Int64List | long[] | LongArray |
Float32List | float[] | FloatArray |
Float64List | double[] | DoubleArray |
List | java.util.ArrayList | List |
Map | java.util.HashMap | HashMap |
通常情况下,Dart 中的 int 都会被转换成 Java 中的 Long,而非 Int
通道和平台线程
- 目标平台向 Flutter 发起 channel 调用的时候,需要在对应平台的主线程执行。同样的,在 Flutter 向目标平台发起 channel 调用的时候,需要在 root Isolate 中执行。
- 目标平台侧的 handler 既可以在平台的主线程执行,也可以通过 Task Queue 在后台执行。
- 目标平台侧的 handler 的返回值可以在任意线程异步执行。
简单来说就是:
- 平台方法必须在 主线程 上互调,UI 相关的操作必须在 主线程 上调用
- 回调线程不做要求,即可以同步也可以异步,具体可以使用哪种方式要看 api 是怎么设计的
- 具体来说,Android 中普通
MethodCallHandler
的onMethodCall
方法回调在 主线程,但可以在任意线程执行及响应。 - 而指定
TaskQueue
为makeBackgroundTaskQueue
时,onMethodCall
方法回调在 子线程,同样可以在任意线程执行及响应。
被注解
@UiThread
标记的方法需要在 UI 线程(即main
线程、主线程)中执行。
MethodChannel 使用案例
Android 端代码
class MyFlutterActivity : FlutterActivity() {
companion object {
private const val CHANNEL_NAME = "com.bqt.test/base_channel"
private const val METHOD_GETBATTERYLEVEL = "getBatteryLevel"
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME).setMethodCallHandler { call, result ->
when (call.method) {
METHOD_GETBATTERYLEVEL -> onCallGetBatteryLevel(call, result)
else -> result.notImplemented()
}
}
}
private fun onCallGetBatteryLevel(call: MethodCall, result: MethodChannel.Result) {
Log.i("bqt", "onMethodCall 是否运行在主线程 ${Looper.getMainLooper().thread == Thread.currentThread()}") // true
Thread {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
val batteryLevel: Int = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
if (batteryLevel != -1) {
result.success(batteryLevel) // 可在子线程响应请求
Log.i("bqt", "当前是否是主线程 ${Looper.getMainLooper().thread == Thread.currentThread()}") // false
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
}.start()
}
}
在后台线程中执行 handlers
要在 channel 对应的平台侧的后台中执行 handler,需要使用 Task Queue API。
class MyFlutterActivity2 : FlutterActivity() {
companion object {
private const val CHANNEL_NAME = "com.bqt.test/base_channel"
private const val METHOD_NAME = "getVersionCode"
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val binaryMessenger: BinaryMessenger = flutterEngine.dartExecutor.binaryMessenger
val taskQueue: BinaryMessenger.TaskQueue = binaryMessenger.makeBackgroundTaskQueue()
val methodCodec: MethodCodec = StandardMethodCodec.INSTANCE
val methodChannel = MethodChannel(binaryMessenger, CHANNEL_NAME, methodCodec, taskQueue)
methodChannel.setMethodCallHandler { call, result ->
when (call.method) {
METHOD_NAME -> onCallGetVersionCode(call, result)
else -> result.notImplemented()
}
}
}
private fun onCallGetVersionCode(call: MethodCall, result: MethodChannel.Result) {
Log.i("bqt", "onMethodCall 是否运行在主线程 ${Looper.getMainLooper().thread == Thread.currentThread()}") // false
result.success(BuildConfig.VERSION_CODE)
}
}
Flutter 端代码
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Text('共点击了 $_counter 次\n$_batteryLevel')
floatingActionButton: FloatingActionButton(onPressed: _incrementCounter)
static const platform = MethodChannel('com.bqt.test/base_channel');
String _batteryLevel = '点击获取电量';
int _counter = 0;
void _incrementCounter() {
setState(() => _counter++); // This call to setState causes rerun the build method below
_getBatteryLevel().then((value) {
debugPrint(value);
setState(() => _batteryLevel = "$value 第 $_counter 次获取");
});
}
Future<String> _getBatteryLevel() async {
try {
final int result = await platform.invokeMethod('getBatteryLevel');
return '电量 $result % .';
} on PlatformException catch (e) {
return "获取失败: '${e.message}'.";
}
}
Pigeon 简介
Pigeon is a
code generator tool
to make communication between Flutter and the host platformtype-safe
, easier and faster.
使用 MethodChannel
在 host 和 client 之间进行通信,并不是类型安全的。为了正确通信,host 和 client 必须声明相同的参数和数据类型。Pigeon 可以用作 MethodChannel
的替代品。
特性
- generate code that sends messages in a
structured typesafe
结构化类型安全 manner - no need to
match strings
between host and client for thenames and datatypes
of messages - supports
nested classes
支持嵌套类 - supports
grouping
messages into APIs 支持分组(即支持以 class 为单位分组) - generation of
asynchronous wrapper code
andsending messages
in either direction 双向通讯 - generated code is readable -- 支持使用
Builder
模式组装数据 - guarantees 保证
no conflicts
between multiple clients of different versions -- 可持续迭代
命令参数
flutter pub run pigeon \
--input pigeons/message.dart \
--dart_out lib/pigeon.dart \
--java_out pigeons/Pigeon.java \
--java_package "com.bqt.test.pigeon"
目前只支持生成 Java 文件,而不支持生成 Kotlin 文件
- --input:原始的
.dart
文件路径 - --dart_out:转换后的
.dart
文件保存路径 - --java_out:转换后的
.java
文件保存路径 - --java_package:包名
- --no-dart_null_safety:生成
non-null-safe
的代码
空安全
- 支持生成空安全
null-safe
代码:- Nullable and Non-nullable class fields
- Nullable return values
- Nullable method parameters
- 不支持可为空的泛型类型参数 Nullable generics type arguments
- 例如,仅支持
List<int>
,不支持List<int?>
,因为 Java 中根本没有等价的类型 - 虽然声明时泛型参数
<String?>
可为空,但生成的代码仍是不可为空的
- 例如,仅支持
The default is to generate
null-safe
code but in order to generatenon-null-safe
code run Pigeon with the extra argument--no-dart_null_safety
.
使用步骤
- 执行命令
dart pub add pigeon
添加依赖- 也可手动在
pubspec.yaml
中添加依赖:pigeon: ^3.1.0
- 还需要在开发过程中依赖即可,即以
dev_dependencies
方式依赖
- 也可手动在
- 在
lib
目录外定义一个新的目录,用于存放定义通讯接口.dart
文件,例如pigeons/message.dart
- 在
pigeons/message.dart
中定义数据结构、声明通讯接口 - 执行
flutter pub run pigeon xx
命令生成 Flutter 所需的.dart
文件及客户端所需的.java
文件 - 将生成的
.dart
文件移动到lib
目录,将客户端所需的.java
文件移动到正确的包名目录下 - 在客户端实现
@HostApi
中声明的方法,在 Flutter 端实现@FlutterApi
中声明的方法 - 在客户端和 Flutter 端调用静态方法
xxx.setup
注册 method channel - 在合适的时机调用 method channel 即可
Pigeon 使用案例
定义数据结构及声明接口
import 'package:pigeon/pigeon.dart';
// ------------------------------------- 定义数据类型 -------------------------------------
class Book {
int? id; // Initialization isn't supported for fields in Pigeon data classes
String? title; // 不支持初始值,所以只能使用 nullable 类型,也即默认值都是 null
Author? author; // 支持嵌套类
}
class Author {
String? name;
bool? male;
StateEnum? state; // 支持枚举
}
enum StateEnum { success, error, unknown } // 枚举类
// ------------------------------------- 定义 native 方法 -------------------------------------
@HostApi() // 使用注解 @HostApi 修饰的方法,是在 native 中实现的,可以被 Flutter 调用的方法
abstract class TestBookApi {
Book? search(String? keyword); // 支持可为空的参数或返回值
List<Book> searchList(String keyword); // 也支持不可为空的参数或返回值
List<Book?> searchList2(List<String?> keys); // 虽然定义的泛型参数 <String?> 可为空,但生产的代码仍是不可为空的
void testNoArguments(); // 也支持没有参数或没有返回值
}
@HostApi()
abstract class TestAsyApi {
@async
String calculate(int key); //默认生成同步的 handlers,可以使用 @async 注解异步响应消息
}
@HostApi()
abstract class TestTaskQueueApi {
@TaskQueue(type: TaskQueueType.serialBackgroundThread)
int add(int x, int y);
}
// ------------------------------------- 定义 Flutter 方法 -------------------------------------
@FlutterApi() // 使用注解 @FlutterApi 修饰的方法,是在 Flutter 中实现的,可以被 native 调用的方法
abstract class TestFlutterApi {
String getYourName(int key);
}
Android 端的同步 HostApi
object TestBookApiImpl : Pigeon.TestBookApi {
override fun search(keyword: String?): Pigeon.Book = Pigeon.Book().apply {
println("search $keyword isMainThread: ${isMainThread()}") // 默认回调在主线程
id = null // 参数、返回值、属性都有明确的 @Nullable 或 @NonNull 注解,但是属性都是 @Nullable 的,默认值都是 null
title = "《我的$keyword》"// 支持调用 set/get 方法,也支持使用 Builder 模式
author = Pigeon.Author.Builder()
.setName("白乾涛")
.setMale(true)
.setState(Pigeon.StateEnum.success)
.build()
}
override fun searchList(keyword: String): MutableList<Pigeon.Book> = mutableListOf(Pigeon.Book()).also {
println("searchList $keyword isMainThread: ${isMainThread()}")
}
override fun searchList2(keys: MutableList<String>): MutableList<Pigeon.Book> = mutableListOf(Pigeon.Book()).also {
println("searchList2 $keys isMainThread: ${isMainThread()}")
}
override fun testNoArguments() = println("testNoArguments isMainThread: ${isMainThread()}")
}
Android 端的异步 HostApi
object TestAsyApiImpl : Pigeon.TestAsyApi {
override fun calculate(key: Long, result: Pigeon.Result<String>?) {
println("search $key isMainThread: ${isMainThread()}") // 注意,这里也是回调在主线程
if (Random.nextBoolean()) {
Thread {
Thread.sleep(1000 * 3) // 可以在子线程中回调
result?.success("白乾涛") // 异步调用 native 时,native 不是直接返回,而是以回调的形式响应
}.start()
} else {
result?.error(Throwable("异常了")) // Flutter 端需要捕获异常
}
}
}
Android 端的 TaskQueueApi
object TestTaskQueueApiImpl : Pigeon.TestTaskQueueApi {
override fun add(x: Long, y: Long): Long {
println("add $x $y isMainThread: ${isMainThread()}") // false
// 一定要注意了,使用 serialBackgroundThread 时是回调在【子线程】,而其他情况都是回调在【主线程】
return x + y
}
}
Android 端的核心逻辑
class MyFlutterActivity : FlutterActivity() {
var mFlutterApi: Pigeon.TestFlutterApi? = null
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
val binaryMessenger: BinaryMessenger = flutterEngine.dartExecutor.binaryMessenger
Pigeon.TestBookApi.setup(binaryMessenger, TestBookApiImpl)
Pigeon.TestAsyApi.setup(binaryMessenger, TestAsyApiImpl)
Pigeon.TestTaskQueueApi.setup(binaryMessenger, TestTaskQueueApiImpl)
mFlutterApi = Pigeon.TestFlutterApi(binaryMessenger)
callFlutterMethod()
}
private fun callFlutterMethod() {
val handler = Handler(Looper.getMainLooper())
(0..20)
.map { it.toLong() * 100 }
.forEach {
handler.postDelayed(it) {
mFlutterApi?.getYourName(it) { value -> // 必须在主线程中调用
println("从 Flutter 获取到的值是:$value ,isMainThread:${isMainThread()}") // true,回调在主线程
}
}
}
}
}
fun Handler.postDelayed(delay: Long, runnable: Runnable) = postDelayed(runnable, delay)
fun isMainThread() = Looper.getMainLooper().thread == Thread.currentThread()
Flutter 端的 FlutterApi
定义在
lib/flutter_api.dart
中
import 'package:flutter/material.dart';
import 'package:qt_flutter_module/pigeon.dart';
class TestFlutterApiImpl extends TestFlutterApi {
@override
String getYourName(int key) {
debugPrint("Flutter 收到 native 的请求:$key");
return "结果$key";
}
}
Flutter 端的核心逻辑
import 'package:flutter/material.dart';
import 'flutter_api.dart';
import 'pigeon.dart';
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
TestBookApi api = TestBookApi(); // 注意,引用的是 lib/pigeon.dart 下的类,而不是 pigeons 目录下的
TestAsyApi asyApi = TestAsyApi();
TestTaskQueueApi taskQueueApi = TestTaskQueueApi();
@override
void initState() {
super.initState();
TestFlutterApi.setup(TestFlutterApiImpl()); // 同样需要在使用前注入 method channel
}
void _incrementCounter() {
setState(() => _counter++);
callNativeMethod(); // 在合适的时机调用 method channel
}
void callNativeMethod() {
if (_counter % 5 == 0) {
api.search("哈哈").then((book) { // 都是异步回调
if (book != null) {
debugPrint("查询结果:${book.id} - ${book.title}");
Author? author = book.author;
if (author != null) {
debugPrint("作者信息:${author.name} - ${author.male} - ${author.state}");
}
}
});
} else if (_counter == 1) {
api.searchList("哈哈哈").then((list) => debugPrint("返回数量 ${list.length}"));
} else if (_counter == 2) {
api.searchList2(["啊", "哈"]).then((list) => debugPrint("返回数量 ${list.length}"));
} else if (_counter == 3) {
api.testNoArguments();
taskQueueApi.add(2, 3).then((value) => debugPrint("求和结果:$value"));
} else {
asyApi.calculate(10088).then((value) => debugPrint("返回值为 $value"));
}
}
@override
Widget build(BuildContext context) { }
}
2022-5-22
标签:Pigeon,dart,线程,MethodChannel,null,Flutter,result 来源: https://www.cnblogs.com/baiqiantao/p/16296811.html