Flutter-自定义插件

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.png

iOS实现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

插件发布

  1. 上传插件前,需要完善一些资料:
  • 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地址 
  1. 上传前的需要清理插件,避免插件过大无法上传:
flutter clean
  1. 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'
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容