Flutter 用 pigeon 写一个原生插件

前言

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

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

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

  • 依然使用的 FlutterView/FlutterRenderer 去获取的 getBitmap,结果当然还是失败。
  • 报错 Software rendering doesn't support hardware bitmaps。 Google 下要用 PixelCopy 。android - java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps - Stack Overflow
  • 项目用了 flutter_boost ,导致插件获取 Controller 失败。

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

创建一个插件

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 

**@interface** FfNativeScreenshotPlugin : NSObject

**@end**

FfNativeScreenshotPlugin.m

#import "FfNativeScreenshotPlugin.h"

**@implementation** FfNativeScreenshotPlugin

+ (**void**)registerWithRegistrar:(NSObject*)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 

#import "ScreenshotApi.h""


**@interface** FfNativeScreenshotPlugin : NSObject

**@end**

FfNativeScreenshotPlugin.m

#import "FfNativeScreenshotPlugin.h"


**@implementation** FfNativeScreenshotPlugin

+ (**void**)registerWithRegistrar:(NSObject*)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*)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 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 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 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 写一个原生插件)