Flutter 和 Android 通讯 Pigeon 类型安全
目录
目录
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("calculate $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 端的核心逻辑
fun isMainThread() = Looper.getMainLooper().thread == Thread.currentThread()
fun Handler.postDelayed(delay: Long, runnable: Runnable) = postDelayed(runnable, delay)
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,回调在主线程
}
}
}
}
}
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-6-3
本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/16340272.html