Flutter Plugin:Flutter插件
特殊的Package尤辱。提供Android或者iOS的底層封裝懈凹,在Flutter層提供組件功能,使Flutter可以較方便的調(diào)取Native的模塊。對于Flutter實(shí)現(xiàn)起來比較復(fù)雜的部分,都可以封裝成Plugin仅孩。
其原理如下
iOS
AppDelegate -> FlutterViewController -> iOS Platform API(及第三方依賴)
Android
Activity -> FlutterView -> Android Platform API(及第三方依賴)
核心原理(通用)
- dart 中的 getPlatformVersion 通過
_channel.invokeMethod
發(fā)起一次請求, - Java 代碼中的
onMethodCall
方法回被調(diào)用印蓖,該方法有兩個參數(shù): -
MethodCall call
:請求本身 -
Result result
:結(jié)果處理方法 - 然后通過
call.method
可以知道 _channel.invokeMethod 中的方法名
辽慕,然后通過result.success
回調(diào)返回成功結(jié)果響應(yīng)
- iOS 類似:register 一個名為
flutter_plugin
的 channel,然后去handleMethodCall
赦肃,同樣的通過call.method
拿到方法名溅蛉,通過result
做出響應(yīng).
插件實(shí)現(xiàn)步驟
1.創(chuàng)建plugin項(xiàng)目
flutter create --template=plugin flutter_plugin
如果想支持swift或者kotlin,可以用如下命令進(jìn)行創(chuàng)建:
flutter create --org com.example --plugin -i swift -a kotlin flutter_text_plugin
項(xiàng)目的組織結(jié)構(gòu)如下
root
android
example
ios
lib
...
- android以及ios文件夾是我們將要編寫插件的native層的地方
- lib文件夾是編寫與native層映射的地方
- example // 一個完整的調(diào)用了我們正在開發(fā)的插件的 他宛,編寫的插件可以直接在這個項(xiàng)目中進(jìn)行驗(yàn)證
- pubspec.yaml // 項(xiàng)目配置文件
- native與flutter之間不能直接通信船侧,必須通過MethodChannel來間接調(diào)用。
2.編寫Dart測試代碼
修改 example/lib/main.dart 代碼
class FlutterPlugin {
...
static int calculate (int a, int b) {
return a + b;
}
}
class _MyAppState extends State<MyApp> {
String _platformVersion = 'Unknown';
///1. 定義一個 int 型變量厅各,用于保存計(jì)算結(jié)果
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;
///2. init 的時候勺爱,計(jì)算一下 6 + 6 的結(jié)果
_calculateResult = FlutterPlugin.calculate(6, 6);
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'),
///3. 輸出該結(jié)果
Text('Calculate Result: $_calculateResult\n'),
],
),
),
),
),
);
}
}
效果圖
3.支持原生編碼
很多時候,寫插件讯检,更多的是因?yàn)槲覀冃枰寫?yīng)用能夠調(diào)用原生代碼提供的方法
Android
打開 /android/src/main/java/com/example/flutter_plugin/FlutterPlugin.java
文件
package com.example.flutter_plugin;
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;
import com.tencent.mm.opensdk.openapi.WXAPIFactory;
/** FlutterPlugin */
public class FlutterPlugin implements MethodCallHandler {
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_plugin");
channel.setMethodCallHandler(new FlutterPlugin());
}
@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 if (call.method.equals("register")) {
appid = call.argument("appid");
api = WXAPIFactory.createWXAPI(context, appid, true);
result.success(api.registerApp(appid));
} else {
result.notImplemented();
}
}
}
iOS
打開 ios/Classes/FlutterPlugin.m
文件
#import "FlutterPlugin.h"
@implementation FlutterPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
FlutterMethodChannel* channel = [FlutterMethodChannel
methodChannelWithName:@"flutter_plugin"
binaryMessenger:[registrar messenger]];
FlutterPlugin* instance = [[FlutterPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
}
- (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 if ([@"register" isEqualToString:call.method]) {
[WXApi registerApp:arguments[@"appid"]];
result(nil);
}else {
result(FlutterMethodNotImplemented);
}
}
@end
lib/flutter_plugin.dart
中修改方法實(shí)現(xiàn)
import 'dart:async';
import 'package:flutter/services.dart';
class FlutterPlugin {
static const MethodChannel _channel =
const MethodChannel('flutter_plugin');
static Future<String> get platformVersion async {
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
static Future<int> calculate (int a, int b) async {
final String result = await _channel.invokeMethod('calculate', {
'a': a,
'b': b
});
return int.parse(result);
}
/// Register app to Wechat with [appid]
static Future<dynamic> register(String appid) async {
var result = await _channel.invokeMethod(
'register',
{
'appid': appid
}
);
return result;
}
}
在main.dart
中使用
///2. init 的時候琐鲁,計(jì)算一下 6 + 6 的結(jié)果
_calculateResult = await FlutterPlugin.calculate(6, 6);
FlutterPlugin.register('appId');
4.添加第三方 SDK
4.1-Android
- 打開
android/build.gradle
文件 ,在最下方粘貼以上片段即可
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
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:+'
}
- FlutterPlugin.java 文件添加方法實(shí)現(xiàn)
else if (call.method.equals("register")) {
appid = call.argument("appid");
api = WXAPIFactory.createWXAPI(context, appid, true);
result.success(api.registerApp(appid));
}
- lib/flutter_plugin.dart 添加相應(yīng)調(diào)用
static Future<dynamic> register(String appid) async {
var result = await _channel.invokeMethod(
'register',
{
'appid': appid
}
);
return result;
}
4.2-iOS
- 通過 pod 添加依賴:(
s.dependency 'WechatOpenSDK'
)
打開 ios/flutter_plugin.podspec 人灼,可以看到如下內(nèi)容:
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'flutter_plugin'
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.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
- 在.m 中添加方法實(shí)現(xiàn)
else if ([@"register" isEqualToString:call.method]) {
[WXApi registerApp:arguments[@"appid"]];
result(nil);
}
5.插件發(fā)布到pub
在發(fā)布之前,確保pubspec.yaml,投放、README.md以及CHANGELOG.md文件的內(nèi)容都正確填寫完畢奈泪。可以通過dry-run
命令來看準(zhǔn)備是否就緒
灸芳。
flutter packages pub publish --dry-run
檢查無誤后涝桅,可以執(zhí)行下面的命令,發(fā)布到Pub上烙样。
flutter packages pub publish
引用插件庫
1.引用發(fā)布的庫
dependencies:
flutter_plugin: ^0.0.1
如果這個庫包含了一些平臺相關(guān)的東西冯遂,例如需要在native層進(jìn)行使用的話,則需要在對應(yīng)的native項(xiàng)目單獨(dú)做引用
1.1-Android
修改android/build.gradle的dependencies處做引用:
dependencies {
provided rootProject.findProject(":url_launcher")
}
1.2-iOS
修改.podspec
文件
Pod::Spec.new do |s|
# lines skipped
s.dependency 'flutter_plugin'
2.引用未發(fā)布的庫
引用未發(fā)布的庫有兩種方式谒获,通過本地路徑和git地址的方式:
2.1-基于Path的引用方式:
這種方式主要針對本地的未發(fā)布的庫蛤肌,引用的路徑可以是相對或者絕對路徑。
dependencies:
plugin1:
path: ../plugin1/
2.2-基于Git的引用方式:
這種方式針對存放在git上的庫批狱,其中path是可選的裸准,可以定位到某個子目錄
dependencies:
package1:
git:
url: git://github.com/flutter/packages.git
path: packages/package1
3.引用沖突
引用不同的庫可能會導(dǎo)致一些沖突,例如A和B兩個插件赔硫,都包含了C插件炒俱,但是所需的版本不同。因此我們可以采取以下措施避免這種問題:
- 盡量使用范圍版本而不是指定一個特定的版本。
- 強(qiáng)制統(tǒng)一沖突的插件版本
- 對于native層权悟,android可以通過
force
命令強(qiáng)制指定版本砸王,而iOS這邊,Cocoapods則不支持引用的override功能
僵芹。
Flutter插件報(bào)錯匯總-補(bǔ)充
1.The application's Info.plist does not contain CFBundleVersion.
解決辦法:
project目錄/example/pubspec.yaml
該目錄添加version 字段即可
version: 0.0.1
2.創(chuàng)建Plugin 項(xiàng)目
- 默認(rèn)創(chuàng)建方式
flutter create --org com.example --template=plugin flutter_demo
- Objective-C 項(xiàng)目
flutter create --org com.example --template=plugin -i objc -a java flutter_demo
- Swift 項(xiàng)目
flutter create --org com.example --template=plugin -i swift -a kotlin flutter_demo