A new Flutter plugin use pigeon, 实现 flutter 和 native 互调( Android, iOS).
Flutter官方提供的Pigeon插件,通过dart入口,生成双端通用的模板代码,Native部分只需通过重写模板内的接口,无需关心methodChannel部分的具体实现,入参,出参也均通过生成的模板代码进行约束。假设接口新增,或者参数修改,只需要在dart侧更新协议文件,生成双端模板,即可达到同步更新。
- 在
Android Studio
中,创建 flutter plugin project - 控制台,命令行
// The plugin project was generated without specifying the `--platforms` flag, no new platforms are added.
// To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same
// directory. You can also find detailed instructions on how to add platforms in the `pubspec.yaml`
flutter create --org com.zero --template plugin flutterPigeon
// 正确
flutter create --org com.zero --template plugin --platforms android,ios flutterPigeon
注意 创建命令需要带
--platforms android,ios
,才会创建 Android iOS目录
在Android Studio
中运行,gradle
版本要求为 7.0.2
,需要修改各个目录下的gradle和build配置
修改内容如下:
// android/gradle/wrapper/gradle-wrapper.properties
// example/android/gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
// android/build.gradle
// example/android/app/build.gradle
ext.kotlin_version = '1.5.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
至此,project 构建完成,可运行 flutterPlugin/example
首先在pubspec.yaml
中添加依赖
// flutterPigeon\pubspec.yaml
dependencies:
flutter:
sdk: flutter
pigeon: 9.0.4
然后按照官方的要求添加一个pigeons
目录,这里我们放dart侧的入口文件,内容为接口、参数、返回值的定义,后面通过pigeon的命令,生产native端代码。
pigeons/message.dart
import 'package:pigeon/pigeon.dart';
// 输出配置
// 控制台执行:flutter pub run pigeon --input pigeons/message.dart
@ConfigurePigeon(PigeonOptions(
dartOut: './lib/message.dart',
kotlinOut: 'android/src/main/kotlin/com/zero/flutter_pigeon_plugin/Pigeon.kt',
kotlinOptions: KotlinOptions(
// copyrightHeader: ['zero'],
package: 'com.zero.flutter_pigeon_plugin',
),
//see ios/flutter_pigeon_plugin.podspec -> s.source_files = 'Classes/**/*'
objcHeaderOut: 'ios/Classes/Pigeon.h',
objcSourceOut: 'ios/Classes/Pigeon.m',
objcOptions: ObjcOptions(
prefix: 'FLT',
),
))
class SearchRequest {
String query;
}
class SearchReply {
String result;
}
/// flutter call native
@HostApi()
abstract class FlutterCallNativeApi {
SearchReply search(SearchRequest request);
}
/// native call flutter
@FlutterApi()
abstract class NativeCallFlutterApi{
SearchReply query(SearchRequest request);
}
message.dart
文件中定义了请求参数类型、返回值类型、通信的接口以及pigeon输出的配置。
这里@HostApi()
标注了通信对象和接口的定义,后续需要在native
侧注册该对象,在flutter
侧通过该对象的实例来调用接口。
这里@FlutterApi()
标注了通信对象和接口的定义,后续需要在flutter
侧注册该对象,在native
侧通过该对象的实例来调用接口。
configurePigeon
为执行pigeon生产双端模板代码的输出配置(输出路径文件夹需先创建, 如 ios/Runner/
)。
dartOut
为dart侧输出位置objcHeaderOut、objcSourceOut
为iOS侧输出位置prefix
为插件默认的前缀kotlinOut、kotlinOptions.package
为Android侧输出位置和包名
之后我们只需要执行如下命令,就可以生成对应的代码到指定目录中。
flutter pub run pigeon --input pigeons/message.dart
--input
为我们的输入文件
解决 Android native
生成的代码Pigeon
中的错误
// android/build.gradle
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
android/src/main/kotlin/com/zero/flutter_pigeon_plugin/FlutterPigeonPlugin.kt
class FlutterPigeonPlugin: FlutterPlugin, FlutterCallNativeApi {
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
// setup
FlutterCallNativeApi.setUp(flutterPluginBinding.binaryMessenger, this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
FlutterCallNativeApi.setUp(binding.binaryMessenger, null)
}
// flutter call native
override fun search(arg: SearchRequest): SearchReply {
val reply = SearchReply(arg.query + "-nativeResult")
// ------ native call flutter
nativeApi.query(arg){
Toast.makeText(context, reply.result, Toast.LENGTH_SHORT).show()
}
// -------
// native reply flutter
return reply
}
}
Android侧
example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
,build时自动生成
/**
* Generated file. Do not edit.
* This file is generated by the Flutter tool based on the
* plugins that support the Android platform.
*/
@Keep
public final class GeneratedPluginRegistrant {
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
flutterEngine.getPlugins().add(new com.zero.flutter_pigeon_plugin.FlutterPigeonPlugin());
}
}
example/android/app/src/main/kotlin/com/zero/flutter_pigeon_plugin_example/MainActivity.kt
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
// notice : here will auto call
// flutterEngine.getPlugins().add(new com.zero.flutter_pigeon_plugin.FlutterPigeonPlugin());
super.configureFlutterEngine(flutterEngine)
}
}
super.configureFlutterEngine(flutterEngine)
中通过反射,调用了GeneratedPluginRegistrant.registerWith()
flutter 侧
example/lib/main.dart
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
Future<void> getNativeResult() async{
FlutterCallNativeApi api = FlutterCallNativeApi();
SearchRequest request = SearchRequest()..query = "Zero";
SearchReply reply = await api.search(request);
setState(() {
_platformVersion = reply.result;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(
children: [
Text('Running on: $_platformVersion\n'),
MaterialButton(
height: 40,
color: Colors.blue,
textColor: Colors.white,
elevation: 5,
splashColor: Colors.teal,
padding: EdgeInsets.all(8),
child: Text("点击调用 native"),
onPressed: ()=>{
getNativeResult()
})
],
),
),
),
);
}
}
android/src/main/kotlin/com/zero/flutter_pigeon_plugin/FlutterPigeonPlugin.kt
class FlutterPigeonPlugin: FlutterPlugin, FlutterCallNativeApi {
lateinit var nativeApi : NativeCallFlutterApi
lateinit var context: Context
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
// setup
FlutterCallNativeApi.setup(flutterPluginBinding.binaryMessenger, this)
nativeApi = NativeCallFlutterApi(flutterPluginBinding.binaryMessenger)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
FlutterCallNativeApi.setup(binding.binaryMessenger, null)
}
// flutter call native
override fun search(arg: SearchRequest): SearchReply {
val reply = SearchReply.Builder().setResult(arg.query + "-nativeResult").build()
// ------ native call flutter
nativeApi.query(arg, object : NativeCallFlutterApi.Reply<SearchReply>{
override fun reply(reply: SearchReply) {
// flutter reply
Toast.makeText(context, reply.result, Toast.LENGTH_SHORT).show()
}
})
// -------
// native reply flutter
return reply
}
}
ios/Classes/FlutterPigeonPlugin.h
#import <Flutter/Flutter.h>
#import "Pigeon.h"
@interface FlutterPigeonPlugin : NSObject<FlutterPlugin>
@end
@interface FlutterCallNativeApi : NSObject<FLTFlutterCallNativeApi>
- (void)setupFLTNativeCallFlutterApiWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger;
@end
ios/Classes/FlutterPigeonPlugin.m
#import "FlutterPigeonPlugin.h"
#if __has_include(<flutter_pigeon_plugin/flutter_pigeon_plugin-Swift.h>)
#import <flutter_pigeon_plugin/flutter_pigeon_plugin-Swift.h>
#else
#import "flutter_pigeon_plugin-Swift.h"
#endif
#import <UIKit/UIKit.h>
@implementation FlutterPigeonPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftFlutterPigeonPlugin registerWithRegistrar:registrar];
FlutterCallNativeApi *api = [[FlutterCallNativeApi alloc] init];
[api setupFLTNativeCallFlutterApiWithBinaryMessenger:[registrar messenger]];
FLTFlutterCallNativeApiSetup([registrar messenger], api);
}
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
NSLog(@"didFinishLaunchingWithOptions");
return YES;
}
@end
@interface FlutterCallNativeApi ()
@property(nonatomic, strong) FLTNativeCallFlutterApi *nativeCallFlutterApi;
@end
@implementation FlutterCallNativeApi
- (nullable FLTSearchReply *)searchRequest:(FLTSearchRequest *)request error:(FlutterError *_Nullable *_Nonnull)error
{
NSString *result = @"flutter call iOS ";
FLTSearchReply *reply = [FLTSearchReply makeWithResult:result];
[self.nativeCallFlutterApi queryRequest:[FLTSearchRequest makeWithQuery:@"iOS call flutter"] completion:^(FLTSearchReply *_Nullable reply, FlutterError * _Nullable error) {
NSLog(@"queryRequest-> queryRequest : %@", reply.result);
}];
return reply;
}
- (void)setupFLTNativeCallFlutterApiWithBinaryMessenger:(id<FlutterBinaryMessenger>)binaryMessenger
{
self.nativeCallFlutterApi = [[FLTNativeCallFlutterApi alloc] initWithBinaryMessenger:binaryMessenger];
}
@end
flutter 侧
example/lib/main.dart
class NativeCallFlutterApiImpl extends NativeCallFlutterApi{
@override
SearchReply query(SearchRequest arg) {
SearchReply reply = SearchReply(result: arg.query + "-flutterResult");
return reply;
}
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
@override
void initState() {
super.initState();
NativeCallFlutterApi.setup(NativeCallFlutterApiImpl());
}
}
参考