Flutter Plugin开发流程(提供Android或者iOS的底层封装)

Flutter Plugin:Flutter插件

特殊的Package。提供Android或者iOS的底层封装,在Flutter层提供组件功能,使Flutter可以较方便的调取Native的模块。对于Flutter实现起来比较复杂的部分,都可以封装成Plugin。

其原理如下

iOS

AppDelegate -> FlutterViewController -> iOS Platform API(及第三方依赖)

Android

Activity -> FlutterView -> Android Platform API(及第三方依赖)


原理图

核心原理(通用)

  • dart 中的 getPlatformVersion 通过 _channel.invokeMethod 发起一次请求,
  • Java 代码中的 onMethodCall 方法回被调用,该方法有两个参数:
  • MethodCall call请求本身
  • Result result结果处理方法
  • 然后通过 call.method 可以知道 _channel.invokeMethod 中的方法名,然后通过 result.success 回调返回成功结果响应
  • iOS 类似:register 一个名为 flutter_plugin 的 channel,然后去 handleMethodCall,同样的通过 call.method拿到方法名,通过 result 做出响应.

插件实现步骤

1.创建plugin项目

flutter create --template=plugin flutter_plugin

如果想支持swift或者kotlin,可以用如下命令进行创建:

flutter create --org com.example --plugin -i swift -a kotlin flutter_text_plugin

项目的组织结构如下

root
    android
    example
    ios
    lib
    ...
  • android以及ios文件夹是我们将要编写插件的native层的地方
  • lib文件夹是编写与native层映射的地方
  • example // 一个完整的调用了我们正在开发的插件的 ,编写的插件可以直接在这个项目中进行验证
  • pubspec.yaml // 项目配置文件
  • native与flutter之间不能直接通信,必须通过MethodChannel来间接调用。

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 型变量,用于保存计算结果
  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 的时候,计算一下 6 + 6 的结果
    _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. 输出该结果
                Text('Calculate Result: $_calculateResult\n'),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

效果图

测试效果图

3.支持原生编码

很多时候,写插件,更多的是因为我们需要让应用能够调用原生代码提供的方法

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 中修改方法实现

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 的时候,计算一下 6 + 6 的结果
_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 文件添加方法实现
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 添加相应调用
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 ,可以看到如下内容:
#
# 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 中添加方法实现
else if ([@"register" isEqualToString:call.method]) {
    [WXApi registerApp:arguments[@"appid"]];
    result(nil);
  }

5.插件发布到pub

在发布之前,确保pubspec.yaml,、README.md以及CHANGELOG.md文件的内容都正确填写完毕。可以通过dry-run命令来看准备是否就绪

flutter packages pub publish --dry-run

检查无误后,可以执行下面的命令,发布到Pub上。

flutter packages pub publish

引用插件库

1.引用发布的库

dependencies:
  flutter_plugin: ^0.0.1

如果这个库包含了一些平台相关的东西,例如需要在native层进行使用的话,则需要在对应的native项目单独做引用

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.引用未发布的库

引用未发布的库有两种方式,通过本地路径和git地址的方式:

2.1-基于Path的引用方式:

这种方式主要针对本地的未发布的库,引用的路径可以是相对或者绝对路径。

dependencies:
  plugin1:
    path: ../plugin1/

2.2-基于Git的引用方式:

这种方式针对存放在git上的库,其中path是可选的,可以定位到某个子目录

dependencies:
  package1:
    git:
      url: git://github.com/flutter/packages.git
      path: packages/package1  

3.引用冲突

引用不同的库可能会导致一些冲突,例如A和B两个插件,都包含了C插件,但是所需的版本不同。因此我们可以采取以下措施避免这种问题:

  • 尽量使用范围版本而不是指定一个特定的版本。
  • 强制统一冲突的插件版本
  • 对于native层,android可以通过force命令强制指定版本,而iOS这边,Cocoapods则不支持引用的override功能

Flutter插件报错汇总-补充

1.The application's Info.plist does not contain CFBundleVersion.

解决办法:
project目录/example/pubspec.yaml该目录添加version 字段即可
version: 0.0.1

效果图

2.创建Plugin 项目

  • 默认创建方式
flutter create --org com.example --template=plugin flutter_demo
  • Objective-C 项目
flutter create --org com.example --template=plugin -i objc -a java flutter_demo
  • Swift 项目
flutter create --org com.example --template=plugin -i swift -a kotlin flutter_demo
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容