本文主要介绍Flutter Plugin原理以及其开发和发布过程。

前言

Flutter优势主要体现在UI上–高性能且跨平台表现一致。但是针对平台(Android、IOS)的实现,如:获取电量、判断WiFi使用、调起WebView加载网页等,得调用特定平台的API包。Flutter Plugin就是为调用平台API而生。

下文中所提及到的"平台"指的是Android、IOS两端。

介绍

Flutter Plugin

包含针对Android(Java或Kotlin代码)或iOS(Objective-C或Swift代码)的原生实现,通过Platform Channels与FLutter(dart)层通讯并暴露API。

img

Platform Channels

  • 允许Flutter UI和平台之间传递消息。
  • Platform Channel中的消息和响应是异步传递的,以确保用户界面保持响应。
  • 一个Flutter应用可以存在多个channel,使用name作为区分。
  • Platform Channel并非是线程安全,故平台跟Flutter Engine的所有交互必须在平台的主线程中执行的。
  • Flutter默认的消息编解码器是StandardMessageCodec class,现支持平台数据类型如下:

img

创建Flutter Plugin

接下来介绍使用Android Studio创建Flutter Plugin。使用Visual Studio Code创建的过程也是大同小异,机智的你一定能举一反三,在这里就不一一细说。

第一步:选择创建一个Flutter project

img

第二步:选择Flutter Project的类型为Flutter Plugin

  • Flutter Application 是创建一个纯flutter项目工程
  • Flutter Plugin 是创建一个可以暴露平台API的插件工程
  • Flutter Package 是创建一个纯Dart的组件包。
  • Fultter Module 是创建一个Flutter Module,用于被引入现有的原生App。

img

第三步:填写Project name等工程信息,完成Flutter Plugin创建。

工程创建完成后的目录结构如下: + lib/flutter_plugin_eg.dart 是插件包dart API实现 + android/src/main/ 是插件包API Android的实现 + ios/Classes/ 是插件包API IOS的实现 + example 是基于依赖当前插件的纯flutter示例工程,一般用作展示API调用。

注:从 Flutter 1.9 开始,iOS 新项目默认使用 Swift 语言,而非 Objective-C;Android 新项目则默认使用 Kotlin,而非 Java。如有需要,是可随时切换回之前的 Objective-C 或 Java。

img

使用Android模拟器运行example工程

我们先看看示例工程在Android模拟器上的运行效果:

进入example工程目录,运行lib/main.dart。

APP在Android模拟器运行后,可以看到屏幕出现了"Running on: Android 9"。

img

接下来我们通过代码看看flutter是怎么显示出当前平台的系统版本。

示例工程 example/lib/main.dart

关键代码:

...
import 'package:flutter_plugin_eg/flutter_plugin_eg.dart';
...
platformVersion = await FlutterPluginEg.platformVersion;
...
setState(() {
  _platformVersion = platformVersion;
});
...
body: Center(
  child: Text('Running on: $_platformVersion\n'),
)

示例工程中的lib/main.dart引入了我们刚刚创建Flutter Plugin中dart API实现flutter_plugin_eg.dart

随后使用异步的方式调用flutter_plugin_eg.dartFlutterPluginEg.platformVersion并把返回值赋值给platformVersion,随后通过setState方法把platformVersion的值赋值给当前状态组件的_platformVersion,触发UI重渲把_platformVersion的值"Android 9"显示出来。

Flutter Plugin中dart API实现 lib/flutter_plugin_eg.dart

全量代码:

import 'dart:async';

import 'package:flutter/services.dart';

class FlutterPluginEg {
  static const MethodChannel _channel =
      const MethodChannel('flutter_plugin_eg');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}
  • service.dart暴露与平台通讯的API,如:MethodChannelPlatform Channel的一种类型
  • _channel是FlutterPluginEg类的属性,是一个实例化的MethodChannel,name为"flutter_plugin_eg"
  • platformVersion是FlutterPluginEg类的静态可计算属性,会异步返还一个String。
  • platformVersion中,调用_channelinvokeMethod方法,入参"getPlatformVersion"为调用平台约定的方法名。然后把invokeMethod的异步结果赋值给String version作为platformVersion的返回值。

Flutter Plugin中Android实现

android/src/main/kotlin/…/FlutterPluginEgPlugin.kt 全量代码:

package com.yy.flutter_plugin_eg

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

class FlutterPluginEgPlugin: MethodCallHandler {
  companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "flutter_plugin_eg")
      channel.setMethodCallHandler(FlutterPluginEgPlugin())
    }
  }

  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }
}

再次强调:一个Flutter应用是可以有多个channel,并而每个channel都可以有多个method,所以需要重点了解平台的代码是通过怎么样去对接channel name 与method name。从上文.kt源码可以看到:

  • 注册MethodChannel约定通道名"flutter_plugin_eg",并开始监听通道消息。
companion object {
    @JvmStatic
    fun registerWith(registrar: Registrar) {
      val channel = MethodChannel(registrar.messenger(), "flutter_plugin_eg")
      channel.setMethodCallHandler(FlutterPluginEgPlugin())
    }
  }
  • 实现onMethodCall方法,判断方法名"getPlatformVersion",返回Android ${android.os.Build.VERSION.RELEASE}
override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }

当channel name 和 method name 约定后,Flutter Plugin就可以在dart中方便调用平台的实现,并暴露API供Flutter项目使用。

Flutter Plugin中IOS实现

ios/Classes/SwiftFlutterPluginEgPlugin.swift

import Flutter
import UIKit

public class SwiftFlutterPluginEgPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "flutter_plugin_eg", binaryMessenger: registrar.messenger())
    let instance = SwiftFlutterPluginEgPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
}

可以看出,IOS与Android实现思路雷同:通过"flutter_plugin_eg"注册FlutterMethodChannel并开始监听。

但惊奇发现Flutter Plugin中swift默认生成的代码居然没FlutterMethodCall方法名判断,在swift断点看是能获取到"getPlatformVersion"这个方法名的。

img

IOS模拟器上运行效果:

img

发布Flutter Plugin

轮子造好后,可以发布到pub.dev,以下内容纯属搬砖。

补充文档

在Flutter Plugin根目录添加 + README.md:介绍包的文件 + CHANGELOG.md 记录每个版本中的更改 + LICENSE 包含软件包许可条款的文件 + 编写API文档

发布package

一旦你实现了一个包,你可以在Pub上发布它 ,这样其他开发人员就可以轻松使用它

在发布之前,检查pubspec.yaml、README.md以及CHANGELOG.md文件,以确保其内容的完整性和正确性。

然后, 运行 dry-run 命令以查看是否都准备OK了:

flutter packages pub publish --dry-run

最后, 运行发布命令:

flutter packages pub publish

有关发布的详细信息,请参阅Pub publishing docs

思考

开发一个功能完备的APP,说到底还是需要有原生平台开发的能力,就如Flutter Plugin需要开发原生平台部分。相信有很多Web前端童鞋也开始接触Flutter,但是如果不去深入了解原生开发,那么也只能停留在UI层上。