Flutter 与原生进行交互

标签: 编程学习 Flutter学习

创建插件

iOS默认使用的是OC,Android默认使用的是kotlin,如果想设置语言需要加上参数,我这里就使用默认的语言了。

flutter create --org com.duicode --template=plugin dc_plugin
flutter create --org com.duicode --template=plugin -i swift -a kotlin dc_plugin

一般情况下交互涉及到的也就是方法互相调用及参数的传递,Flutter 使用的是平台通道 MethodChannel 进行交互的

平台通道使用标准消息编/解码器对消息进行编解码,它可以高效的对消息进行二进制序列化与反序列化,下面来看一下数据类型的映射关系

Dart Android iOS
null null nil (NSNull when nested)
bool java.lang.Boolean NSNumber numberWithBool:
int java.lang.Integer NSNumber numberWithInt:
int, 如果不足32位 java.lang.Long NSNumber numberWithLong:
int, 如果不足64位 java.math.BigInteger FlutterStandardBigInteger
double java.lang.Double NSNumber numberWithDouble:
String java.lang.String NSString
Uint8List byte[] FlutterStandardTypedData typedDataWithBytes:
Int32List int[] FlutterStandardTypedData typedDataWithInt32:
Int64List long[] FlutterStandardTypedData typedDataWithInt64:
Float64List double[] FlutterStandardTypedData typedDataWithFloat64:
List java.util.ArrayList NSArray
Map java.util.HashMap NSDictionary

我这里拿我们用到的云片图片验证的插件事例来做一个演示。

插件文件的编写

插件创建的时候都会创建一个 channel 对象

static const MethodChannel _channel = const MethodChannel('dc_plugin');

///监听,这里处理的其实是原生调用 Flutter
Map<String, Function> _closures = {};

/// 构造
DcPlugin() {
    /// 原生调用 flutter 时候使用
    _channel.setMethodCallHandler((call) async {
      var method = call.method;
      var arguments = call.arguments;
      print("method:$method; arguments:$arguments");
      _handleCallBack(method, arguments);
    });
}

///回调部分
_handleCallBack(String methodName, var arguments) {
    print("---_listeners:${_closures.length}");
    var function = _closures[methodName];
    // 调用函数
    Function.apply(function, [arguments]);
}

_listen(String methodName, CallBackClosure closure) async {
    _closures[methodName] = closure;
}

/// 成功回调(这里其实就是使用的原生调用 Flutter)
successListener(CallBackClosure closure) async => await _listen('success', closure);

/// 使用  _channel.invokeMethod  调用原生方法 init
static void init(String appId) {
    _channel.invokeMethod('init', appId);
}

交互的写法很简单,上面已经实现了原生与 Flutter 之间的互相调用,原生回调的时候上面使用的是监听的写法,为了使用简单还可以这样写

static void verify({
    @required OnSuccess onSuccess,
    @required OnFail onFail,
    VoidCallback onCancel,
    VoidCallback onLoaded,
    ValueChanged<String> onError,
    String lang = 'en',
    bool isShowLoading = false,
    double alpha = 0.7
}) {
    Map<String, dynamic> arguments = {
      'lang': langValueMap[lang],
      'isShowLoading': isShowLoading,
      'alpha': alpha
    };

    setMethodCallHandler(
        onSuccess: onSuccess, 
        onFail: onFail,
        onLoaded: onLoaded,
        onError: onError,
        onCancel: onCancel
    );

    _channel.invokeMethod('verify', arguments);
}

与iOS的交互

注册插件->保存通道->通道调用invokeMethod

@interface DcPlugin()
//参数
@property (nonatomic, strong)NSDictionary *arguments;
//保存通道对象
@property (nonatomic, strong)FlutterMethodChannel *channel;
- (id)initWithChannel:(FlutterMethodChannel *)channel;
@end

@implementation DcPlugin
// 注册插件
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"dc_plugin" binaryMessenger:[registrar messenger]];
  FlutterCaptchaPlugin* instance = [[DcPlugin alloc] initWithChannel:channel];
  [registrar addMethodCallDelegate:instance channel:channel];
}

/// 构造
- (id)initWithChannel:(FlutterMethodChannel *)channel {
    if (self = [super init]) {
        self.channel = channel;
    }
    return self;
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  // 根据方法名称字符来判断调用的方法
  if ([@"init" isEqualToString:call.method]) {
      [self startWithCall:call result:result];
  } else if ([@"verify" isEqualToString:call.method]) {
      [self verifyWithCall:call result:result];
  } else {
    result(FlutterMethodNotImplemented);
  }
}

#pragma mark - 初始化云片插件
- (void)startWithCall: (FlutterMethodCall *)call result: (FlutterResult)result {
    @try {
        [YPCaptchaSDK startWithCaptchaId:call.arguments];
        result(@(YES));
    } @catch (NSException *exception) {
        result([FlutterError errorWithCode:@"-1" message:exception.name details:exception.reason]);
    }
}

// 图片验证码回调
- (void)verifyWithCall: (FlutterMethodCall *)call result: (FlutterResult)result {
    BOOL showLoading = [call.arguments[@"isShowLoading"] boolValue];
    NSNumber *alpha = call.arguments[@"alpha"];

    YPConfigModel *model = [[YPConfigModel alloc] init];
    model.alpha = [alpha floatValue];
    model.showLoadingView = showLoading;

    [YPCaptchaSDK verfiy:model
                onLoaded:^{
                    //原生调用flutter 方法
                   [self.channel invokeMethod:@"onLoaded" arguments:NULL];
               }
               onSuccess:^(NSDictionary *_Nonnull info) {
                  NSDictionary *data = info[@"data"];
                  //原生调用flutter 方法
                  [self.channel invokeMethod:@"onSuccess" arguments: [self dictionaryToJson:data]];
               }
               onFail:^(NSDictionary *_Nonnull info) {
                  if([[NSString stringWithFormat:@"%@", info[@"code"]] isEqualToString:@"10008"]) {
                    //原生调用flutter 方法
                    [self.channel invokeMethod:@"onCancel" arguments:NULL];
                  } else {
                    //原生调用flutter 方法
                    [self.channel invokeMethod:@"onFail" arguments:[self dictionaryToJson:info]];
                  }
               }];

    result(@(YES));
}

#pragma mark 字典转化字符串
- (NSString *)dictionaryToJson: (NSDictionary *)dic {
    NSError *parseError = nil;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:NSJSONWritingPrettyPrinted error:&parseError];
    return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}

@end

与Android的交互

安卓与iOS的写法就差不多了

public class DcPlugin() : FlutterPlugin, MethodCallHandler, ActivityAware {
    //保存通道
    lateinit var channel: MethodChannel
    lateinit var activity: Activity

    // 注册插件
    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        DcPlugin().apply {
                    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "dc_plugin")
                    channel.setMethodCallHandler(this) 
                    }
    }

    // 注册插件
    companion object {
        @JvmStatic
        fun registerWith(registrar: Registrar) {
            DcPlugin()
                    .apply {
                        channel = MethodChannel(registrar.messenger(), "dc_plugin")
                        channel.setMethodCallHandler(this)
                        activity = registrar.activity()
                    }
        }
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        //根据方法名来判断调用的方法
        if (call.method == "init") {
            init(call, result);
        } else if (call.method == "verify") {
            verify(call, result);
        } else {
            result.notImplemented()
        }
    }

    // 初始化云片插件
    private fun init(call: MethodCall, result: Result) {
        try {
            QPCaptcha.getInstance().init(activity, call.arguments())
            result.success(true);
        } catch (e: Exception) {
            result.error("-1", e.message, e.toString())
        }
    }

    // 验证云片
    private fun verify(call: MethodCall, result: Result) {
        val showLoading = call.argument<Boolean>("isShowLoading")!!
        val alpha = call.argument<Float>("alpha")!!

        val builder = QPCaptchaConfig.Builder(activity)
                .setAlpha(alpha) // 视图透明度
                .showLoadingView(showLoading)  // 是否显示加载

        // 云片插件回调
        builder.setCallback(object : QPCaptchaListener {
            override fun onSuccess(msg: String?) {
                // 原生调用 Flutter 方法
                channel.invokeMethod("onSuccess", msg);
                Log.d("DcPlugin:", "onSuccess$msg");
            }

            override fun onFail(msg: String?) {
                // 原生调用 Flutter 方法
                channel.invokeMethod("onFail", msg);
                Log.d("DcPlugin:", "onFail$msg");

            }

            override fun onCancel() {
                // 原生调用 Flutter 方法
                channel.invokeMethod("onCancel", null);
                Log.d("DcPlugin:", "onCancel");

            }

            override fun onLoaded() {
                // 原生调用 Flutter 方法
                channel.invokeMethod("onLoaded", null);
                Log.d("DcPlugin:", "onLoaded");

            }

            override fun onError(msg: String?) {
                // 原生调用 Flutter 方法
                channel.invokeMethod("onSuccess", msg);
                Log.d("DcPlugin:", "onError $msg");

            }

        }) // 设置回调接口

        QPCaptcha.getInstance().verify(builder.build())
        result.success(true);
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
    }

    override fun onDetachedFromActivity() {
    }

    override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
    }

    override fun onAttachedToActivity(binding: ActivityPluginBinding) {
        activity = binding.activity
    }

    override fun onDetachedFromActivityForConfigChanges() {
    }
}

注意

记得在插件的 podspec 和 build.gradle 中补充完善信息,比如依赖的插件等。

这样一个简单的插件就完成了,比起之前玩的 RN 与原生的交互简直方便太多了。