就像 React Native 一样,在 Flutter 应用中,如果需要调用第三方库的方法或者有一些功能需要使用原生的开发来提供,使用 Flutter Plugin 是一种不错的方式,它本质上就是一个 Dart Package,但与其它的 package 不同点在于,Flutter 插件中一般都存在两个特殊的文件夹:android
与 ios
,如果需要编写Java、Kotlin或者 Object-C 以及 Swift 代码,我们就需要在这两个文件夹项目中进行,然后通过相应的方法将原生代码中开发的方法映射到 dart 中。
本文以开发一个微信插件为例,为Flutter应用提供微信分享、登录、支付等功能,项目代码可以直接在下方找到,也已经提交至Pub库:
Pub库:https://pub.dartlang.org/packages/wechat
项目地址:https://github.com/pantao/flutter-wechat
创建插件目录
要开发插件,可以使用下面的代码快速基于 plugin
模板开始:
flutter create --template=plugin wechat
上面的代码中,表示以 plugin
模板创建一个名为 wechat
的 package
,创建完成之后,整个项目的目录结构就都提供好了,并且官方还提供了一些基本开发示例。
目录结构
- android // Android 相关原生代码目录
- ios // ios 相关原生代码目录
- lib // Dart 代码目录
- example // 一个完整的调用了我们正在开发的插件的 Flutter App
- pubspec.yaml // 项目配置文件
从 example/lib/main.dart
开始
在开发我们的应用之后,先来了解一下 flutter
为我们生成的文件们,打开 example/lib/main.dart
,代码如下:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:wechat/wechat.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
@override
void initState() {
super.initState();
initPlatformState();
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
String platformVersion;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await Wechat.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// 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 = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Text('Running on: $_platformVersion
'),
),
),
);
}
}
这里需要特别注意的就是 initPlatformState()
方法中对 Wechat.platformVersion
的调用,这里面的 Wechat
就是我们的插件,platformVersion
就是插件提供的 get
方法,跟着这个文件,找到 lib/wechat.dart
文件,代码如下:
import 'dart:async';
import 'package:flutter/services.dart';
class Wechat {
static const MethodChannel _channel =
const MethodChannel('wechat');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
在该文件中,可以看到 class Wechat
定义了一个 get
方法 platformVersion
,它的函数体有点特别:
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
我们的 version
是通过 _channel.invokeMethod('getPlatformVersion')
方法的调用得到的,这个 _channel
就是我们 Dart 代码与 原生代码进行通信的桥了,也是 Flutter 原生插件的核心(当然,如果你编写的插件并不需要原生代码相关的功能,那么,_channel
就是可有可无的了,比如我们可以写一个下面这样的方法,返回 两个数字 a
与 b
的和:
class Wechat {
...
static int calculate (int a, int b) {
return a + b;
}
}
之后,修改 example/lib/main.dart
代码:
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
// 定义一个 int 型变量,用于保存计算结果
int _calculateResult;
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
String platformVersion;
try {
platformVersion = await Wechat.platformVersion;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
if (!mounted) return;
// init 的时候,计算一下 10 + 10 的结果
_calculateResult = Wechat.calculate(10, 10);
setState(() {
_platformVersion = platformVersion;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Container(
padding: EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Text('Running on: $_platformVersion
'),
// 输出该结果
Text('Calculate Result: $_calculateResult
'),
],
),
),
),
),
);
}
}
支持原生编码提供的方法
很多时候,写插件,更多的是因为我们需要让应用能够调用原生代码提供的方法,怎么做呢?
Android 系统
打开 android/src/main/java/com/example/wechat/WechatPlugin.java
文件,看如下代码:
package com.example.wechat;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
/** WechatPlugin */
public class WechatPlugin implements MethodCallHandler {
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "wechat");
channel.setMethodCallHandler(new WechatPlugin());
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
}
还记得上面提到的 getPlatformVersion
吗?还记得 _channel
那么,是不是在这里面也看到的对应的存在?没错, dart
中的 getPlatformVersion
通过 _channel.invokeMethod
发起一次请求,然后,Java
代码中的 onMethodCall
方法回被调用,该方法有两个参数:
MethodCall call
:请求本身Result result
:结果处理方法
然后通过 call.method
可以知到 _channel.invokeMethod
中的方法名,然后通过 result.success
回调返回成功结果响应。
registerWith
在上面还有一小段代码
registerWith
,可以看到里面有一个调用:final MethodChannel channel = new MethodChannel(registrar.messenger(), "wechat"); channel.setMethodCallHandler(new WechatPlugin());
这里就是在注册我们的插件,将
channel
名,这样,才不会调用alipay
插件的调用最后到了
iOS 系统
同样的,这次我们打开 ios/Classes/WechatPlugin.m
文件:
#import "WechatPlugin.h"
@implementation WechatPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"wechat"
binaryMessenger:[registrar messenger]];
WechatPlugin* instance = [[WechatPlugin 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
虽然语法有所不同,但是,可以看到,跟 android
的 Java 代码结构上几乎是一模一样的,首先 register
一个名为 wechat
的 channel
,然后去 handleMethodCall
,同样的通过 call.method
拿到方法名,通过 result
做出响应。
小试牛刀
接下来,我们将前面的 caculate
方法,移到原生代码中来提供(虽然这很没必要,但毕竟,只是为了演示嘛)。
Android
在前面打开的 android/src/main/java/com/example/wechat/WechatPlugin.java
文件中,修改 onMethodCall
方法:
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else if (call.method.equals("calculate")) {
int a = call.argument("a");
int b = call.argument("b");
int r = a + b;
result.success("" + r);
} else {
result.notImplemented();
}
}
添加了 call.method.equals("calculate")
判断,这里面具体的过程是:
- 调用
call.argument()
方法,可以取得由wechat.dart
传递过来的参数 - 计算结果
- 调用
result.success()
响应结果
然后,我们需要在 lib/wechat.dart
中修改 calculate
方法的实现,代码如下:
static Future<int> calculate (int a, int b) async {
final String result = await _channel.invokeMethod('calculate', {
'a': a,
'b': b
});
return int.parse(result);
}
由于 _channel.invokeMethod
是一个异步操作,所以,我们需要将 calculate
的返回类型修改为 Future
,同时加上 async
,此时我们就可以直接使用 await
关键字了,跟 JavaScript
中的 await
一样,让我们用同步的方式编写异步代码,在新版的 calculate
代码中,我们并没有直接计算 a+b
的结果,而是调用 _channel.invokeMethod
方法,将 a
与 b
传递给了 Java
端的 onMethodCall
方法,然后返回该方法返回的结果。
_channel.invokeMethod
该方法接受两个参数,第一个定义一个方法名,它是一个标识,简单来说,它告诉原生端的代码,我们这次是要干什么,第二个参数是一个
Map<String, dynamic>
型数据,是参数列表,我们可以在原生代码中获取到。
接着,我们需要更新一下对该方法的调用了,回到 example/lib/main.dart
中,修改成如下调用:
_calculateResult = await Wechat.calculate(10, 10);
因为我们现在的 calculate
方法已经是一个异步方法了。
iOS
如果我们的插件需要支持 Android
与 IOS
两端,那么需要同步的在 ios
中实现上面的方法,打开 ios/Classes/WechatPlugin.m
文件,作如下修改:
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
NSDictionary *arguments = [call arguments];
if ([@"getPlatformVersion" isEqualToString:call.method]) {
result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
} else if ([@"calculate" isEqualToString:call.method]) {
NSInteger a = [arguments[@"a"] intValue];
NSInteger b = [arguments[@"b"] intValue];
result([NSString stringWithFormat:@"%d", a + b]);
} else {
result(FlutterMethodNotImplemented);
}
}
实现过程与 java
端保持一致即可。
添加第三方 SDK
我们的插件是可以提供微信的分享相关功能的,所以,肯定需要用到第三方SDK,还是从 Android 开始。
Android 端 WechatSDK
按 官方接入指南 所述,我们需要添加依赖:
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}
或
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
前者带有统计功能,这很简单,打开 android/build.gradle
文件 ,在最下方粘贴以上片段即可:
...
android {
compileSdkVersion 27
defaultConfig {
minSdkVersion 16
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
}
dependencies {
compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}
然后,回到 WechatPlugin.java
文件,先添加一个 register
方法,它将我们的Appid 注册给微信,还是接着前面的 onMethodCall
中的 if
判断:
...
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
...
else if (call.method.equals("register")) {
appid = call.argument("appid");
api = WXAPIFactory.createWXAPI(context, appid, true);
result.success(api.registerApp(appid));
}
...
然后回到 lib/wechat.dart
添加相应调用:
...
/// Register app to Wechat with [appid]
static Future<dynamic> register(String appid) async {
var result = await _channel.invokeMethod(
'register',
{
'appid': appid
}
);
return result;
}
...
此时,在我们的 example
应该中,就可以调用 Wechat.register
方法,来注册应用了
ios
按照官方 ios 接入指南所述,我们可以通过 pod
添加依赖:
pod 'WechatOpenSDK'
打开 ios/wechat.podspec
,可以看到如下内容:
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'wechat'
s.version = '0.0.1'
s.summary = 'A new flutter plugin project.'
s.description = <<-DESC
A new flutter plugin project.
DESC
s.homepage = 'http://example.com'
s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.ios.deployment_target = '8.0'
end
留意到数第三行的 s.dependency
,这就是在指定我们依赖 Flutter
,如果有其它依赖在这里添加一行即可:
...
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.dependency 'WechatOpenSDK'
s.ios.deployment_target = '8.0'
end
然后打开 ios/Classes/WechatPlugin.h
文件,修改如下:
#import <Flutter/Flutter.h>
#include "WXApi.h"
@interface WechatPlugin : NSObject<FlutterPlugin, WXApiDelegate>
@end
再回到 ios/Classes/WechatPlugin.m
,接着前面的 if
条件继续添加判断:
...
// Register app to Wechat with appid
else if ([@"register" isEqualToString:call.method]) {
[WXApi registerApp:arguments[@"appid"]];
result(nil);
}
...
此时,我们的插件已经支持微信 SDK 的 注册至微信 功能了,更多实现,本文就不再讨论,有兴趣,可以直接下载完整项目,后面都是大同小异的实现,唯一需要的是,你需要有一定的 Java
编码与 Objective-C
编码能力。