Flutter 插件開發:以微信SDK爲例

就像 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 系統

打開 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());

這裏就是在註冊咱們的插件,將 wechat 註冊成爲咱們的 channel 名,這樣,纔不會調用 alipay 插件的調用最後到了 wechat 插件這裏。

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") 判斷,這裏面具體的過程是:

  1. 調用 call.argument() 方法,能夠取得由 wechat.dart 傳遞過來的參數
  2. 計算結果
  3. 調用 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 編碼能力。

相關文章
相關標籤/搜索