就像 React Native 同樣,在 Flutter 應用中,若是須要調用第三方庫的方法或者有一些功能須要使用原生的開發來提供,使用 Flutter Plugin 是一種不錯的方式,它本質上就是一個 Dart Package,但與其它的 package 不一樣點在於,Flutter 插件中通常都存在兩個特殊的文件夾:android
與 ios
,若是須要編寫Java、Kotlin或者 Object-C 以及 Swift 代碼,咱們就須要在這兩個文件夾項目中進行,而後經過相應的方法將原生代碼中開發的方法映射到 dart 中。html
本文以開發一個微信插件爲例,爲Flutter應用提供微信分享、登陸、支付等功能,項目代碼能夠直接在下方找到,也已經提交至Pub庫:java
Pub庫:https://pub.dartlang.org/packages/wechat
項目地址:https://github.com/pantao/flutter-wechatandroid
要開發插件,可使用下面的代碼快速基於 plugin
模板開始:ios
flutter create --template=plugin wechat
上面的代碼中,表示以 plugin
模板建立一個名爲 wechat
的 package
,建立完成以後,整個項目的目錄結構就都提供好了,而且官方還提供了一些基本開發示例。git
- android // Android 相關原生代碼目錄 - ios // ios 相關原生代碼目錄 - lib // Dart 代碼目錄 - example // 一個完整的調用了咱們正在開發的插件的 Flutter App - pubspec.yaml // 項目配置文件
example/lib/main.dart
開始在開發咱們的應用以後,先來了解一下 flutter
爲咱們生成的文件們,打開 example/lib/main.dart
,代碼以下:github
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\n'), ), ), ); } }
這裏須要特別注意的就是 initPlatformState()
方法中對 Wechat.platformVersion
的調用,這裏面的 Wechat
就是咱們的插件,platformVersion
就是插件提供的 get
方法,跟着這個文件,找到 lib/wechat.dart
文件,代碼以下:api
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
的和:app
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\n'), // 輸出該結果 Text('Calculate Result: $_calculateResult\n'), ], ), ), ), ), ); } }
不少時候,寫插件,更多的是由於咱們須要讓應用可以調用原生代碼提供的方法,怎麼作呢?
打開 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/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/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
方法已是一個異步方法了。
若是咱們的插件須要支持 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,仍是從 Android 開始。
按 官方接入指南 所述,咱們須要添加依賴:
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 接入指南所述,咱們能夠經過 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
編碼能力。