Flutter插件是什么?
在开发Flutter应用过程中会涉及到平台相关接口调用,例如数据库操作、相机调用、定位等业务场景。Flutter自身并不支持直接在平台上实现这些功能,而是通过插件包接口去调用指定平台API从而实现原生平台上特定功能
创建Flutter插件工程
在Android Studio里点击Flie - New - New Flutter Project,在左侧里选中Flutter,然后点击Next。 wecom-temp-d83e54cdf760937727daf020f4128b67.png- 在Project Name里输入项目名,只能是小写英文
- 在Project type里选择Plugin
- 在Organization里写包名,.Project Name会拼在包名的最后面成为包名的一部分
使用命令创建插件
flutter create --org com.example --template=plugin plugin_name -I swift
其中 com.example 是插件包名的一部分,plugin_name 是插件的名称。插件的完整包名为 com.example.plugin_name
插件目录结构 wecom-temp-7353b068adcc6be10902b1ae2a3c2cf1.png
我们需要关注的主要有以下4个:
- android目录是用来开发Android端的插件功能
- ios目录是用来开发iOS端的插件功能
- lib是实现Flutter插件接口的代码
- example目录是测试项目,用来测试开发出来的插件的
插件功能开发
App端实现
在register方法里,我们注册了一个通道(已经默认注册了),通道名默认就是项目名,该名字在通信里必须是唯一的,可以修改,一旦修改,需要把dart和android里的名字也一并修改。
在handle方法里,实现Flutter调用原生的API,其中call.method就是方法名,call.arguments就是Flutter传递过来的参数。使用result(返回值)可以把结果返回给Flutter。
当找不到方法名时,可以返回FlutterMethodNotImplemented给Flutter表示该方法还没实现,以此来做版本兼容
public class SwiftTestPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "test_plugin", binaryMessenger: registrar.messenger())
let instance = SwiftTestPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "getNetWorkType" { // 获取网络类型的实现
result("WIFI")
} else if call.method == "bonusPoints" { // 使用参数的实现
let array = call.arguments as! Array<Int>
result(array[0] + array[1])
} else if call.method == "getPlatformVersion" { // 默认的实现
result("iOS " + UIDevice.current.systemVersion)
} else {
// 找不到方法
result(FlutterMethodNotImplemented)
}
}
}
Flutter端实现
增加方法用来调用iOS端的方法,方法名不需要和iOS端的保持一致,主要是通道里调用iOS端的方法名就行了
abstract class TestPluginPlatform extends PlatformInterface {
/// Constructs a TestPluginPlatform.
TestPluginPlatform() : super(token: _token);
static final Object _token = Object();
static TestPluginPlatform _instance = MethodChannelTestPlugin();
/// The default instance of [TestPluginPlatform] to use.
///
/// Defaults to [MethodChannelTestPlugin].
static TestPluginPlatform get instance => _instance;
/// Platform-specific implementations should set this with their own
/// platform-specific class that extends [TestPluginPlatform] when
/// they register themselves.
static set instance(TestPluginPlatform instance) {
PlatformInterface.verifyToken(instance, _token);
_instance = instance;
}
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
Future<String> getNetWorkType() async {
throw UnimplementedError('getNetWorkType() has not been implemented.');
}
Future<int> add() async {
throw UnimplementedError('add() has not been implemented.');
}
}
插件自动生成的抽象类
class MethodChannelTestPlugin extends TestPluginPlatform {
/// The method channel used to interact with the native platform.
@visibleForTesting
final methodChannel = const MethodChannel('test_plugin');
@override
Future<String?> getPlatformVersion() async {
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
return version;
}
/// 实现iOS端新增的方法
@override
Future<String> getNetWorkType() async {
final String state = await methodChannel.invokeMethod('getNetWorkType');
return state;
}
/// 实现iOS端新增的方法
@override
Future<int> add() async {
final int result = await methodChannel.invokeMethod('bonusPoints', [5, 8]); /// 接收一个数组或者字典作为参数传递给原生端
return result;
}
}
定义methodChannel,最终由methodChannel通过invokeMethod调用原生方法,invokeMethod内的方法名需要与原生定义的方法名一致
class TestPlugin {
Future<String?> getPlatformVersion() {
return TestPluginPlatform.instance.getPlatformVersion();
}
/// 实现iOS端新增的方法
Future<String> getNetWorkType() async {
return TestPluginPlatform.instance.getNetWorkType();
}
/// 实现iOS端新增的方法
Future<int> add() async {
return TestPluginPlatform.instance.add();
}
}
需要注意的是,Flutter和原生通信都是异步的,所以都需要使用await和async
三方库使用
写插件不可避免的会用到第三方库,在使用第三方库的时候,会遇到3种情况:
- 仅原生端使用第三方库
当仅原生端需要依赖某些第三方库时,可以在podspec文件里加上s.dependency '第三方库名' - 仅Flutter端使用第三方库
当仅Flutter端需要依赖某些第三方库时,可以在pubspec.yaml文件里的dependencies部分
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.0.16
- 都使用同一个第三方库
假设Flutter里需要用到url_launcher,然后原生里也需要用到,那我们就得在Flutter的pubspec.yaml文件里的dependencies部分添加依赖包,同时也要在iOS端的podspec文件里加上s.dependency 'url_launcher'
私有库使用
写插件不可避免的会用到私有库,在使用第三方库的时候:
-
原生端需要在podspec文件里加上s.dependency '私有库名'
-
Flutter项目需要在podFile里添加私有的source和path
tips: 可将私有库打包成.a,直接集成在插件里, 避免了私有库引用带来的麻烦
通信的数据类型
原生与Flutter互相通信时使用的数据类型是有限制的,以下是可用的数据类型:
Dart | kotlin | Java | Objective-C | Swift |
---|---|---|---|---|
null | null | null | NSNull | NSNull |
bool | Boolean | java.lang.Boolean | NSNumber numberWithBool: | NSNumber(value: Bool)或者Bool |
int 32位平台 | Int | java.lang.Integer | NSNumber numberWithInt: | NSNumber(value: Int32)或者Int32 |
int | Long | java.lang.Long | NSNumber numberWithLong: | NSNumber(value: Int)或者Int |
double | Double | java.lang.Double | NSNumber numberWithDouble: | NSNumber(value: Double)或者Double |
String | String | java.lang.String | NSString | String或者NSString |
Uint8List | ByteArray | byte[] | FlutterStandardTypedData typedDataWithBytes: | FlutterStandardTypedData(bytes: Data) |
Int32List | IntArray | int[] | FlutterStandardTypedData typedDataWithInt32: | FlutterStandardTypedData(int32: Data) |
Int64List | LongArray | long[] | FlutterStandardTypedData typedDataWithInt64: | FlutterStandardTypedData(int64: Data) |
Float32List | FloatArray | float[] | FlutterStandardTypedData typedDataWithFloat32: | FlutterStandardTypedData(float32: Data) |
Float64List | DoubleArray | double[] | FlutterStandardTypedData typedDataWithFloat64: | FlutterStandardTypedData(float64: Data) |
List | List | java.util.ArrayList | NSArray | Array或者NSArray |
Map | HashMap | java.util.HashMap | NSDictionary | Dictionary或者NSDictionary |
- Swift的基础类型可以用Objective-C的对象类型,集合类型可以兼容Objective-C的集合类型(不过这些都是Swift本身的特性)
- 在使用Swift时,最好还是使用它本身的类型,如果使用Objective-C的类型,就无法判断详细类型,比如Int和Double,在使用Objective-C类型的时候,都是NSNumber
思考
能不能统一生成两端通用的代码?开发是否可以不需要关心Method Channe的具体实现,只需要实现对应接口即可?
pigeon工具插件
一个代码生成工具,让Flutter和宿主平台更安全、更简单、更快地通信。通过Dart入口,生成两端通用的模板代码,原生则只需重写模板内的接口,无需管理Method Channel的实现。参数可以通过模板来同步生成。
目前的pigeon只支持生成OC和Java代码
1. 添加依赖
dev_dependencies:
pigeon: ^3.1.0
2. 添加插件交互类和方法
- @HostApi() 标记的,是用于 Flutter 调用原生的方法。
- @FlutterApi() 标记的,是用于原生调用 Flutter 的方法。
- @async 如果原生的方法,是异步回调那种,你就可以使用这个标记
- 只支持 dart 的基础类型
import 'package:pigeon/pigeon.dart';
class Everything {
bool? aBool;
int? anInt;
double? aDouble;
String? aString;
Uint8List? aByteArray;
Int32List? a4ByteArray;
Int64List? a8ByteArray;
Float64List? aFloatArray;
// ignore: always_specify_types
List? aList;
// ignore: always_specify_types
Map? aMap;
List<List<bool?>?>? nestedList;
Map<String?, String?>? mapWithAnnotations;
}
/// Flutter调用原生的方法
@HostApi()
abstract class HostEverything {
Everything giveMeEverything();
Everything echo(Everything everything);
}
/// 原生调用Flutter的方法
@FlutterApi()
abstract class FlutterEverything {
Everything giveMeEverythingFlutter();
Everything echoFlutter(Everything everything);
}
3. 生成各端插件代码
在项目目录下,执行以下命令:
$flutter pub run pigeon
--input pigeon/schema.dart
--dart_out lib/api_generated.dart
--objc_header_out ios/Classes/AllTypesPigeon.h
--objc_source_out ios/Classes/AllTypesPigeon.m
--objc_prefix FLT
--java_out android/src/main/java/com/akulaku/test_plugin_pigeon/AllTypesPigeon.java
--java_package "com.akulaku.test_plugin_pigeon"
或者执行 ./run_pigeon.sh 脚本内容如下:
flutter pub run pigeon \
--input pigeon/schema.dart \
--dart_out lib/api_generated.dart \
--objc_header_out ios/Classes/AllTypesPigeon.h \
--objc_source_out ios/Classes/AllTypesPigeon.m \
--objc_prefix FLT \
--java_out android/src/main/java/com/akulaku/test_plugin_pigeon/AllTypesPigeon.java \
--java_package "com.akulaku.test_plugin_pigeon"
- input 第二步定义的交互类
- dart_out 对应的dart文件
- objc_header_out/objc_source_out 对应的iOS实现文件
- java_out 对应的android实现文件
执行脚本或者命令生成以下文件:
wecom-temp-92d7254da48d39c25b198cc85c0b27cc.pngiOS实现Flutter调用原生的方法
① 删掉项目中之前的获取版本的原生的和Flutter侧的相关channel代码
public class SwiftTestPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "test_plugin", binaryMessenger: registrar.messenger())
let instance = SwiftTestPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
}
② 在AllTypesPigeon.m中自动生成了一个方法HostEverythingSetup
void FLTHostEverythingSetup(id<FlutterBinaryMessenger> binaryMessenger, NSObject<FLTHostEverything> *api) {
{
FlutterBasicMessageChannel *channel =
[[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.HostEverything.giveMeEverything"
binaryMessenger:binaryMessenger
codec:FLTHostEverythingGetCodec() ];
if (api) {
NSCAssert([api respondsToSelector:@selector(giveMeEverythingWithError:)], @"FLTHostEverything api (%@) doesn't respond to @selector(giveMeEverythingWithError:)", api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
FlutterError *error;
FLTEverything *output = [api giveMeEverythingWithError:&error];
callback(wrapResult(output, error));
}];
}
else {
[channel setMessageHandler:nil];
}
}
{
FlutterBasicMessageChannel *channel =
[[FlutterBasicMessageChannel alloc]
initWithName:@"dev.flutter.pigeon.HostEverything.echo"
binaryMessenger:binaryMessenger
codec:FLTHostEverythingGetCodec() ];
if (api) {
NSCAssert([api respondsToSelector:@selector(echoEverything:error:)], @"FLTHostEverything api (%@) doesn't respond to @selector(echoEverything:error:)", api);
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
NSArray *args = message;
FLTEverything *arg_everything = GetNullableObjectAtIndex(args, 0);
FlutterError *error;
FLTEverything *output = [api echoEverything:arg_everything error:&error];
callback(wrapResult(output, error));
}];
}
else {
[channel setMessageHandler:nil];
}
}
}
③ 在SwiftTestPluginPigeonPlugin的注册方法里,调用这个setup方法进行初始化和设置
public static func register(with registrar: FlutterPluginRegistrar) {
let messenger: FlutterBinaryMessenger = registrar.messenger()
let api: HostEverything & NSObjectProtocol = SwiftTestPluginPigeonPlugin.init()
FLTHostEverythingSetup(messenger, api)
}
④ iOS/Classes目录下,创建test_plugin_pigeon.h文件,导入头文件,此文件在iOS自动生成的<test_plugin_pigeon/test_plugin_pigeon-Swift.h>文件中会自动引用。
#ifndef test_plugin_pigeon_h
#define test_plugin_pigeon_h
#import "AllTypesPigeon.h"
#endif /* test_plugin_pigeon_h */
⑤ SwiftFlutterPigeonPlugin遵循HostEverything协议,实现Flutter调原生的方法
import Flutter
import UIKit
/// 遵循HostEverything协议,实现Flutter调原生的方法
public class SwiftTestPluginPigeonPlugin: NSObject, FlutterPlugin, FLTHostEverything {
public static func register(with registrar: FlutterPluginRegistrar) {
let messenger: FlutterBinaryMessenger = registrar.messenger()
let api: FLTHostEverything & NSObjectProtocol = SwiftTestPluginPigeonPlugin.init()
FLTHostEverythingSetup(messenger, api)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
result("iOS " + UIDevice.current.systemVersion)
}
public func giveMeEverythingWithError(_ error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> FLTEverything? {
let everyThing = FLTEverything()
everyThing.aString = "原生返给Flutter的字符串"
everyThing.aBool = false
everyThing.anInt = 11
return everyThing
}
public func echo(_ everything: FLTEverything, error: AutoreleasingUnsafeMutablePointer<FlutterError?>) -> FLTEverything? {
let everyThing = FLTEverything()
everyThing.aString = "原生返给Flutter的字符串"
everyThing.aBool = false
everyThing.anInt = 11
return everyThing
}
}
上面都是讲Flutter怎么调原生的,那具体是怎么通信的呢?
原生与Flutter通信
Flutter 提供了 Platform Channel 机制,让消息能够在 native 与 Flutter 之间进行传递
Flutter定义了三种不同类型的Channel通信类,
- BasicMessageChannel:用于传递字符串和半结构化的信息。
- MethodChannel:用于传递方法调用(method invocation)。
- EventChannel: 用于数据流(event streams)的通信。
Channel 使用 codec 消息编解码器,支持从基础数据到二进制格式数据的转换、解析。
三种Channel之间互相独立,每种Channel均有三个重要成员变量:
- name: String类型,代表Channel的名字,也是其唯一标识符。
- messager:BinaryMessenger类型,代表消息信使,是消息的发送与接收的工具。
- codec: MessageCodec类型或MethodCodec类型,代表消息的编解码器。
一个Flutter应用中可能存在多个Channel,每个Channel在创建时必须指定一个独一无二的name,Channel之间使用name来区分彼此。当有消息从Flutter端发送到Platform端时,会根据其传递过来的channel name找到该Channel对应的Handler(消息处理器)。
Channel 注册
以FlutterMethodChannel为例看一个注册流程
FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
methodChannelWithName:@"samples.flutter.io/battery"
binaryMessenger:controller];
[batteryChannel setMethodCallHandler:^(FlutterMethodCall* call,
FlutterResult result) {
if ([@"getBatteryLevel" isEqualToString:call.method]) {
int batteryLevel = [weakSelf getBatteryLevel];
if (batteryLevel == -1) {
result([FlutterError errorWithCode:@"UNAVAILABLE"
message:@"Battery info unavailable"
details:nil]);
} else {
result(@(batteryLevel));
}
......
}];
初始化一个桥接,传入桥接名和处理消息发送接收的类,桥接名为"samples.flutter.io/battery"
通过setMethodCallHandler 方法,在 native 项目中注册该桥接的回调 handler,其中参数 result 表示向 Flutter 回传的结果
- (void)setMethodCallHandler:(FlutterMethodCallHandler)handler {
if (!handler) {
[_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil];
return;
}
#此处又包装了一个回调 messageHandler,用于解码二进制消息 message,并向 Flutter 侧回传执行结果 reply
FlutterBinaryMessageHandler messageHandler = ^(NSData* message, FlutterBinaryReply callback) {
FlutterMethodCall* call = [_codec decodeMethodCall:message];
handler(call, ^(id result) {
if (result == FlutterMethodNotImplemented)
callback(nil);
else if ([result isKindOfClass:[FlutterError class]])
callback([_codec encodeErrorEnvelope:(FlutterError*)result]);
else
callback([_codec encodeSuccessEnvelope:result]);
});
};
[_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:messageHandler];
}
- (void)setMessageHandlerOnChannel:(NSString*)channel
binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
NSAssert(channel, @"The channel must not be null");
[_engine.get() setMessageHandlerOnChannel:channel binaryMessageHandler:handler];
}
- (void)setMessageHandlerOnChannel:(NSString*)channel
binaryMessageHandler:(FlutterBinaryMessageHandler)handler {
NSAssert(channel, @"The channel must not be null");
FML_DCHECK(_shell && _shell->IsSetup());
self.iosPlatformView->GetPlatformMessageRouter().SetMessageHandler(channel.UTF8String, handler);
}
// PlatformMessageRouter
void PlatformMessageRouter::SetMessageHandler(const std::string& channel,
FlutterBinaryMessageHandler handler) {
message_handlers_.erase(channel);
if (handler) {
message_handlers_[channel] =
fml::ScopedBlock<FlutterBinaryMessageHandler>{handler, fml::OwnershipPolicy::Retain};
}
}
PlatformMessageRouter的message_handlers_ 是个哈希表,key 是桥接名,value 放 handle。原生注册桥接方法,其实就是维护一个 map 对象
Channel调用方法
dart 侧调用获取电量的桥接方法
static const MethodChannel methodChannel =
MethodChannel('samples.flutter.io/battery');
final int result = await methodChannel.invokeMethod('getBatteryLevel');
channel调用invokeMethod,查看invokeMethod源码:
// platform_channel.dart
const MethodChannel(this.name, [this.codec = const StandardMethodCodec()]);
Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {
assert(method != null);
final dynamic result = await BinaryMessages.send(
name,
codec.encodeMethodCall(MethodCall(method, arguments)),
);
if (result == null)
throw MissingPluginException('No implementation found for method $method on channel $name');
return codec.decodeEnvelope(result);
}
nvokeMethod 方法将方法名和参数转化为二进制数据,并通过 BinaryMessages send 方法发送出去
// platform_messages.dart
static Future<ByteData> send(String channel, ByteData message) {
final _MessageHandler handler = _mockHandlers[channel];
if (handler != null)
return handler(message);
return _sendPlatformMessage(channel, message);
}
static Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
final Completer<ByteData> completer = Completer<ByteData>();
ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'during a platform message response callback',
));
}
});
return completer.future;
}
// window.dart
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) {
final String error =
_sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
if (error != null)
throw new Exception(error);
}
String _sendPlatformMessage(String name,
PlatformMessageResponseCallback callback,
ByteData data) native 'Window_sendPlatformMessage';
_sendPlatformMessage 将调用 native 的方法 Window_sendPlatformMessage。
注册流程图:
292976-4d087d00c5fac7fe.png.png
dart 侧的代码跟踪结束了,接下来又到了 native
void Window::RegisterNatives(tonic::DartLibraryNatives* natives) {
natives->Register({
{"Window_defaultRouteName", DefaultRouteName, 1, true},
{"Window_scheduleFrame", ScheduleFrame, 1, true},
{"Window_sendPlatformMessage", _SendPlatformMessage, 4, true},
{"Window_respondToPlatformMessage", _RespondToPlatformMessage, 3, true},
{"Window_render", Render, 2, true},
{"Window_updateSemantics", UpdateSemantics, 2, true},
{"Window_setIsolateDebugName", SetIsolateDebugName, 2, true},
});
}
注册 native 方法,供 dart 端调用,其中 Window_sendPlatformMessage 被 dart 调用,对应的 _SendPlatformMessage 方法。而 _SendPlatformMessage 又调用了SendPlatformMessage
// WindowClient
Dart_Handle SendPlatformMessage(Dart_Handle window,
const std::string& name,
Dart_Handle callback,
const tonic::DartByteData& data) {
UIDartState* dart_state = UIDartState::Current();
if (!dart_state->window()) {
// Must release the TypedData buffer before allocating other Dart objects.
data.Release();
return ToDart("Platform messages can only be sent from the main isolate");
}
fml::RefPtr<PlatformMessageResponse> response;
if (!Dart_IsNull(callback)) {
response = fml::MakeRefCounted<PlatformMessageResponseDart>(
tonic::DartPersistentValue(dart_state, callback),
dart_state->GetTaskRunners().GetUITaskRunner());
}
if (Dart_IsNull(data.dart_handle())) {
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(name, response));
} else {
const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
dart_state->window()->client()->HandlePlatformMessage(
fml::MakeRefCounted<PlatformMessage>(
name, std::vector<uint8_t>(buffer, buffer + data.length_in_bytes()),
response));
}
return Dart_Null();
}
// Engine.cc
void Engine::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
if (message->channel() == kAssetChannel) {
HandleAssetPlatformMessage(std::move(message));
} else {
delegate_.OnEngineHandlePlatformMessage(std::move(message));
}
}
// |shell::Engine::Delegate|
void Shell::OnEngineHandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
task_runners_.GetPlatformTaskRunner()->PostTask(
[view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
if (view) {
view->HandlePlatformMessage(std::move(message));
}
});
}
// |shell::PlatformView|
void PlatformViewIOS::HandlePlatformMessage(fml::RefPtr<blink::PlatformMessage> message) {
platform_message_router_.HandlePlatformMessage(std::move(message));
}
void PlatformMessageRouter::HandlePlatformMessage(
fml::RefPtr<blink::PlatformMessage> message) const {
fml::RefPtr<blink::PlatformMessageResponse> completer = message->response();
auto it = message_handlers_.find(message->channel());
if (it != message_handlers_.end()) {
FlutterBinaryMessageHandler handler = it->second;
NSData* data = nil;
if (message->hasData()) {
data = GetNSDataFromVector(message->data());
}
handler(data, ^(NSData* reply) {
if (completer) {
if (reply) {
completer->Complete(GetMappingFromNSData(reply));
} else {
completer->CompleteEmpty();
}
}
});
} else {
if (completer) {
completer->CompleteEmpty();
}
}
}
注册 native 方法,供 dart 端调用,其中 Window_sendPlatformMessage 被 dart 调用,对应的 _SendPlatformMessage 方法。而 _SendPlatformMessage 又调用了SendPlatformMessage
最终PlatformView把消息转发给PlatformMessageRouter,然后从message_handlers_中取出channelName对应的 handle 并执行
handle 完成后,将结果回调给 Flutter
调用流程图:
292976-94777a411f6313bd.png.jpg
插件功能测试
① 在test_plugin_pigeon.dart文件里添加测试代码:
import 'api_generated.dart';
import 'test_plugin_pigeon_platform_interface.dart';
class TestPluginPigeon {
final HostEverything _hostEverything = HostEverything();
Future<String?> getPlatformVersion() {
return TestPluginPigeonPlatform.instance.getPlatformVersion();
}
/// Flutter 调用原生方法
Future<Everything> getEverything() async {
return await _hostEverything.giveMeEverything();
}
/// Flutter 调用原生方法
Future<Everything> echoEveryThing(Everything everything) async {
return await _hostEverything.echo(everything);
}
}
② 在example目录里的lib目录,里面有一个main.dart文件,调用测试代码
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:test_plugin_pigeon/api_generated.dart';
import 'package:test_plugin_pigeon/test_plugin_pigeon.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
String _aString = 'Unknown';
final _testPluginPigeonPlugin = TestPluginPigeon();
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
String aString;
// Platform messages may fail, so we use a try/catch PlatformException.
// We also handle the message potentially returning null.
try {
// platformVersion =
// await _testPluginPigeonPlugin.getPlatformVersion() ?? 'Unknown platform version';
Everything everything = await _testPluginPigeonPlugin.getEverything();
aString = everything.aString ?? "";
} on PlatformException {
platformVersion = 'Failed to get platform version.';
aString = "Failed to get aString.";
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_platformVersion = "Unknown";
_aString = aString;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text('Running on: $_platformVersion\n'),
),
Center(
child: Text('asString: $_aString\n'),
),
]) ,
),
);
}
}
测试结果如图:
wecom-temp-8fc64c83c5aa1aa6c0fe80cde4c66ae8.png插件发布
- 上传插件前,需要完善一些资料:
- README.md介绍包的文件
- CHANGELOG.md记录每个版本中的更改
- LICENSE包含软件包许可条款的文件
- pubspec.yaml的资料
- 所有公共API的API文档
pubspec.yaml,对Flutter插件来说,pubspec.yaml里除了插件的依赖,还包含一些元信息,根据需要,把这些补上
name: test_plugin_pigeon# 要发布的项目名称
description: A new Flutter project. # 项目描述
version: 0.0.1 # 发布的版本
homepage: http://www.google.com/ # 项目主页
publish_to: https://xxxxxx.com/ # 发布地址
email: "xxxxx.com" # 开发者邮箱
author: Wangjs # 开发者
repository: "https:xxxxxxxxx" # 一般写当前插件源代码的Github地址
- 上传前的需要清理插件,避免插件过大无法上传:
flutter clean
- cd到插件目录下,执行发布操作
flutter pub publish
可以使用以下命令来测试发布:flutter pub publish --dry-run --server="xxxx"
插件使用
插件使用方式:
- pub
- git
- 私有pub库
pub依赖
这种是最常见的方式,直接在工程的pubspec.yaml中写上你需要的插件名和版本,之后执行Pub get就行了。
dependencies:
flutter:
sdk: flutter
xxxxxx: ^0.0.1 # 添加库
git依赖
如果我们不想发布到pub,但又想团队共享插件,那么我们可以把库上传到git仓库里面,然后在pubspec.yaml中配置,之后执行Pub get就行了。
dependencies:
flutter:
sdk: flutter
nakiri:
git:
url: https://github.com/xxx/xxxx.git
ref: tag/branch
url:git地址
ref:表示git引用,可以是commit hash,tag或者分支
私有pub仓库依赖
一般而言,pub管理插件比git管理方便,所以一般大公司都会搭建自己的私有pub仓库,依赖私有pub仓库也很简单,只需要在pubspec.yaml中配置完成后,之后执行Pub get就行了。
dependencies:
flutter:
sdk: flutter
xxxx:
hosted: 'https:xxxxxxxxx'
version: '^0.0.1'
网友评论