§ Fox4.0外壳教程-插件开发
§ 说明
Fox4.0外壳是基于Flutter,主要是使用Dart语言进行。少量的第三方SDK集成还需要用到原生语言,例如OCR、人脸识别等。
点击Flutter和Dart教程 (opens new window)学习。
§ 工程结构说明
§ 工程目录结构
.
├── apps # 应用目录
│ └── fox_app
├── melos_fox_flutter_packages.iml
├── melos.yaml
├── packages # 插件目录
│ ├── fox_baidu_location
│ ├── fox_config
│ ├── fox_core
│ ├── fox_file_picker
│ ├── fox_http_fetch
│ ├── fox_image
│ ├── fox_loading
│ ├── fox_location
│ ├── fox_logger
│ ├── fox_media_compress
│ ├── fox_media_picker
│ ├── fox_ninetales
│ ├── fox_plugin_biometric
│ ├── fox_plugin_cover
│ ├── fox_plugin_device_info
│ ├── fox_plugin_encrypt
│ ├── fox_plugin_file_proxy
│ ├── fox_plugin_http_client
│ ├── fox_plugin_lifecycle
│ ├── fox_plugin_location
│ ├── fox_plugin_media_proxy
│ ├── fox_plugin_micro_app
│ ├── fox_plugin_preference
│ ├── fox_plugin_share_bus
│ ├── fox_plugin_trace
│ ├── fox_plugin_version
│ ├── fox_plugin_wechat_proxy
│ ├── fox_provider
│ ├── fox_session_http_fetch
│ ├── fox_trace
│ └── fox_version
├── pubspec.lock
├── pubspec.yaml
├── README.md
└── tool
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
§ 应用目录
apps目录为应用的目录,当前的应用为fox_app主要适配移动端(Android、iOS、ohos),后续可根据项目需要新建应用,例如fox_app_pc
§ 插件目录
packages目录为插件的目录,应用可根据需要需要通过引用不同的插件。
在应用下的pubspec.yaml进行插件引用,例如fox_app下的pubspec.yaml的引用如下:
name: fox_app
description: "fox应用"
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.4.0 <4.0.0'
dependencies: # 应用插件列表
fox_plugin_cover:
path: ../../packages/fox_plugin_cover
fox_plugin_wechat_proxy:
path: ../../packages/fox_plugin_wechat_proxy
fox_plugin_biometric:
path: ../../packages/fox_plugin_biometric
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
插件在dependencies下进行本地引用
插件命名:
path: ../../packages/插件名
2
例如引用fox_config插件,的写法如下:
fox_config:
path: ../../packages/fox_config
2
§ 开发规范
§ 应用类
应用是程序的启动入口,需要建立在apps目录下:
fox_app_xxx
例如 fox_app_pc为pc端的应用
§ 工具类插件
工具类型插件,为通用功能的封装,主要给其他插件引用,命名规范为:fox_xxx
例如:
fox_http_fetch 封装了http访问功能。
fox_core封装通用的工具类型,如文件访问、配置设置、加密/解密工具、公共存储、md5等通用功能。
§ H5类插件
H5类插件、为H5应用提供原生能力,命名规范为fox_plugin_xxx
例如:
fox_plugin_http_client为H5应用提供服务请求、文件上传下载功能。
fox_plugin_file_proxy为H5应用提供文件操作功能。
§ 插件开发
§ H5类插件开发
根据逻辑功能,H5类插件分为三个父类:Plugin、Native、Device。
- Plugin类功能最强大、提供APP生命周期hook和H5访问接口。
- Native和Device是简化类的插件,是Plugin的子集、仅仅提供H5的访问接口。
- 而Native和Device的区别是:Device主要针对设备如相机、指纹仪、刷卡器等,而Native用于封装非设备类的能力、如加密、解密等
§ Plugin插件开发
Plugin插件也分为两中类型:
- 纯dart实现的插件
- dart+原生平台代码(Kotlin、Swift、ArkTS)实现的插件
这两种类型的区别详情可点击Package 和插件 (plugin) 的区别 (opens new window)到Flutter官网进查看。
下面我们将通过一个现成的插件fox_plugin_wechat,学习如何开发插件, 该插件的目录结构如下:
.
├── analysis_options.yaml
├── CHANGELOG.md
├── fox_plugin_wechat_proxy.iml
├── lib
│ ├── fox_plugin_wechat_proxy.dart # 在这里导出公共类
│ └── src # 插件逻辑代码路径
│ ├── utils.dart
│ └── wechat_share_plugin.dart
├── LICENSE
├── pubspec_overrides.yaml
├── pubspec.lock
├── pubspec.yaml # 插件信息和依赖
├── README.md
└── test
└── fox_plugin_wechat_proxy_test.dart
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
§ 步骤一、创建插件
下面我们使用插件fox_plugin_wechat_proxy作为例子,介绍整个开发过程
打开vscode的terminal,进入目录pacakges
纯dart插件创建
# 进行pcakges目录
cd pacakges
# 使用命令创建插件
flutter create --org org.fox --template=package fox_plugin_wechat_proxy
2
3
4
5
6
dart+原生代码插件创建,例子fox_baidu_location
# 进行pcakges目录
cd pacakges
# 使用命令创建插件
flutter create --org org.fox --template=plugin --platforms=android,ios,ohos fox_plugin_xxx
2
3
4
5
6
§ 步骤二、初始化插件
打开fox_plugin_wechat_proxy插件,修改插件根目录下的pubspec.yaml文件
§ 修改插件信息
name: fox_plugin_wechat_proxy
description: "微信访问代理插件"
version: 0.0.1
homepage: https://github.com/jiangch316/
publish_to: none
2
3
4
5
6
- 修改description信息
- 修改homepage信息
- 添加
publish_to: none配置(这个很重要,如果没设置,会导致本地插件无法引用成功)
§ 添加依赖
在dependencies下添加本地依赖和远程pub库上的依赖
dependencies:
flutter:
sdk: flutter
fox_logger:
path: ../fox_logger
fox_ninetales:
path: ../fox_ninetales
fox_core:
path: ../fox_core
fox_http_fetch:
path: ../fox_http_fetch
fluwx: ^5.5.4
2
3
4
5
6
7
8
9
10
11
12
本地依赖,直接手动添加上,如下
fox_logger:
path: ../fox_logger
2
pub库上的依赖可通过命令加入,如下
flutter pub add fluwx
§ 初始插件和下载依赖
通过下面命令,把初始化插件并下载依赖
flutter pub get
§ 步骤三、开发插件代码
§ 1、在lib目录下建立src目录
§ 2、新建插件文件, lib/src/wechat_share_plugin.dart
§ 3、在文件头加入引用
import 'dart:convert';
import 'dart:typed_data';
import 'package:fox_logger/fox_logger.dart';
import 'package:fox_ninetales/fox_ninetales.dart';
import 'package:fox_core/fox_core.dart';
import 'package:fluwx/fluwx.dart';
import './utils.dart';
2
3
4
5
6
7
8
三个很重要的引用
日志能力:import 'package:fox_logger/fox_logger.dart';
插件Plugin父类: 'package:fox_ninetales/fox_ninetales.dart';
工具类和Native父类、Device父类:import 'package:fox_core/fox_core.dart';
§ 4.继承Plugin,并编写逻辑
/*
* @Author: 江成
* @Date: 2025-06-03 11:12:46
* @LastEditors: 江成
* @LastEditTime: 2025-06-17 11:25:49
*/
// 微信分享插件
class WechatSharePlugin extends Plugin {
// 安装
static install() {
// 获取plugin manager
final pluginManager = PluginManager();
// 加入共享存储插件
pluginManager.set('wechatShare', WechatSharePlugin(), PluginType.exec);
}
// 日志
late Logger logger;
// fluwx
late Fluwx fluwx;
// 构造函数
WechatSharePlugin() {
logger = Logger.get(this);
// 创建fluwx对象
fluwx = Fluwx();
}
/// 执行
///
/// 参数
/// action: 动作
/// args: 参数列表
/// context: 回调上下文
@override
execute(String action, List<dynamic> args, CallbackContext context) async {
logger.debug('执行微信分享插件 action:$action');
logger.debug('执行微信分享插件 action:$action');
try {
// 注册应用
if (action == 'registerApp') {
// 逻辑代码
context.success(true);
}
// 判断微信是否安装
if (action == 'isWeChatInstalled') {
// 鸿蒙下isWeChatInstalled方法未实现
if (FoxPlatform.isOhos) {
return true;
}
final ret = await isWeChatInstalled;
if (ret == true) {
context.success(true);
} else {
context.error('微信未安装');
}
return;
}
if (action == 'shareText') {
// 逻辑代码
} else if (action == 'shareImage') {
// 逻辑代码
} else if (action == 'shareMusic') {
// 逻辑代码
} else if (action == 'shareVideo') {
// 逻辑代码
} else if (action == 'shareWebPage') {
// 逻辑代码
} else if (action == 'shareFile') {
// 逻辑代码
} else if (action == 'shareMiniProgram') {
// 逻辑代码
} else {
context.error('未知动作,action:$action');
}
} catch (e) {
// 打印日志
logger.error(e.toString());
// 错误回调
context.error(e.toString());
}
}
}
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
84
85
86
重要代码片段说明
1、execute函数
/// 执行
///
/// 参数
/// action: 动作
/// args: 参数列表
/// context: 回调上下文
@override
execute(String action, List<dynamic> args, CallbackContext context) async {
logger.debug('执行微信分享插件 action:$action');
try {
// 注册应用
if (action == 'registerApp') {
// 逻辑代码
context.success(true);
}
// 判断微信是否安装
if (action == 'isWeChatInstalled') {
// 鸿蒙下isWeChatInstalled方法未实现
if (FoxPlatform.isOhos) {
return true;
}
final ret = await isWeChatInstalled;
if (ret == true) {
context.success(true);
} else {
context.error('微信未安装');
}
return;
}
if (action == 'shareText') {
// 逻辑代码
} else if (action == 'shareImage') {
// 逻辑代码
} else if (action == 'shareMusic') {
// 逻辑代码
} else if (action == 'shareVideo') {
// 逻辑代码
} else if (action == 'shareWebPage') {
// 逻辑代码
} else if (action == 'shareFile') {
// 逻辑代码
} else if (action == 'shareMiniProgram') {
// 逻辑代码
} else {
context.error('未知动作,action:$action');
}
} catch (e) {
// 打印日志
logger.error(e.toString());
// 错误回调
context.error(e.toString());
}
}
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
H5的操作会传递到插件的execute方法,我们根据不同action动作进行对应的处理, 并通过context返回结果
参数说明:
- action: H5应用的执行动作
- args:H5应用传递过来的参数
- context: 调用上下文可以获取应用信息和用于返回结果
返回结果:
- context.success(参数) 成功返回
- context.error(参数) 错误返回
- context.process(参数) 进度返回,用于通知h5应用进度,例如文件下载进度
2、install函数
// 安装
static install() {
// 获取plugin manager
final pluginManager = PluginManager();
// 加入共享存储插件
pluginManager.set('wechatShare', WechatSharePlugin(), PluginType.exec);
}
2
3
4
5
6
7
install函数为静态函数,用安装插件。上面代码就是注册了一个wechatShare的H5类型插件
§ 步骤四、导出插件
在lib/fox_plugin_wechat_proxy.dart中导出插件
/*
* @Author: 江成
* @Date: 2025-06-03 11:08:27
* @LastEditors: 江成
* @LastEditTime: 2025-06-03 16:55:55
*/
library fox_plugin_wechat_proxy;
export './src/wechat_share_plugin.dart';
2
3
4
5
6
7
8
9
10
§ 步骤五、注册插件
我们编写的插件,必须在apps下的应用注册后,才能生效。下面使用fox_app为例子,说明整个注册过程:
§ 引用插件
在apps/fox_app/pubspec.yaml的dependencies节点下加入依赖
dependencies:
fox_plugin_wechat_proxy:
path: ../../packages/fox_plugin_wechat_proxy
2
3
§ 注册插件
在apps/fox_app/lib/src/register/plugin_register.dart中引入插件,并在_installExternalPlugins方法中执行插件install方法
/*
* @Author: 江成
* @Date: 2025-01-31 20:50:12
* @LastEditors: 江成
* @LastEditTime: 2025-06-17 10:05:34
*/
import 'package:fox_ninetales/fox_ninetales.dart';
// 导入core deviceProxy和nativeProxy插件
import 'package:fox_core/fox_core.dart';
// 导入微信分享插件
import 'package:fox_plugin_wechat_proxy/fox_plugin_wechat_proxy.dart';
/// 插件组册表
class PluginsRegister {
/// 安装扩展插件
_installExternalPlugins(PluginManager pluginManager) {
// 安装微信分享插件
WechatSharePlugin.install();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
§ 步骤六、编写H5端插件
下面我们与移动端模版工程mobile来说明如何编写H5插件
在mobile/src/native目录下,新增share目录和对应的index.ts
share
└── index.ts
2
index.ts的代码如下:
/*
* @version: 1.0
* @Author: 江成
* @Date: 2024-04-04 11:56:08
*/
import { BasePlugin, type Result, isJsonString } from '../base'
// 应用参数
// appId: 应用id
// doOnIOS: if [doOnIOS] is true ,fluwx will register WXApi on iOS.
// doOnAndroid: if [doOnAndroid] is true, fluwx will register WXApi on Android.
// universalLink: [universalLink] is required if you want to register on iOS.
type ShareRegisterAppOptions = {
// 应用id
appId: string
// if [doOnIOS] is true ,fluwx will register WXApi on iOS.
doOnIOS?: boolean
// if [doOnAndroid] is true, fluwx will register WXApi on Android.
doOnAndroid?: boolean
// [universalLink] is required if you want to register on iOS.
universalLink?: string
}
// share options
interface ShareOptions {
// 分享场景
scene?: 'session' | 'timeline' | 'favorite'
// 标题
title?: string
// 描述
description?: string
// thumb path
thumbPath?: string
}
// share text options
interface ShareTextOptions extends ShareOptions {
// 分享文本
text: String
}
// share image options
interface ShareImageOptions extends ShareOptions {
// 分享图片路径
imagePath: String
}
// share music options
interface ShareMusicOptions extends ShareOptions {
// music 主页url
musicUrl?: string
// music 主页url
musicLowBandUrl?: string
// music 数据url
musicDataUrl?: string
// music 数据url
musicLowBandDataUrl?: string
}
// share video options
interface ShareVideoOptions extends ShareOptions {
// video url
videoUrl?: string
// video low band url
videoLowBandUrl?: string
}
// share webPage options
interface ShareWebPageOptions extends ShareOptions {
// webPage url
webPageUrl: string
}
// share file options
interface ShareFileOptions extends ShareOptions {
// file path
filePath: string
}
// share MiniProgram options
interface ShareMiniProgramOptions extends ShareOptions {
// webPage url
webPageUrl: string
// user name
userName: string
// path
path?: string
}
/**
* 分享对象
*/
export class Share extends BasePlugin {
/**
* 注册app
* @param options
* @returns
*/
public async registerApp(options: ShareRegisterAppOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'registerApp', [options], true)
return this.encodeResult(ret)
}
// 判断微信是否安装
public async isWeChatInstalled(): Promise<Result> {
const ret = await this.exec('wechatShare', 'isWeChatInstalled', [], true)
return this.encodeResult(ret)
}
/**
* 分享文本
* @param options
* @returns
*/
public async shareText(options: ShareTextOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareText', [options], true)
return this.encodeResult(ret)
}
/**
* 分享图片
* @param options
* @returns
*/
public async shareImage(options: ShareImageOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareImage', [options], true)
return this.encodeResult(ret)
}
/**
* 分享音乐
* @param options
* @returns
*/
public async shareMusic(options: ShareMusicOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareMusic', [options], true)
return this.encodeResult(ret)
}
/**
* 分享视频
* @param options
* @returns
*/
public async shareVideo(options: ShareVideoOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareVideo', [options], true)
return this.encodeResult(ret)
}
/**
* 分享网页
* @param options
* @returns
*/
public async shareWebPage(options: ShareWebPageOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareWebPage', [options], true)
return this.encodeResult(ret)
}
/**
* 分享文件
* @param options
* @returns
*/
public async shareFile(options: ShareFileOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareFile', [options], true)
return this.encodeResult(ret)
}
/**
* 分享小程序
* @param options
* @returns
*/
public async shareMiniProgram(options: ShareMiniProgramOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'shareMiniProgram', [options], true)
return this.encodeResult(ret)
}
}
// 分享单例
const share = new Share()
/**
* 获取分享对象
* @returns
*/
export function useShare() {
return share
}
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
§ 重要代码片段说明
/**
* 注册app
* @param options
* @returns
*/
public async registerApp(options: ShareRegisterAppOptions): Promise<Result> {
const ret = await this.exec('wechatShare', 'registerApp', [options], true)
return this.encodeResult(ret)
}
2
3
4
5
6
7
8
9
this.exec('插件名', '动作', [参数], true)
1、插件名:对应插件的install方法中设置的名称wechatShare,如下
// 安装
static install() {
// 获取plugin manager
final pluginManager = PluginManager();
// 加入微信分享插件
pluginManager.set('wechatShare', WechatSharePlugin(), PluginType.exec);
}
2
3
4
5
6
7
2、动作和参数
- 动作:action
- 参数:args
/// 执行
///
/// 参数
/// action: 动作
/// args: 参数列表
/// context: 回调上下文
@override
execute(String action, List<dynamic> args, CallbackContext context) async
2
3
4
5
6
7
8
§ Native插件开发
Native插件创建插件的过程和Plugin插件基本一致,
下面我们以插件fox_plugin_device_info为例子,介绍Native插件的开发
fox_plugin_device_info插件目录结构
.
├── analysis_options.yaml
├── CHANGELOG.md
├── fox_plugin_device_info.iml
├── lib
│ ├── fox_plugin_device_info.dart
│ └── src
│ ├── device_info_native.dart
│ └── utils.dart
├── LICENSE
├── pubspec_overrides.yaml
├── pubspec.lock
├── pubspec.yaml
├── README.md
└── test
└── fox_plugin_device_info_test.dart
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
§ 步骤一、创建插件
下面我们使用插件fox_plugin_device_info作为例子,介绍整个开发过程
打开vscode的terminal,进入目录pacakges
纯dart插件创建
# 进行pcakges目录
cd pacakges
# 使用命令创建插件
flutter create --org org.fox --template=package fox_plugin_device_info
2
3
4
5
6
dart+原生代码插件创建,例子fox_baidu_location
# 进行pcakges目录
cd pacakges
# 使用命令创建插件
flutter create --org org.fox --template=plugin --platforms=android,ios,ohos fox_plugin_xxx
2
3
4
5
6
§ 步骤二、初始化插件
打开fox_plugin_device_info插件,修改插件根目录下的pubspec.yaml文件
§ 修改插件信息
name: fox_plugin_device_info
description: "设备信息插件"
version: 0.0.1
homepage: https://github.com/jiangch316/
publish_to: none
2
3
4
5
6
7
- 修改description信息
- 修改homepage信息
- 添加
publish_to: none配置(这个很重要,如果没设置,会导致本地插件无法引用成功)
§ 添加依赖
在dependencies下添加本地依赖和远程pub库上的依赖
dependencies:
device_info_plus: ^11.3.0
flutter:
sdk: flutter
fox_logger:
path: ../fox_logger
fox_ninetales:
path: ../fox_ninetales
fox_core:
path: ../fox_core
2
3
4
5
6
7
8
9
10
本地依赖,直接手动添加上,如下
fox_logger:
path: ../fox_logger
2
pub库上的依赖可通过命令加入,如下
flutter pub add xxx
§ 初始插件和下载依赖
通过下面命令,把初始化插件并下载依赖
flutter pub get
§ 步骤三、编写插件代码
§ 1、在lib目录下建立src目录
§ 2、新建插件文件, lib/src/device_info_native.dart
§ 3、在文件头加入引用
import 'package:device_info_plus/device_info_plus.dart';
import 'package:fox_logger/fox_logger.dart';
import 'package:fox_core/fox_core.dart';
import 'utils.dart';
2
3
4
5
二个很重要的引用
日志能力:import 'package:fox_logger/fox_logger.dart';
工具类和Native父类、Device父类:import 'package:fox_core/fox_core.dart';
§ 4.继承Navive,并编写逻辑
/*
* @Author: 江成
* @Date: 2025-05-15 20:43:09
* @LastEditors: 江成
* @LastEditTime: 2025-06-17 10:04:51
*/
import 'package:device_info_plus/device_info_plus.dart';
import 'package:fox_logger/fox_logger.dart';
import 'package:fox_core/fox_core.dart';
import 'utils.dart';
// device info native
class DeviceInfoNative extends Native {
// 安装
static install() {
// 创建device info native
final deviceInfoNative = DeviceInfoNative();
// 获取native manager
final nativeManager = NativeManager();
// 注册uniqueID action
nativeManager.set('getUniqueID', deviceInfoNative);
// 注册uniqueID action
nativeManager.set('uniqueID', deviceInfoNative);
// 注册getDeviceInfo
nativeManager.set('getDeviceInfo', deviceInfoNative);
}
// 日志
late Logger logger;
// 构造函数
DeviceInfoNative() {
// 获取日志
logger = Logger.get(this);
}
///native调用
///
///参数
///action:动作
///param: 参数
///cite: 上下文引用
///callback: 回调函数
@override
Future<void> call(
String action, dynamic params, Cite cite, ProxyCallback callback) async {
try {
// unique id (兼容旧的action:uniqueID)
if (action == 'getUniqueID' || action == 'uniqueID') {
// 唯一id
String uniqueID = await getInstallationId();
// 返回结果
callback(code: ResultCodes.success, message: '', data: uniqueID);
} else if (action == 'getDeviceInfo') {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final info = await deviceInfo.deviceInfo;
// 返回结果
callback(code: ResultCodes.success, message: '', data: info.data);
}
} catch (e) {
final errorMsg = e.toString();
// 打印日志
logger.error(errorMsg);
// 返回结果
callback(code: ResultCodes.success, message: errorMsg, data: {});
}
}
}
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
重要代码片段
1、call方法
///native调用
///
///参数
///action:动作
///param: 参数
///cite: 上下文引用
///callback: 回调函数
@override
Future<void> call(
String action, dynamic params, Cite cite, ProxyCallback callback) async {
try {
// unique id (兼容旧的action:uniqueID)
if (action == 'getUniqueID' || action == 'uniqueID') {
// 唯一id
String uniqueID = await getInstallationId();
// 返回结果
callback(code: ResultCodes.success, message: '', data: uniqueID);
} else if (action == 'getDeviceInfo') {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final info = await deviceInfo.deviceInfo;
// 返回结果
callback(code: ResultCodes.success, message: '', data: info.data);
}
} catch (e) {
final errorMsg = e.toString();
// 打印日志
logger.error(errorMsg);
// 返回结果
callback(code: ResultCodes.success, message: errorMsg, data: {});
}
}
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
call参数说明
- action 动作,和H5端的插件需要一一对应
- params 参数,由H5端传递过来
- cite 上下文引用:获取应用信息
- callback 回调函数,callback(code:响应码, message:错误信息,data:成功返回函数)
响应码如下:
/// result codes
class ResultCodes {
// 成功
static int success = 0;
// 取消
static int cancel = 1;
// 错误
static int error = 2;
// 进度
static int processing = 3;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2、install函数
// 安装
static install() {
// 创建device info native
final deviceInfoNative = DeviceInfoNative();
// 获取native manager
final nativeManager = NativeManager();
// 注册uniqueID action
nativeManager.set('getUniqueID', deviceInfoNative);
// 注册uniqueID action
nativeManager.set('uniqueID', deviceInfoNative);
// 注册getDeviceInfo
nativeManager.set('getDeviceInfo', deviceInfoNative);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
install函数为静态函数,用安装插件。上面代码就是注册了getUniqueID和getDeviceInfo的Native接口
§ 步骤四、导出插件
在lib/fox_plugin_device_info.dart中导出插件
/*
* @Author: 江成
* @Date: 2025-05-15 18:26:09
* @LastEditors: 江成
* @LastEditTime: 2025-06-17 10:05:13
*/
library fox_plugin_device_info;
export './src/device_info_native.dart';
2
3
4
5
6
7
8
9
10
11
§ 步骤五、注册插件
我们编写的插件,必须在apps下的应用注册后,才能生效。下面使用fox_app为例子,说明整个注册过程:
§ 引用插件
在apps/fox_app/pubspec.yaml的dependencies节点下加入依赖
dependencies:
fox_plugin_device_info:
path: ../../packages/fox_plugin_device_info
2
3
§ 注册插件
在apps/fox_app/lib/src/register/plugin_register.dart中引入插件,并在_installExternalPlugins方法中执行插件install方法
/*
* @Author: 江成
* @Date: 2025-01-31 20:50:12
* @LastEditors: 江成
* @LastEditTime: 2025-06-17 10:05:34
*/
import 'package:fox_ninetales/fox_ninetales.dart';
// 导入core deviceProxy和nativeProxy插件
import 'package:fox_core/fox_core.dart';
// 导入device info插件
import 'package:fox_plugin_device_info/fox_plugin_device_info.dart';
/// 插件组册表
class PluginsRegister {
/// 安装扩展插件
_installExternalPlugins(PluginManager pluginManager) {
// 安装device info插件
DeviceInfoNative.install();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
§ 步骤六、编写H5端插件
下面我们与移动端模版工程mobile来说明如何编写H5插件
在mobile/src/native目录下,新增device-info目录和对应的index.ts
sharedevice-info
└── index.ts
2
index.ts的代码如下:
/*
* @Author: 江成
* @Date: 2025-06-12 19:29:16
* @LastEditors: 江成
* @LastEditTime: 2025-06-12 19:34:48
*/
import { ComponentPublicInstance, getCurrentInstance } from 'vue'
import { Result } from '@fox-js/plugins'
import { BaseNative } from '../base'
/**
* device info
*/
export class DeviceInfo extends BaseNative {
/**
* 获取唯一id
* @param args
* @returns
*/
public getUniqueID(cover: boolean = true): Promise<Result> {
return this.call('getUniqueID', { cover })
}
/**
* 获取设备信息
* @param args
* @returns
*/
public getDeviceInfo(cover: boolean = true): Promise<Result> {
return this.call('getDeviceInfo', { cover })
}
}
const deviceInfo = new DeviceInfo()
/**
* 获取device info插件
* @returns
*/
export function useDeviceInfo(): DeviceInfo {
return deviceInfo
}
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
§ 重要代码片段
/**
* 获取唯一id
* @param args
* @returns
*/
public getUniqueID(cover: boolean = true): Promise<Result> {
return this.call('getUniqueID', { cover })
}
2
3
4
5
6
7
8
this.call('Native接口名', 参数)
Native接口名:在原生插件的install方法中通过nativeManager.set注册
// 安装
static install() {
// 创建device info native
final deviceInfoNative = DeviceInfoNative();
// 获取native manager
final nativeManager = NativeManager();
// 注册uniqueID action
nativeManager.set('getUniqueID', deviceInfoNative);
// 注册uniqueID action
nativeManager.set('uniqueID', deviceInfoNative);
// 注册getDeviceInfo
nativeManager.set('getDeviceInfo', deviceInfoNative);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
call动作的处理和参数介绍,需要在原生插件的call方法中处理,原生代码如下:
///native调用
///
///参数
///action:动作
///param: 参数
///cite: 上下文引用
///callback: 回调函数
@override
Future<void> call(
String action, dynamic params, Cite cite, ProxyCallback callback) async {
try {
// unique id (兼容旧的action:uniqueID)
if (action == 'getUniqueID' || action == 'uniqueID') {
// 唯一id
String uniqueID = await getInstallationId();
// 返回结果
callback(code: ResultCodes.success, message: '', data: uniqueID);
} else if (action == 'getDeviceInfo') {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
final info = await deviceInfo.deviceInfo;
// 返回结果
callback(code: ResultCodes.success, message: '', data: info.data);
}
} catch (e) {
final errorMsg = e.toString();
// 打印日志
logger.error(errorMsg);
// 返回结果
callback(code: ResultCodes.success, message: errorMsg, data: {});
}
}
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
§ Device插件开发
Device插件创建插件的过程和Plugin插件基本一致,
下面我们以插件fox_plugin_biometric为例子,介绍Native插件的开发
fox_plugin_biometric插件目录结构
.
├── analysis_options.yaml
├── CHANGELOG.md
├── fox_plugin_biometric.iml
├── lib
│ ├── fox_plugin_biometric.dart
│ └── src
│ ├── biometric_device.dart
│ └── biometric.dart
├── LICENSE
├── pubspec.lock
├── pubspec.yaml
├── README.md
└── test
└── fox_plugin_biometric_test.dart
2
3
4
5
6
7
8
9
10
11
12
13
14
15
§ 步骤一、创建插件
下面我们使用插件fox_plugin_biometric作为例子,介绍整个开发过程
打开vscode的terminal,进入目录pacakges
纯dart插件创建
# 进行pcakges目录
cd pacakges
# 使用命令创建插件
flutter create --org org.fox --template=package fox_plugin_biometric
2
3
4
5
6
dart+原生代码插件创建,例子fox_baidu_location
# 进行pcakges目录
cd pacakges
# 使用命令创建插件
flutter create --org org.fox --template=plugin --platforms=android,ios,ohos fox_plugin_xxx
2
3
4
5
6
§ 步骤二、初始化插件
打开fox_plugin_biometric插件,修改插件根目录下的pubspec.yaml文件
§ 修改插件信息
name: fox_plugin_biometric
description: "生物识别认证插件"
version: 0.0.1
homepage: https://github.com/jiangch316/
publish_to: none
2
3
4
5
6
7
- 修改description信息
- 修改homepage信息
- 添加
publish_to: none配置(这个很重要,如果没设置,会导致本地插件无法引用成功)
§ 添加依赖
在dependencies下添加本地依赖和远程pub库上的依赖
dependencies:
flutter:
sdk: flutter
local_auth: ^2.3.0
fox_logger:
path: ../fox_logger
fox_ninetales:
path: ../fox_ninetales
fox_core:
path: ../fox_core
2
3
4
5
6
7
8
9
10
本地依赖,直接手动添加上,如下
fox_logger:
path: ../fox_logger
2
pub库上的依赖可通过命令加入,如下
flutter pub add local_auth
§ 初始插件和下载依赖
通过下面命令,把初始化插件并下载依赖
flutter pub get
§ 步骤三、编写插件代码
§ 1、在lib目录下建立src目录
§ 2、新建插件文件, lib/src/biometric_device.dart
§ 3、在文件头加入引用
import 'dart:convert';
import 'package:fox_core/fox_core.dart';
import 'package:fox_logger/fox_logger.dart';
import 'biometric.dart';
2
3
4
5
二个很重要的引用
日志能力:import 'package:fox_logger/fox_logger.dart';
工具类和Native父类、Device父类:import 'package:fox_core/fox_core.dart';
§ 4.继承Device,并编写逻辑
/*
* @Author: 江成
* @Date: 2025-06-13 15:07:40
* @LastEditors: 江成
* @LastEditTime: 2025-06-13 21:27:31
*/
import 'dart:convert';
import 'package:fox_core/fox_core.dart';
import 'package:fox_logger/fox_logger.dart';
import 'biometric.dart';
// 生物识别设备
class BiometricDevice extends Device {
// 安装
static install() {
DeviceManager deviceManager = DeviceManager();
deviceManager.set('biometric', BiometricDevice());
}
// 日志
late Logger logger;
// 构造函数
BiometricDevice() {
// 获取日志
logger = Logger.get(this);
}
///device调用
///
///参数
///type:外设类型
///action:动作
///param: 参数
///cite: 上下文引用
///callback: 回调函数
@override
Future<void> call(String type, String action, dynamic params, Cite cite,
ProxyCallback callback) async {
try {
if (action == 'isAvailable') {
// 获取生物识别工具类
final biometric = Biometric();
// 获取生物识别是否可用信息
final ret = await biometric.isAvailable();
// 成功回调
callback(code: ResultCodes.success, data: {
'available': ret.available,
'type': ret.types.join(','),
'types': ret.types
});
} else if (action == 'verify') {
// 获取参数
final options = params is String ? jsonDecode(params) : params;
// 获取验证原因
final reason = options['reason'] as String? ?? '请验证';
// 是否只允许什么识别
final biometricOnly = options['biometricOnly'] as bool? ?? false;
// 获取生物识别工具类
final biometric = Biometric();
// 获取生物识别是否可用信息
final success = await biometric.verify(reason, biometricOnly);
// 成功回调
callback(code: ResultCodes.success, data: success);
}
} catch (e) {
final errorMsg = e.toString();
logger.error(errorMsg);
callback(code: ResultCodes.error, message: errorMsg);
}
}
}
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
重要代码片段
1、call方法
///device调用
///
///参数
///type:外设类型
///action:动作
///param: 参数
///cite: 上下文引用
///callback: 回调函数
@override
Future<void> call(String type, String action, dynamic params, Cite cite,
ProxyCallback callback) async {
try {
if (action == 'isAvailable') {
// 获取生物识别工具类
final biometric = Biometric();
// 获取生物识别是否可用信息
final ret = await biometric.isAvailable();
// 成功回调
callback(code: ResultCodes.success, data: {
'available': ret.available,
'type': ret.types.join(','),
'types': ret.types
});
} else if (action == 'verify') {
// 获取参数
final options = params is String ? jsonDecode(params) : params;
// 获取验证原因
final reason = options['reason'] as String? ?? '请验证';
// 是否只允许什么识别
final biometricOnly = options['biometricOnly'] as bool? ?? false;
// 获取生物识别工具类
final biometric = Biometric();
// 获取生物识别是否可用信息
final success = await biometric.verify(reason, biometricOnly);
// 成功回调
callback(code: ResultCodes.success, data: success);
}
} catch (e) {
final errorMsg = e.toString();
logger.error(errorMsg);
callback(code: ResultCodes.error, message: errorMsg);
}
}
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
call方法的参数说明
- type:外设类型
- action:动作
- param: 参数
- cite: 上下文引用
- callback 回调函数,callback(code:响应码, message:错误信息,data:成功返回函数)
响应码如下:
/// result codes
class ResultCodes {
// 成功
static int success = 0;
// 取消
static int cancel = 1;
// 错误
static int error = 2;
// 进度
static int processing = 3;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2、install函数
// 安装
static install() {
DeviceManager deviceManager = DeviceManager();
deviceManager.set('biometric', BiometricDevice());
}
2
3
4
5
install函数为静态函数,用安装插件。上面代码就是注册了biometric设备的处理插件
§ 步骤四、导出插件
在lib/fox_plugin_biometric.dart中导出插件
/*
* @Author: 江成
* @Date: 2025-06-13 14:58:17
* @LastEditors: 江成
* @LastEditTime: 2025-06-13 20:30:07
*/
library fox_plugin_biometric;
export 'src/biometric_device.dart';
2
3
4
5
6
7
8
9
10
11
12
§ 步骤五、注册插件
我们编写的插件,必须在apps下的应用注册后,才能生效。下面使用fox_app为例子,说明整个注册过程:
§ 引用插件
在apps/fox_app/pubspec.yaml的dependencies节点下加入依赖
dependencies:
fox_plugin_biometric:
path: ../../packages/fox_plugin_biometric
2
3
§ 注册插件
在apps/fox_app/lib/src/register/plugin_register.dart中引入插件,并在_installExternalPlugins方法中执行插件install方法
/*
* @Author: 江成
* @Date: 2025-01-31 20:50:12
* @LastEditors: 江成
* @LastEditTime: 2025-06-17 10:05:34
*/
import 'package:fox_ninetales/fox_ninetales.dart';
// 导入core deviceProxy和nativeProxy插件
import 'package:fox_core/fox_core.dart';
// 导入生物认证插件
import 'package:fox_plugin_biometric/fox_plugin_biometric.dart';
/// 插件组册表
class PluginsRegister {
/// 安装扩展插件
_installExternalPlugins(PluginManager pluginManager) {
// 安装生物识别插件
BiometricDevice.install();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
§ 步骤六、编写H5端插件
下面我们与移动端模版工程mobile来说明如何编写H5插件
在mobile/src/native目录下,新增biometric目录和对应的index.ts
biometric
└── index.ts
2
index.ts的代码如下:
/*
* @Author: 江成
* @Date: 2025-06-13 20:27:57
* @LastEditors: 江成
* @LastEditTime: 2025-06-13 20:35:06
*/
import { BaseDevice, type Result, isJsonString } from '../base'
/**
* 生物认证参数
*/
export type BiometricOptions = {
// 是否打开cover
cover?: boolean
// 验证原因提示
reason?: string
// 是否只允许生物识别
biometricOnly?: boolean
}
/**
* 生物认证对象
*/
export class Biometric extends BaseDevice {
/**
* 判断生物认证是否可用
*/
public async isAvailable(): Promise<Result> {
const ret = await this.call('biometric', 'isAvailable', {})
if (isJsonString(ret.data)) {
ret.data = JSON.parse(ret.data)
}
return ret
}
/**
* 认证
* @param params
*/
public async verify(options: BiometricOptions = {}) {
const ret = await this.call('biometric', 'verify', options)
if (isJsonString(ret.data)) {
ret.data = JSON.parse(ret.data)
}
return ret
}
}
// biometric单例
const biometric = new Biometric(true)
/**
* 获取biometric操作对象
* @returns
*/
export function useBiometric() {
return biometric
}
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
§ 重要代码片段
/**
* 判断生物认证是否可用
*/
public async isAvailable(): Promise<Result> {
const ret = await this.call('biometric', 'isAvailable', {})
if (isJsonString(ret.data)) {
ret.data = JSON.parse(ret.data)
}
return ret
}
2
3
4
5
6
7
8
9
10
call方法:call('设备名', '动作', 参数)
设备名在原生插件的install函数中通过DeviceManager.set(设备名,插件)注册,如下:
// 安装
static install() {
DeviceManager deviceManager = DeviceManager();
deviceManager.set('biometric', BiometricDevice());
}
2
3
4
5
动作需要在对应原生插件中call的处理逻辑,原生代码如下:
///device调用
///
///参数
///type:外设类型
///action:动作
///param: 参数
///cite: 上下文引用
///callback: 回调函数
@override
Future<void> call(String type, String action, dynamic params, Cite cite,
ProxyCallback callback) async {
try {
if (action == 'isAvailable') {
// 获取生物识别工具类
final biometric = Biometric();
// 获取生物识别是否可用信息
final ret = await biometric.isAvailable();
// 成功回调
callback(code: ResultCodes.success, data: {
'available': ret.available,
'type': ret.types.join(','),
'types': ret.types
});
} else if (action == 'verify') {
// 获取参数
final options = params is String ? jsonDecode(params) : params;
// 获取验证原因
final reason = options['reason'] as String? ?? '请验证';
// 是否只允许什么识别
final biometricOnly = options['biometricOnly'] as bool? ?? false;
// 获取生物识别工具类
final biometric = Biometric();
// 获取生物识别是否可用信息
final success = await biometric.verify(reason, biometricOnly);
// 成功回调
callback(code: ResultCodes.success, data: success);
}
} catch (e) {
final errorMsg = e.toString();
logger.error(errorMsg);
callback(code: ResultCodes.error, message: errorMsg);
}
}
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