• Flutter 和 Android 通讯 Pigeon 类型安全


    本文地址


    目录

    Pigeon 简介

    Pigeon is a code generator tool to make communication between Flutter and the host platform type-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 the names and datatypes of messages
    • supports nested classes 支持嵌套类
    • supports grouping messages into APIs 支持分组(即支持以 class 为单位分组)
    • generation of asynchronous wrapper code and sending 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 generate non-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

  • 相关阅读:
    【30篇突击 android】源码统计 二十
    【eoeAndroid社区索引】Android控件知识汇总
    【eoeAndroid社区索引】Android二维码知识汇总
    【30篇突击 android】源码统计 十八
    Andorid中xml资料汇总
    android 问题汇总系列之六
    android 问题汇总系列之七
    Android Permission大全1.0最终版本
    android中绘图的方法
    Nokia60的Image.createImage死机问题
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/16340272.html
Copyright © 2020-2023  润新知