Flutter Plugin開發(fā)流程(提供Android或者iOS的底層封裝)

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末处硬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拇派,更是在濱河造成了極大的恐慌荷辕,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件件豌,死亡現(xiàn)場離奇詭異疮方,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茧彤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門骡显,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人曾掂,你說我怎么就攤上這事惫谤。” “怎么了珠洗?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵溜歪,是天一觀的道長。 經(jīng)常有香客問我许蓖,道長蝴猪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任膊爪,我火速辦了婚禮自阱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘米酬。我一直安慰自己沛豌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布淮逻。 她就那樣靜靜地躺著琼懊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪爬早。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天启妹,我揣著相機(jī)與錄音筛严,去河邊找鬼。 笑死饶米,一個胖子當(dāng)著我的面吹牛桨啃,可吹牛的內(nèi)容都是我干的车胡。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼照瘾,長吁一口氣:“原來是場噩夢啊……” “哼匈棘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起析命,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤主卫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鹃愤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體簇搅,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年软吐,在試婚紗的時候發(fā)現(xiàn)自己被綠了瘩将。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡凹耙,死狀恐怖姿现,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肖抱,我是刑警寧澤备典,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站虐沥,受9級特大地震影響熊经,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜欲险,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一镐依、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧天试,春花似錦槐壳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至带兜,卻和暖如春枫笛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刚照。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工刑巧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓啊楚,卻偏偏與公主長得像吠冤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子恭理,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容