Flutter 提供了一个强大而灵活的框架,允许开发者编写高效且美观的应用。但是,有时候我们需要访问一些 Flutter 核心库之外的平台特性或第三方库,此时就需要使用或创建自定义插件。本文主要介绍如何在 Flutter 中创建自定义插件,以及如何实现 Flutter 插件与 Android 和 iOS 平台的交互。

下面让我们一起来看看吧:

flutter中的 插件主要分为两种, 一种是自定义 package 模式;一种是 plugins 模式;

Package 模式

package 模式可以创建易于共享的模块化代码.使用纯 dart 编写. 不涉及调用原生平台的代码;
我们可以使用 flutter 的指令 flutter create --template=package --org com.qiankun test_package 来创建一个package 包

一个基本的 package 包含以下内容:

  • pubspec.yaml

    package 的定义信息, package name, author, version 等等.更详细的配置你可以在官方文档中查看

  • lib 目录 — package 功能的代码实现

  • LICENSE 文件 — 许可证文件

  • test/xxxx_test.dart — 测试文件

  • CHANGELOG.md — 版本变更文件

1691368847.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
library test_package;

class Snowflake {
// 开始时间截 (2020-04-01)
static const int twepoch = 1585644268888;

// 机器id所占的位数
static const int workerIdBits = 5;
// 数据标识id所占的位数
static const int datacenterIdBits = 5;
// 支持的最大机器id,结果是31
static const int maxWorkerId = -1 ^ (-1 << workerIdBits);
// 支持的最大数据标识id,结果是31
static const int maxDatacenterId = -1 ^ (-1 << datacenterIdBits);
// 序列在id中占的位数
static const int sequenceBits = 12;

// 机器ID向左移12位
static const int workerIdShift = sequenceBits;
// 数据标识id向左移17位
static const int datacenterIdShift = sequenceBits + workerIdBits;
// 时间截向左移22位
static const int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// 生成序列的掩码,这里为4095
static const int sequenceMask = -1 ^ (-1 << sequenceBits);

// 工作机器ID(0~31)
final int workerId;
// 数据中心ID(0~31)
final int datacenterId;
// 毫秒内序列(0~4095)
int _sequence = 0;
// 上次生成ID的时间截
int _lastTimestamp = -1;

Snowflake(this.workerId, this.datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw Exception('workerId can\'t be greater than $maxWorkerId or smaller than 0');
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw Exception('datacenterId can\'t be greater than $maxDatacenterId or smaller than 0');
}
}

// 获得下一个毫秒的时间戳
int _tilNextMillis(int lastTimestamp) {
int timestamp = _timeGen();
while (timestamp <= lastTimestamp) {
timestamp = _timeGen();
}
return timestamp;
}

// 返回以毫秒为单位的当前时间
int _timeGen() {
return DateTime.now().millisecondsSinceEpoch;
}

// 生成下一个ID
int nextId() {
int timestamp = _timeGen();

if (timestamp < _lastTimestamp) {
throw Exception('Clock moved backwards. Refusing to generate id for ${_lastTimestamp - timestamp} milliseconds');
}

if (_lastTimestamp == timestamp) {
_sequence = (_sequence + 1) & sequenceMask;
if (_sequence == 0) {
// 当前毫秒内序列溢出
timestamp = _tilNextMillis(_lastTimestamp);
}
} else {
_sequence = 0;
}
_lastTimestamp = timestamp;

// 移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | _sequence;
}
}


Plugins 模式

Plugins 模式可以针对不同平台分别支持原生功能, 各自平台原生代码实现相应的功能, 然后通过flutter 提供的通信方式实现原生与 flutter 之间的通信,使用使得 Flutter 具有极大的灵活性,能够轻松访问和使用本地平台的功能和服务。

  • Method Channels
    Method Channels 允许 Dart 调用原生代码的方法,并且允许原生代码调用 Dart。它主要用于传递消息和响应,可以进行数据序列化和反序列化。通常用于执行一些具体的任务,例如文件访问、网络请求等。

  • Event Channels
    Event Channels 用于在 Dart 和本地代码之间发送事件流。这在需要将连续事件(例如传感器数据或用户位置更新)从本地代码传输到 Dart 时特别有用。

  • BasicMessageChannel
    BasicMessageChannel 用于传递字符串和半结构化的信息。可以选择不同的编解码器来序列化消息。这对于一些简单的信息交换非常有用。

  • Binary Channels
    Binary Channels 允许直接传递二进制数据,它更底层,通常用于更高级或更特定的用途。

下面我们就来一起看一看如何实现一个 Plugin

  1. 创建
    flutter create --template=plugin --platforms=android,ios -a java -i objc test_plugins ,
    –platforms : 可以再后面跟 plugins 实现的相关平台, 例如:Android , iOS ,macOS, Windows 等
    -a: 表示安卓插件功能使用什么语言实现
    -i: 表示iOS插件功能使用什么语言实现

  2. 编辑 pubspec.yaml 文件
    pubspec 定义了package 的信息, package name, author, version 等等.更详细的配置你可以在官方文档中查看

  3. 编写 dart 代码, 提供通信协议

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        import 'dart:async';
    import 'package:flutter/services.dart';

    class HapticFeedbackPlugin {

    static const MethodChannel _channel = const MethodChannel('haptic_feedback');

    static Future<void> vibrate() async {
    await _channel.invokeMethod('vibrate');
    }
    }
  4. 编写本地代码

    • Android

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class HapticFeedbackPlugin implements MethodCallHandler {
      @Override
      public void onMethodCall(MethodCall call, Result result) {
      if (call.method.equals("vibrate")) {
      Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
      vibrator.vibrate(500); // 震动时长为 500 毫秒
      result.success(null);
      } else {
      result.notImplemented();
      }
      }
      }

    • iOS

      1
      2
      3
      4
      5
      6
      7
      8
      9
      - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
      if ([@"vibrate" isEqualToString:call.method]) {
      AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
      result(nil);
      } else {
      result(FlutterMethodNotImplemented);
      }
      }

  5. 使用插件
    将 插件添加到主项目的 pub 文件中
    通过代码

    1
    HapticFeedbackPlugin.vibrate();

    这篇文章的代码, 你可以在这里查看 github

在这篇博客文章中,我们已经学习了如何使用 Flutter 创建一个package 和 plugins 的插件,并详细解释了每个步骤。通过 Flutter 插件机制,开发者可以轻松地访问并使用本地平台的特性,从而丰富应用的功能。