开发插件

一个简单的闹钟 API

查看项目源代码:https://github.com/openkraken/samples/tree/main/plugins/my_kraken_plugin

接下来通过一个简单的例子来演示如何给 Kraken 的 JS 环境中添加一个自定义的 API。

我们的目标是通过创建一个插件,然后新增一个 alarmClock 的全局对象,并支持在 JS 层注册一个回调用于处理闹钟响了,然后在 Dart 层去实现定时器的功能。

准备工作

初始化项目工程

通过 flutter create 命令来创建一个 flutter plugin 脚手架。然后在这个 plugin 脚手架上添加我们的代码,现在我们需要添加一些配置,让构建的时候,将所有的依赖都打包进 App 中。

  1. flutter create --template=plugin ./my_kraken_plugin

添加 JavaScript 层的实现

在 lib/ 目录下创建一个名为 my_plugin.js 的文件,然后放入以下代码:

my_plugin.js

  1. kraken.addKrakenModuleListener(function(moduleName, event, data) {
  2. if (moduleName == 'AlarmClock') {
  3. if (alarmClock.onTimeListener != null) {
  4. alarmClock.onTimeListener(event, data);
  5. }
  6. }
  7. });
  8. const alarmClock = {
  9. onTimeListener: null,
  10. setTime(time) {
  11. kraken.invokeModule('AlarmClock', 'setTime', time, (e, ret) => {
  12. if (e) {
  13. throw new Error(e);
  14. }
  15. console.log(ret);
  16. });
  17. },
  18. onTime(fn) {
  19. this.onTimeListener = fn;
  20. },
  21. };
  22. Object.defineProperty(globalThis, 'alarmClock', {
  23. value: alarmClock,
  24. enumerable: true,
  25. writable: false,
  26. configurable: false,
  27. });

通过上面的代码实现了一个 AlarmClock 对象,通过在 JS 插件内调用 kraken.invokeModulekraken.addKrakenModuleListener 方法来实现和 Dart 层的通讯。

Kraken 在 JS 全局环境中注入了名为 kraken 的全局变量,提供插件实现所必要的功能:

  1. // 调用 Dart 函数
  2. kraken.invokeModule: (module: string, method: string, params?: Object | null, fn?: (err: Error, data: any) => void) => string;
  3. // 监听 Dart 触发的 Module 事件
  4. kraken.addKrakenModuleListener: (fn: (moduleName: string, event: Event, extra: string) => void) => void;

Dart 层逻辑的实现

上面的 JavaScript 实现将调用转发到了 Dart 层,接下来就是要在 Dart 层实现闹钟的业务逻辑。

alarm_clock_module.dart

  1. import 'package:kraken/module.dart';
  2. import 'package:kraken/dom.dart';
  3. import 'dart:async';
  4. class AlarmClockModule extends BaseModule {
  5. AlarmClockModule(ModuleManager moduleManager) : super(moduleManager);
  6. @override
  7. String get name => 'AlarmClock';
  8. @override
  9. void dispose() {}
  10. @override
  11. String invoke(String method, dynamic params, InvokeModuleCallback callback) {
  12. try {
  13. if (method == 'setTime') {
  14. dynamic time = params;
  15. Timer(Duration(seconds: time.toInt()), () {
  16. Event alarmEvent = Event('alarm');
  17. moduleManager.emitModuleEvent(name,
  18. event: alarmEvent, data: 'Wake Up!');
  19. callback(data: 'success');
  20. });
  21. }
  22. return null;
  23. } catch (e, stack) {
  24. String errmsg = '$e\n$stack';
  25. callback(errmsg: errmsg);
  26. }
  27. return null;
  28. }
  29. }

Kraken 提供了基础的 BaseModule 抽象类,实现 BaseModule 所定义的方法就可以实现一个 Kraken 的 Module。

Kraken 在设计上使用 Module 来处理来自 JavaScript API 的调用。因此对于 AlarmClock 这个 JS API,这个 Module 命名是 AlarmClockModule 。

在 Module 内向 JavaScript 返回数据有 2 种方式,第一种是通过 InvokeModuleCallback callback 来进行返回。只要 JavaScript 的代码在调用的时候,在最后了一个参数传入了一个函数作为回调的话,就可以在 Dart 层调用 InvokeModuleCallback callback 来直接进行回调。回调参数可以传递 errmsgdata,用于处理异常和正常的两种情况。

第二种方式是在 Module 内的任何函数内调用 moduleManager.emitModuleEvent(name, event: alarmEvent, data: 'Wake Up!'); 来触发一个 Module 事件。通过在 JavaScript 上调用 kraken.addKrakenModuleListener 就可以监听到这个事件。不过值得注意的是,任何一个 Module 所触发的事件都会执行 kraken.addKrakenModuleListener 所注册的回调,因此还需要判断回调执行时调用的 Module 名称。

完成插件的注册

现在我们已经完成了大部分功能的实现,接下来只需把代码注册到 Kraken 中,就大功告成了。

使用下面的命令调用 kraken-cli 所提供的 qjsc 命令将 JavaScript 代码转成带有 QuickJS bytecode 的 Dart 源代码:

  1. kraken qjsc ./lib/my_plugin.js ./lib/my_plugin_qjsc.dart --dart --pluginName my_plugin

就会在 lib/ 目录下生成 my_plugin_qjsc.dart 文件。

在插件初始化阶段初始化 JS

  1. import 'alarm_clock_module.dart';
  2. import 'package:kraken/module.dart';
  3. import 'my_plugin_qjsc.dart';
  4. class MyKrakenPlugin {
  5. static void initialize() {
  6. registerMyPluginByteData();
  7. ModuleManager.defineModule(
  8. (moduleNamager) => AlarmClockModule(moduleNamager));
  9. }
  10. }

之后只需要在应用中的 main 函数内进行插件的初始化,就可以直接使用 AlarmClock 这个 API 了。

  1. void main() {
  2. MyKrakenPlugin.initialize();
  3. runApp(MyApp());
  4. }