簡介
Pigeon 是在 Flutter 1.20 發(fā)布的,為了解決 Flutter 調(diào)用 native 代碼過于麻煩和困難,需要在字符串的基礎(chǔ)上匹配函數(shù)名和參數(shù),通過使用這個包可以實現(xiàn)
-
與 native 類型安全通信
Flutter 調(diào)用 Native 方法 (
@HostApi())Native 調(diào)用 Flutter 方法 (
@FlutterApi())
通過自動生成減少手寫代碼量
這篇文章主要是詳細(xì)描述了如何使用 Pigeon 從 Flutter 端調(diào)用一個在 Swift/Kotlin 中實現(xiàn)的簡單 getPlatformVersion 方法。實例項目可在此處獲得。
創(chuàng)建 Flutter app
$ flutter create -i swift -a kotlin flutter_pigeon
Pigeon 是做什么的
Java/Objective-C 接口協(xié)議是根據(jù) Dart 端定義的參數(shù)和返回值信息自動生成的。
原生基于這些接口實現(xiàn),可以與 Flutter 端進(jìn)行類型安全通信。
類似于使用 TypeScript 創(chuàng)建類型定義文件。
安裝
# pubspec.yaml
dev_dependencies:
pigeon: ^1.0.0
Dart
首先,創(chuàng)建一個定義與原生通信的 dart 文件。在根目錄下創(chuàng)建 pigeon/schema.dart 文件
// schema.dart
import 'package:pigeon/pigeon.dart';
// Flutter 調(diào)用原生代碼
@HostApi()
abstract class Api {
String getPlatformVersion();
}
// 原生調(diào)用 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"
運(yùn)行腳本自動生成對應(yīng)的接口文件
$ ./run_pigeon.sh
腳本運(yùn)行成功后,Android、iOS 項目會生成對應(yīng)的接口文件,如圖所示:


Android 和 iOS 原生中需要分別實現(xiàn)對應(yīng)的接口協(xié)議
Kotlin
API 接口寫在 Pigeon 生成的 Java 文件中,所以創(chuàng)建一個實現(xiàn)它的類,并傳遞給 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)
// 調(diào)用 Flutter Api
Timer().schedule(object: TimerTask() {
override fun run() {
// Platform Channel func 必須在主線程上執(zhí)行該方法
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 協(xié)議寫在生成的文件中,創(chuàng)建一個實現(xiàn)該協(xié)議的類 MyApi
創(chuàng)建 SwiftPigeonService.swift 文件 實現(xiàn)協(xié)議方法
// 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)
// 注意:方法必須在主線程上執(zhí)行
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 調(diào)用原生方法
在需要調(diào)用原生方法函數(shù)的地方導(dǎo)入并使用 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;
}
原生調(diào)用 Flutter 方法
Flutter
實現(xiàn) @FutterApi() 中的協(xié)議方法,供 Native 調(diào)用
// api_flutter.dart
// 實現(xiàn) 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(),
);
}
}
總結(jié)
Pigeon 自動生成使用 MethodChannel 相關(guān)部分內(nèi)容。(嚴(yán)格來說,Pigeon 使用的是BasicMessageChannel)
預(yù)定義模式允許與 native 進(jìn)行類型安全通信
生成的代碼是 Java/Objective-C,但是由于 Kotlin 可以調(diào)用 Java,Swift 可以調(diào)用 Objective-C
不必了解 Dart 端代碼 (只需調(diào)用自動生成的 API)
不必了解原生代碼 (只需實現(xiàn)自動生成的接口)
實際官方插件 video_player 已經(jīng)在使用了
Native 調(diào)用 Platform Channel Method 必須在主線程上執(zhí)行