美文网首页
Flutter-自定义插件

Flutter-自定义插件

作者: 恩说吧 | 来源:发表于2023-04-11 11:40 被阅读0次

    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'
    

    相关文章

      网友评论

          本文标题:Flutter-自定义插件

          本文链接:https://www.haomeiwen.com/subject/xwifddtx.html