Flutter插件是什么?
在開發(fā)Flutter應(yīng)用過程中會(huì)涉及到平臺(tái)相關(guān)接口調(diào)用将鸵,例如數(shù)據(jù)庫操作勉盅、相機(jī)調(diào)用、定位等業(yè)務(wù)場景顶掉。Flutter自身并不支持直接在平臺(tái)上實(shí)現(xiàn)這些功能草娜,而是通過插件包接口去調(diào)用指定平臺(tái)API從而實(shí)現(xiàn)原生平臺(tái)上特定功能
創(chuàng)建Flutter插件工程
在Android Studio里點(diǎn)擊Flie - New - New Flutter Project,在左側(cè)里選中Flutter痒筒,然后點(diǎn)擊Next宰闰。- 在Project Name里輸入項(xiàng)目名,只能是小寫英文
- 在Project type里選擇Plugin
- 在Organization里寫包名簿透,.Project Name會(huì)拼在包名的最后面成為包名的一部分
使用命令創(chuàng)建插件
flutter create --org com.example --template=plugin plugin_name -I swift
其中 com.example 是插件包名的一部分移袍,plugin_name 是插件的名稱。插件的完整包名為 com.example.plugin_name
插件目錄結(jié)構(gòu)
wecom-temp-7353b068adcc6be10902b1ae2a3c2cf1.png
我們需要關(guān)注的主要有以下4個(gè):
- android目錄是用來開發(fā)Android端的插件功能
- ios目錄是用來開發(fā)iOS端的插件功能
- lib是實(shí)現(xiàn)Flutter插件接口的代碼
- example目錄是測試項(xiàng)目老充,用來測試開發(fā)出來的插件的
插件功能開發(fā)
App端實(shí)現(xiàn)
在register方法里葡盗,我們注冊(cè)了一個(gè)通道(已經(jīng)默認(rèn)注冊(cè)了),通道名默認(rèn)就是項(xiàng)目名啡浊,該名字在通信里必須是唯一的觅够,可以修改胶背,一旦修改,需要把dart和android里的名字也一并修改喘先。
在handle方法里钳吟,實(shí)現(xiàn)Flutter調(diào)用原生的API,其中call.method就是方法名窘拯,call.arguments就是Flutter傳遞過來的參數(shù)红且。使用result(返回值)可以把結(jié)果返回給Flutter。
當(dāng)找不到方法名時(shí)涤姊,可以返回FlutterMethodNotImplemented給Flutter表示該方法還沒實(shí)現(xiàn)直焙,以此來做版本兼容
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" { // 獲取網(wǎng)絡(luò)類型的實(shí)現(xiàn)
result("WIFI")
} else if call.method == "bonusPoints" { // 使用參數(shù)的實(shí)現(xiàn)
let array = call.arguments as! Array<Int>
result(array[0] + array[1])
} else if call.method == "getPlatformVersion" { // 默認(rèn)的實(shí)現(xiàn)
result("iOS " + UIDevice.current.systemVersion)
} else {
// 找不到方法
result(FlutterMethodNotImplemented)
}
}
}
Flutter端實(shí)現(xiàn)
增加方法用來調(diào)用iOS端的方法,方法名不需要和iOS端的保持一致砂轻,主要是通道里調(diào)用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.');
}
}
插件自動(dòng)生成的抽象類
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;
}
/// 實(shí)現(xiàn)iOS端新增的方法
@override
Future<String> getNetWorkType() async {
final String state = await methodChannel.invokeMethod('getNetWorkType');
return state;
}
/// 實(shí)現(xiàn)iOS端新增的方法
@override
Future<int> add() async {
final int result = await methodChannel.invokeMethod('bonusPoints', [5, 8]); /// 接收一個(gè)數(shù)組或者字典作為參數(shù)傳遞給原生端
return result;
}
}
定義methodChannel奔誓,最終由methodChannel通過invokeMethod調(diào)用原生方法,invokeMethod內(nèi)的方法名需要與原生定義的方法名一致
class TestPlugin {
Future<String?> getPlatformVersion() {
return TestPluginPlatform.instance.getPlatformVersion();
}
/// 實(shí)現(xiàn)iOS端新增的方法
Future<String> getNetWorkType() async {
return TestPluginPlatform.instance.getNetWorkType();
}
/// 實(shí)現(xiàn)iOS端新增的方法
Future<int> add() async {
return TestPluginPlatform.instance.add();
}
}
需要注意的是搔涝,F(xiàn)lutter和原生通信都是異步的厨喂,所以都需要使用await和async
三方庫使用
寫插件不可避免的會(huì)用到第三方庫,在使用第三方庫的時(shí)候庄呈,會(huì)遇到3種情況:
- 僅原生端使用第三方庫
當(dāng)僅原生端需要依賴某些第三方庫時(shí)蜕煌,可以在podspec文件里加上s.dependency '第三方庫名' - 僅Flutter端使用第三方庫
當(dāng)僅Flutter端需要依賴某些第三方庫時(shí),可以在pubspec.yaml文件里的dependencies部分
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.0.16
- 都使用同一個(gè)第三方庫
假設(shè)Flutter里需要用到url_launcher诬留,然后原生里也需要用到斜纪,那我們就得在Flutter的pubspec.yaml文件里的dependencies部分添加依賴包,同時(shí)也要在iOS端的podspec文件里加上s.dependency 'url_launcher'
私有庫使用
寫插件不可避免的會(huì)用到私有庫文兑,在使用第三方庫的時(shí)候:
原生端需要在podspec文件里加上s.dependency '私有庫名'
Flutter項(xiàng)目需要在podFile里添加私有的source和path
tips: 可將私有庫打包成.a盒刚,直接集成在插件里, 避免了私有庫引用帶來的麻煩
通信的數(shù)據(jù)類型
原生與Flutter互相通信時(shí)使用的數(shù)據(jù)類型是有限制的,以下是可用的數(shù)據(jù)類型:
Dart | kotlin | Java | Objective-C | Swift |
---|---|---|---|---|
null | null | null | NSNull | NSNull |
bool | Boolean | java.lang.Boolean | NSNumber numberWithBool: | NSNumber(value: Bool)或者Bool |
int 32位平臺(tái) | 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的基礎(chǔ)類型可以用Objective-C的對(duì)象類型绿贞,集合類型可以兼容Objective-C的集合類型(不過這些都是Swift本身的特性)
- 在使用Swift時(shí)因块,最好還是使用它本身的類型,如果使用Objective-C的類型籍铁,就無法判斷詳細(xì)類型涡上,比如Int和Double,在使用Objective-C類型的時(shí)候拒名,都是NSNumber
思考
能不能統(tǒng)一生成兩端通用的代碼吩愧?開發(fā)是否可以不需要關(guān)心Method Channe的具體實(shí)現(xiàn),只需要實(shí)現(xiàn)對(duì)應(yīng)接口即可增显?
pigeon工具插件
一個(gè)代碼生成工具雁佳,讓Flutter和宿主平臺(tái)更安全、更簡單、更快地通信甘穿。通過Dart入口腮恩,生成兩端通用的模板代碼梢杭,原生則只需重寫模板內(nèi)的接口温兼,無需管理Method Channel的實(shí)現(xiàn)。參數(shù)可以通過模板來同步生成武契。
目前的pigeon只支持生成OC和Java代碼
1. 添加依賴
dev_dependencies:
pigeon: ^3.1.0
2. 添加插件交互類和方法
- @HostApi() 標(biāo)記的募判,是用于 Flutter 調(diào)用原生的方法。
- @FlutterApi() 標(biāo)記的咒唆,是用于原生調(diào)用 Flutter 的方法届垫。
- @async 如果原生的方法,是異步回調(diào)那種全释,你就可以使用這個(gè)標(biāo)記
- 只支持 dart 的基礎(chǔ)類型
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調(diào)用原生的方法
@HostApi()
abstract class HostEverything {
Everything giveMeEverything();
Everything echo(Everything everything);
}
/// 原生調(diào)用Flutter的方法
@FlutterApi()
abstract class FlutterEverything {
Everything giveMeEverythingFlutter();
Everything echoFlutter(Everything everything);
}
3. 生成各端插件代碼
在項(xiàng)目目錄下装处,執(zhí)行以下命令:
$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"
或者執(zhí)行 ./run_pigeon.sh 腳本內(nèi)容如下:
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 對(duì)應(yīng)的dart文件
- objc_header_out/objc_source_out 對(duì)應(yīng)的iOS實(shí)現(xiàn)文件
- java_out 對(duì)應(yīng)的android實(shí)現(xiàn)文件
執(zhí)行腳本或者命令生成以下文件:
iOS實(shí)現(xiàn)Flutter調(diào)用原生的方法
① 刪掉項(xiàng)目中之前的獲取版本的原生的和Flutter側(cè)的相關(guān)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中自動(dòng)生成了一個(gè)方法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的注冊(cè)方法里,調(diào)用這個(gè)setup方法進(jìn)行初始化和設(shè)置
public static func register(with registrar: FlutterPluginRegistrar) {
let messenger: FlutterBinaryMessenger = registrar.messenger()
let api: HostEverything & NSObjectProtocol = SwiftTestPluginPigeonPlugin.init()
FLTHostEverythingSetup(messenger, api)
}
④ iOS/Classes目錄下浸船,創(chuàng)建test_plugin_pigeon.h文件妄迁,導(dǎo)入頭文件,此文件在iOS自動(dòng)生成的<test_plugin_pigeon/test_plugin_pigeon-Swift.h>文件中會(huì)自動(dòng)引用李命。
#ifndef test_plugin_pigeon_h
#define test_plugin_pigeon_h
#import "AllTypesPigeon.h"
#endif /* test_plugin_pigeon_h */
⑤ SwiftFlutterPigeonPlugin遵循HostEverything協(xié)議登淘,實(shí)現(xiàn)Flutter調(diào)原生的方法
import Flutter
import UIKit
/// 遵循HostEverything協(xié)議,實(shí)現(xiàn)Flutter調(diào)原生的方法
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怎么調(diào)原生的封字,那具體是怎么通信的呢黔州?
原生與Flutter通信
Flutter 提供了 Platform Channel 機(jī)制,讓消息能夠在 native 與 Flutter 之間進(jìn)行傳遞
Flutter定義了三種不同類型的Channel通信類阔籽,
- BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息流妻。
- MethodChannel:用于傳遞方法調(diào)用(method invocation)。
- EventChannel: 用于數(shù)據(jù)流(event streams)的通信笆制。
Channel 使用 codec 消息編解碼器合冀,支持從基礎(chǔ)數(shù)據(jù)到二進(jìn)制格式數(shù)據(jù)的轉(zhuǎn)換、解析。
三種Channel之間互相獨(dú)立隧枫,每種Channel均有三個(gè)重要成員變量:
- name: String類型横腿,代表Channel的名字,也是其唯一標(biāo)識(shí)符棕叫。
- messager:BinaryMessenger類型,代表消息信使奕删,是消息的發(fā)送與接收的工具俺泣。
- codec: MessageCodec類型或MethodCodec類型,代表消息的編解碼器。
一個(gè)Flutter應(yīng)用中可能存在多個(gè)Channel伏钠,每個(gè)Channel在創(chuàng)建時(shí)必須指定一個(gè)獨(dú)一無二的name横漏,Channel之間使用name來區(qū)分彼此。當(dāng)有消息從Flutter端發(fā)送到Platform端時(shí)熟掂,會(huì)根據(jù)其傳遞過來的channel name找到該Channel對(duì)應(yīng)的Handler(消息處理器)缎浇。
Channel 注冊(cè)
以FlutterMethodChannel為例看一個(gè)注冊(cè)流程
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));
}
......
}];
初始化一個(gè)橋接,傳入橋接名和處理消息發(fā)送接收的類赴肚,橋接名為"samples.flutter.io/battery"
通過setMethodCallHandler 方法素跺,在 native 項(xiàng)目中注冊(cè)該橋接的回調(diào) handler,其中參數(shù) result 表示向 Flutter 回傳的結(jié)果
- (void)setMethodCallHandler:(FlutterMethodCallHandler)handler {
if (!handler) {
[_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil];
return;
}
#此處又包裝了一個(gè)回調(diào) messageHandler誉券,用于解碼二進(jìn)制消息 message指厌,并向 Flutter 側(cè)回傳執(zhí)行結(jié)果 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_ 是個(gè)哈希表,key 是橋接名踊跟,value 放 handle踩验。原生注冊(cè)橋接方法,其實(shí)就是維護(hù)一個(gè) map 對(duì)象
Channel調(diào)用方法
dart 側(cè)調(diào)用獲取電量的橋接方法
static const MethodChannel methodChannel =
MethodChannel('samples.flutter.io/battery');
final int result = await methodChannel.invokeMethod('getBatteryLevel');
channel調(diào)用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 方法將方法名和參數(shù)轉(zhuǎn)化為二進(jìn)制數(shù)據(jù)箕憾,并通過 BinaryMessages send 方法發(fā)送出去
// 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 將調(diào)用 native 的方法 Window_sendPlatformMessage。
注冊(cè)流程圖:
dart 側(cè)的代碼跟蹤結(jié)束了决帖,接下來又到了 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},
});
}
注冊(cè) native 方法厕九,供 dart 端調(diào)用,其中 Window_sendPlatformMessage 被 dart 調(diào)用地回,對(duì)應(yīng)的 _SendPlatformMessage 方法扁远。而 _SendPlatformMessage 又調(diào)用了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();
}
}
}
注冊(cè) native 方法,供 dart 端調(diào)用刻像,其中 Window_sendPlatformMessage 被 dart 調(diào)用畅买,對(duì)應(yīng)的 _SendPlatformMessage 方法。而 _SendPlatformMessage 又調(diào)用了SendPlatformMessage
最終PlatformView把消息轉(zhuǎn)發(fā)給PlatformMessageRouter细睡,然后從message_handlers_中取出channelName對(duì)應(yīng)的 handle 并執(zhí)行
handle 完成后谷羞,將結(jié)果回調(diào)給 Flutter
調(diào)用流程圖:
插件功能測試
① 在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 調(diào)用原生方法
Future<Everything> getEverything() async {
return await _hostEverything.giveMeEverything();
}
/// Flutter 調(diào)用原生方法
Future<Everything> echoEveryThing(Everything everything) async {
return await _hostEverything.echo(everything);
}
}
② 在example目錄里的lib目錄,里面有一個(gè)main.dart文件溜徙,調(diào)用測試代碼
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'),
),
]) ,
),
);
}
}
測試結(jié)果如圖:
插件發(fā)布
- 上傳插件前湃缎,需要完善一些資料:
- README.md介紹包的文件
- CHANGELOG.md記錄每個(gè)版本中的更改
- LICENSE包含軟件包許可條款的文件
- pubspec.yaml的資料
- 所有公共API的API文檔
pubspec.yaml,對(duì)Flutter插件來說蠢壹,pubspec.yaml里除了插件的依賴嗓违,還包含一些元信息,根據(jù)需要图贸,把這些補(bǔ)上
name: test_plugin_pigeon# 要發(fā)布的項(xiàng)目名稱
description: A new Flutter project. # 項(xiàng)目描述
version: 0.0.1 # 發(fā)布的版本
homepage: http://www.google.com/ # 項(xiàng)目主頁
publish_to: https://xxxxxx.com/ # 發(fā)布地址
email: "xxxxx.com" # 開發(fā)者郵箱
author: Wangjs # 開發(fā)者
repository: "https:xxxxxxxxx" # 一般寫當(dāng)前插件源代碼的Github地址
- 上傳前的需要清理插件蹂季,避免插件過大無法上傳:
flutter clean
- cd到插件目錄下冕广,執(zhí)行發(fā)布操作
flutter pub publish
可以使用以下命令來測試發(fā)布:flutter pub publish --dry-run --server="xxxx"
插件使用
插件使用方式:
- pub
- git
- 私有pub庫
pub依賴
這種是最常見的方式,直接在工程的pubspec.yaml中寫上你需要的插件名和版本偿洁,之后執(zhí)行Pub get就行了撒汉。
dependencies:
flutter:
sdk: flutter
xxxxxx: ^0.0.1 # 添加庫
git依賴
如果我們不想發(fā)布到pub,但又想團(tuán)隊(duì)共享插件涕滋,那么我們可以把庫上傳到git倉庫里面睬辐,然后在pubspec.yaml中配置,之后執(zhí)行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管理方便,所以一般大公司都會(huì)搭建自己的私有pub倉庫坡慌,依賴私有pub倉庫也很簡單黔酥,只需要在pubspec.yaml中配置完成后,之后執(zhí)行Pub get就行了洪橘。
dependencies:
flutter:
sdk: flutter
xxxx:
hosted: 'https:xxxxxxxxx'
version: '^0.0.1'