[Flutter] 使用 Pigeon 实现跨平台方法调用
简介
Pigeon 是在 Flutter 1.20 发布的,为了解决 Flutter 调用 native 代码过于麻烦和困难,需要在字符串的基础上匹配函数名和参数,通过使用这个包可以实现
-
与 native 类型安全通信
-
Flutter 调用 Native 方法 (
@HostApi()
) -
Native 调用 Flutter 方法 (
@FlutterApi()
)
-
-
通过自动生成减少手写代码量
这篇文章主要是详细描述了如何使用 Pigeon 从 Flutter 端调用一个在 Swift/Kotlin 中实现的简单 getPlatformVersion
方法。实例项目可在此处获得。
创建 Flutter app
$ flutter create -i swift -a kotlin flutter_pigeon
Pigeon 是做什么的
Java/Objective-C 接口协议是根据 Dart 端定义的参数和返回值信息自动生成
的。
原生基于这些接口实现,可以与 Flutter 端进行类型安全通信。
类似于使用 TypeScript 创建类型定义文件。
安装
# pubspec.yaml
dev_dependencies:
pigeon: ^1.0.0
Dart
首先,创建一个定义与原生通信的 dart 文件。在根目录下创建 pigeon/schema.dart 文件
// schema.dart
import 'package:pigeon/pigeon.dart';
// Flutter 调用原生代码
@HostApi()
abstract class Api {
String getPlatformVersion();
}
// 原生调用 Flutter
@FlutterApi()
abstract class FlutterApi {
void sessionInvalid();
}
新建脚本文件 run_pigeon.sh
# run_pigeon.h
$ flutter pub run pigeon \
--input pigeon/schema.dart \
--dart_out lib/api_generated.dart \
--objc_header_out ios/Runner/Pigeon.h \
--objc_source_out ios/Runner/Pigeon.m \
--objc_prefix FLT \
--java_out android/app/src/main/java/io/flutter/plugins/Pigeon.java \
--java_package "io.flutter.plugins"
运行脚本自动生成对应的接口文件
$ ./run_pigeon.sh
脚本运行成功后,Android、iOS 项目会生成对应的接口文件,如图所示:
android_pigeon.png pigeon_ios.pngAndroid 和 iOS 原生中需要分别实现对应的接口协议
Kotlin
API 接口写在 Pigeon 生成的 Java 文件中,所以创建一个实现它的类,并传递给 setup 方法。
// MainActivity.kt
class MainActivity: FlutterActivity() {
// 声明 Flutter Api
lateinit var flutterApi: Pigeon.MyApi
private class MyApi: Pigeon.Api {
override fun getPlatformVersion(result: Pigeon.Result<String>?) {
var version = "Android ${android.os.Build.VERSION.RELEASE}"
result?.success(version)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 调用 Flutter Api
Timer().schedule(object: TimerTask() {
override fun run() {
// Platform Channel func 必须在主线程上执行该方法
Handler(Looper.getMainLooper()).post {
// Call the desired channel message here.
flutterApi.sessionInValid { Log.d("Call func", "====Call flutter func!===") }
}
}
}, 1000)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// 2. setup()
Pigeon.Api.setup(flutterEngine.dartExecutor.binaryMessenger, MyApi())
// setup Flutter api
flutterApi = Pigeon.MyApi(flutterEngine.dartExecutor.binaryMessenger)
}
}
Swift
在桥接文件 ios/RunnerRunner-Bridging-Header.h
添加 import 语句,让 Pigeon 生成的 Objective-C 文件对 Swift 可见
// ios / Runner / Runner-Bridging-Header.h
#import "Pigeon.h"
由于 API 协议写在生成的文件中,创建一个实现该协议的类 MyApi
创建 SwiftPigeonService.swift 文件 实现协议方法
// SwiftPigeonService.swift
public class SwiftPigeonService: NSObject, FLTApi {
public func getPlatformVersion(completion: @escaping (String?, FlutterError?) -> Void) {
let result = "iOS " + UIDevice.current.systemVersion
completion(result, nil)
}
}
在 AppDelegate 中实例化 API 并将其传递给 setup 方法
// AppDelegate.swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var _flutterApi: FLTMyApi?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
FLTApiSetup(controller.binaryMessenger, SwiftPigeonService())
GeneratedPluginRegistrant.register(with: self)
_flutterApi = FLTMyApi(binaryMessenger: controller.binaryMessenger)
// 注意:方法必须在主线程上执行
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
self._flutterApi?.sessionInValid(completion: { (error) in
print("===Native Call flutter func!===")
})
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
Flutter 调用原生方法
在需要调用原生方法函数的地方导入并使用 Pigeon 生成的 dart 文件
// home.dart
Future<String?> callNativePlatform() async {
// `Api` is a class generated by Pigeon
final api = Api();
final res = await api.getPlatformVersion();
return res;
}
原生调用 Flutter 方法
Flutter
实现 @FutterApi()
中的协议方法,供 Native 调用
// api_flutter.dart
// 实现 Flutter ApiGenerated 的接口
class FTLApiManager extends MyApi {
@override
Future<void> sessionInValid() async {
if (kDebugMode) {
print('====Call session invalid====');
}
}
}
// main.dart
void main() async {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
// 注入 method channel
MyApi.setup(FTLApiManager());
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
总结
-
Pigeon 自动生成使用 MethodChannel 相关部分内容。(严格来说,Pigeon 使用的是BasicMessageChannel)
-
预定义模式允许与 native 进行类型安全通信
-
生成的代码是 Java/Objective-C,但是由于 Kotlin 可以调用 Java,Swift 可以调用 Objective-C
-
不必了解 Dart 端代码 (只需调用自动生成的 API)
-
不必了解原生代码 (只需实现自动生成的接口)
-
实际官方插件 video_player 已经在使用了
-
Native 调用 Platform Channel Method 必须在主线程上执行