美文网首页
Flutter 用 pigeon 写一个原生插件

Flutter 用 pigeon 写一个原生插件

作者: 法的空间 | 来源:发表于2022-07-25 16:58 被阅读0次

    前言

    在有 pigeon | Dart Package (flutter-io.cn) 之前,想写一个原生交互,是相当麻烦的一件事情。项目里面用 pigeon 来写了几个原生插件,用下来感觉还是很方便的,但是在开始使用的时候,我发现这方面的文章几乎没有,单端选手(几乎都是经常安卓端)很多,而且常常只是介绍 Flutter 端到原生端的通信,而原生端到 Flutter 端的几乎看不到介绍。

    做项目的时候遇到用 RepaintBoundary 无法给 Webview 截屏的问题,查了下,是个已知的问题。
    RepaintBoundary Cannot take Screenshot of Platform Views

    然后上 Dart packages (flutter-io.cn) 试用了几个,不能说不能用,而是完全不能用。

    最后还是决定自己写好了。

    创建一个插件

    flutter create -i objc -a java --org com.fluttercandies.plugins --template plugin --platforms ios,android ff_native_screenshot

    使用 pigeon

    添加引用

    dev_dependencies:
      pigeon: ^3.1.0
    

    增加生成类

    1. @HostApi() 标记的,是用于 Flutter 调用原生的方法。
    2. @FlutterApi() 标记的,是用于原生调用 Flutter 的方法。
    3. @async 如果原生的方法,是异步回调那种,你就可以使用这个标记
    4. 只支持 dart 的基础类型
    /// Flutter call Native
    @HostApi()
    abstract class ScreenshotHostApi {
      @async
      Uint8List? takeScreenshot();
    
      void startListeningScreenshot();
      void stopListeningScreenshot();
    }
    
    /// Native call Flutter
    @FlutterApi()
    abstract class ScreenshotFlutterApi {
      void onTakeScreenshot(Uint8List? data);
    }
    

    命令脚本

    我习惯放在脚本里面执行,免得每次都要手打。

    执行 ./pigeon.sh 脚本内容如下:

    flutter pub run pigeon \
      --input pigeons/ff_native_screenshot.dart \
      --dart_out lib/src/ff_native_screenshot.dart \
      --objc_header_out ios/Classes/ScreenshotApi.h \
      --objc_source_out ios/Classes/ScreenshotApi.m \
      --objc_prefix FLT \
      --java_out android/src/main/java/com/fluttercandies/plugins/ff_native_screenshot/ScreenshotApi.java \
      --java_package "com.fluttercandies.plugins.ff_native_screenshot"
    

    iOS端实现

    修改之前:

    FfNativeScreenshotPlugin.h

    #import <Flutter/Flutter.h>
    
    **@interface** FfNativeScreenshotPlugin : NSObject<FlutterPlugin>
    
    **@end**
    

    FfNativeScreenshotPlugin.m

    #import "FfNativeScreenshotPlugin.h"
    
    **@implementation** FfNativeScreenshotPlugin
    
    + (**void**)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    
      FlutterMethodChannel* channel = [FlutterMethodChannel
    
          methodChannelWithName:@"ff_native_screenshot"
    
                binaryMessenger:[registrar messenger]];
    
      FfNativeScreenshotPlugin* instance = [[FfNativeScreenshotPlugin alloc] init];
    
      [registrar addMethodCallDelegate:instance channel:channel];
    
    }
    
    
    - (**void**)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    
      **if** ([@"getPlatformVersion" isEqualToString:call.method]) {
    
        result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
    
      } **else** {
    
        result(FlutterMethodNotImplemented);
    
      }
    
    }
    
    **@end**
    

    ScreenshotHostApi

    FfNativeScreenshotPlugin.h 增加继承 FLTScreenshotHostApi

    #import <Flutter/Flutter.h>
    
    #import "ScreenshotApi.h""
    
    
    **@interface** FfNativeScreenshotPlugin : NSObject<FlutterPlugin,FLTScreenshotHostApi>
    
    **@end**
    

    FfNativeScreenshotPlugin.m

    #import "FfNativeScreenshotPlugin.h"
    
    
    **@implementation** FfNativeScreenshotPlugin
    
    + (**void**)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    
        FfNativeScreenshotPlugin* instance = [[FfNativeScreenshotPlugin alloc] init];
    
        FLTScreenshotHostApiSetup(registrar.messenger,instance);
    }
    
    
    
    - (**void**)startListeningScreenshotWithError:(FlutterError * **_Nullable** **__autoreleasing** * **_Nonnull**)error {
    
    }
    
    
    
    - (**void**)stopListeningScreenshotWithError:(FlutterError * **_Nullable** **__autoreleasing** * **_Nonnull**)error {
    
    }
    
    
    
    - (**void**)takeScreenshotWithCompletion:(**nonnull** **void** (^)(FlutterStandardTypedData * **_Nullable**, FlutterError * **_Nullable**))completion {
    
    }
    
    
    **@end**
    

    ScreenshotFlutterApi

    我们需要注册并且保存 ScreenshotFlutterApi,用于原生给 Flutter 发送消息

    FfNativeScreenshotPlugin.m

    #import "FfNativeScreenshotPlugin.h"
    
    **static** FLTScreenshotFlutterApi *screenshotFlutterApi;
    
    **@implementation** FfNativeScreenshotPlugin
    
    + (**void**)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
        screenshotFlutterApi = [[FLTScreenshotFlutterApi alloc] initWithBinaryMessenger: registrar.messenger ];
    
    }
    
    **@end**
    

    安卓端实现

    修改之前:

    
    public class FfNativeScreenshotPlugin implements FlutterPlugin, MethodCallHandler {
      /// The MethodChannel that will the communication between Flutter and native Android
      ///
      /// This local reference serves to register the plugin with the Flutter Engine and unregister it
      /// when the Flutter Engine is detached from the Activity
      private MethodChannel channel;
    
      @Override
      public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "ff_native_screenshot");
        channel.setMethodCallHandler(this);
      }
    
      @Override
      public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals("getPlatformVersion")) {
          result.success("Android " + android.os.Build.VERSION.RELEASE);
        } else {
          result.notImplemented();
        }
      }
    
      @Override
      public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
      }
    }
    

    ScreenshotHostApi

    我们需要将 MethodCallHandler 替换成 ScreenshotHostApi, 移除无用代码

    
    public class FfNativeScreenshotPlugin implements FlutterPlugin, ScreenshotApi.ScreenshotHostApi {
      @Override
      public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        ScreenshotApi.ScreenshotHostApi.setup(flutterPluginBinding.getBinaryMessenger(), this);
      }
    
      @Override
      public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        ScreenshotApi.ScreenshotHostApi.setup(binding.getBinaryMessenger(), null);
      }
    
      @Override
      public void takeScreenshot(ScreenshotApi.Result<byte[]> result) {
    
      }
    
      @Override
      public void startListeningScreenshot() {
    
      }
    
      @Override
      public void stopListeningScreenshot() {
    
      }
    }
    

    ScreenshotFlutterApi

    我们需要注册并且保存 ScreenshotFlutterApi,用于原生给 Flutter 发送消息

    private ScreenshotApi.ScreenshotFlutterApi screenshotFlutterApi;
    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
      screenshotFlutterApi = new ScreenshotApi.ScreenshotFlutterApi(flutterPluginBinding.getBinaryMessenger());
    }
    
    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
      screenshotFlutterApi = null;
    }
    

    剩下的工作就是对这些方法进行实现,感谢 googlepub.dev上的各个插件大佬, 让我能学习(白嫖)到原生知识,也感谢群里大佬们对原生部分代码的检查和建议。

    封装和使用

    封装

    我们可以做一些封装,将 lib/ff_native_screenshot.dart 修改为如下:

    library ff_native_screenshot;
    
    import 'dart:typed_data';
    import 'src/ff_native_screenshot.dart';
    
    export 'src/ff_native_screenshot.dart';
    
    /// The util of NativeScreenshot
    class FfNativeScreenshot {
      factory FfNativeScreenshot() => _ffNativeScreenshot;
      FfNativeScreenshot._();
      static final FfNativeScreenshot _ffNativeScreenshot = FfNativeScreenshot._();
      final ScreenshotHostApi _flutterScreenshotApi = ScreenshotHostApi();
    
      /// take screenshot by native
      Future<Uint8List?> takeScreenshot() => _flutterScreenshotApi.takeScreenshot();
    
      /// ScreenshotFlutterApi setup
      void setup(ScreenshotFlutterApi api) => ScreenshotFlutterApi.setup(api);
    
      bool _listening = false;
    
      /// whether is listening Screenshot
      bool get listening => _listening;
    
      /// start listening Screenshot
      void startListeningScreenshot() {
        _listening = true;
        _flutterScreenshotApi.startListeningScreenshot();
      }
    
      /// stop listening Screenshot
      void stopListeningScreenshot() {
        _listening = false;
        _flutterScreenshotApi.stopListeningScreenshot();
      }
    }
    

    使用

    pubspec.yaml 中加入引入

    dependencies:
      ff_native_screenshot: any
    

    截图

    Uint8List? data = await FfNativeScreenshot().takeScreenshot();
    

    监听系统截图

    我们需要去实现 ScreenshotFlutterApi,在 onTakeScreenshot 方法中获取到系统截图时候返回的字节流。

    
      @override
      void initState() {
        super.initState();
        FfNativeScreenshot().setup(ScreenshotFlutterApiImplements());
        FfNativeScreenshot().startListeningScreenshot();
      }
    
      @override
      void dispose() {
        FfNativeScreenshot().stopListeningScreenshot();
        super.dispose();
      }
    
      class ScreenshotFlutterApiImplements extends ScreenshotFlutterApi {
        ScreenshotFlutterApiImplements();
        @override
        Future<void> onTakeScreenshot(Uint8List? data) async {
         // if it has something error
         // you can call takeScreenshot 
         data ??= await FfNativeScreenshot().takeScreenshot();
        }
      }
    
    

    结语

    实际上,插件只要你第一次双端实现好了,之后想加方法,改方法,都是很方便的事情。比起来需要手写 if else, 我更喜欢pigeon 的多端接口一致性,对于不太懂原生的开发,还是很友好的。

    当然,写一个 Flutter 的原生插件,你将需要掌握:

    • dart
    • java
    • object-c
    • kotlin
    • swift

    别问为什么这么多,你在 google 上面搜索答案的时候就会发现,大佬们的答案往往是各种语言都有 o(╯□╰)o。

    一次开发,5倍快乐,快来一起写 Flutter 原生插件吧!

    ff_native_screenshot | Flutter Package (flutter-io.cn) 有需求的童鞋自取。

    相关文章

      网友评论

          本文标题:Flutter 用 pigeon 写一个原生插件

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