其他分享
首页 > 其他分享> > Flutter 和原生间相互通讯 MethodChannel Pigeon

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

通道和平台线程

通道和平台线程

简单来说就是:

被注解 @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 platform type-safe, easier and faster.

使用 MethodChannel 在 host 和 client 之间进行通信,并不是类型安全的。为了正确通信,host 和 client 必须声明相同的参数和数据类型。Pigeon 可以用作 MethodChannel 的替代品。

特性

命令参数

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 文件

空安全

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.

使用步骤

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