原文地址: Flutter Background Tasks


Flutter 是一个十分好用的运用 Dart 编程语言构建漂亮移动运用程序的结构,可以让 Android 和 IOS 上共用同一套代码。

移动运用程序可能有运转后台任务需求, 如监听方位改变,监督用户运动情况(步数、跑步、步行、驾驭等);订阅体系事情 如 BootComplete、电池和充电,查找 BT 或 WiFi 网络等。

在 Android 中,咱们可以在运用程序实际封闭时运转一些后台任务!

首要界说一个 BootComplete 播送接收器,当手机发动后立即履行,然后运用 WorkManager 或 AlarmManager 调度后台任务,运用 Service 在后台履行代码。

当然,后台任务中有些需求用户权限,可能会在告诉栏显现一个告诉标明此运用程序在后台运转。只要用户知道并赞同,这些任务就可以在后台运转。

在 iOS 中,后台任务有更严厉的限制,但依然有一些办法可以运转一些后台任务。

提到 Flutter 运用程序及后台任务需求弄清的是他们的履行是在对端渠道!负责注册和办理后台任务(Worker,Alarm,Service,BroadcastReceiver 等)的逻辑是用原生代码编写的,例如 Kotlin 或 Swift。可是,咱们都知道,Flutter 运用程序逻辑是在 Dart 端编写的,这些代码可以构建 UI,还可以办理耐久性数据,用户办理,网络基础架构和令牌等等。

假如咱们想在 Dart 和原生端之间同享数据,可以运用 Flutter 的 MethodChannel 和 EventChannel。

在 Flutter 中,MethodChannel 和 EventChannel 是可以从本地端发送和接收信息到 Dart 端的办法,它们被用于 Flutter 插件。

假设咱们对 BootComplete、电池状况感兴趣,想在后台用 Dart 处理这些事情呢。

一般情况下当运用程序在前台时,经过 MethodChannel 和 EventChannel 在 Dart 侧和本机侧间通讯很简单,可是假如想要从本机侧发动 Dart 并发动一个后台 isolate,该怎么办呢?

让咱们找出来吧!

在继续下面文章之前,我强烈建议您了解 Flutter 插件及其创立办法,因为示例将根据 Flutter 插件完成,详见文档。

发动 Dart 引擎(来自后台)

当运用发动时,Flutter 的 main isolate(进口点)在主(main)函数中发动。走运的是,似乎也可以从本地发动 Dart VM,并在后台 isolate(次进口点)中调用大局函数

Dart VM 发动不仅可以从 main 进口发动,也可以是其他进口,比方后台 isolate 的大局函数

关键在于运用程序后台唤醒时,在本机端持有可用的该进口点(大局函数)引证标识符 — callbackRawHandle

ChatGPT 关于 Dart CallbackRawHandle 说法

在 Dart 中,“callback raw handle”是对 Dart 函数根本完成的引证,可以传递给原生渠道的 API。

callbackRawHandle 允许您绕过 Dart VM 的一般的类型查看,直接从本地代码调用函数。当您需求将 Dart 函数作为回调传递给本地库时,这十分有用callbackRawHandle 运用的场景是运用程序本地端调用 Dart 代码。

为了从本地后台运转 Dart 代码,需求履行几个步骤,在详细介绍代码前,我想用图表来展示它,然后解说它:

Flutter 后台任务

让咱们来看看这个图表并解说每个部分,如您所见,有六个首要步骤:

  1. 在 Dart 中界说一个无参 callbackDispatcher 大局函数,它将作为一个次进口点在后台阻隔中运转,并直接从本地端调用。
  2. 这部分也有三个步骤:
  • 当运用程序初次发动时,将callbackDispatcher函数经过一个 api 的参数传递给插件
  • 在插件中,运用 PluginUtils::toRawHandle 办法生成 callbackDispatcherRawHandle,并经过 MethodChannel 将其转发到插件的本地端(2’)。

上述进程在 Dart 侧。

  • RawHandle 值(一个长整数)保存在本地端的耐久存储中,以便将来可以运用 — 2’’

long 值可以了解成 Dart 中的回调函数的内存地址,传给了本地端。

以上部分可以完结后,咱们将RawHandle保存在耐久存储中,当运用程序在后台醒来时,存储中 RawHandle 可用,并将用于直接从本地端调用callbackDispatcher

  1. 当运用在后台唤醒时(例如:发动完结-后台进程初始化器),从耐久化存储中获取 RawHandle。

  2. 在后台初始化FlutterEngineFlutterLoader

5.经过 RawHandle 获取FlutterCallbackInfo

  1. 运用DartExecutorcallbackInfo(来自第 5 步)调用executeDartCallback。这样就可以调用在 Dart 侧的callbackDispatcher函数了。

  2. callbackDispatcher 被调用时,你可以在插件中注册其他事情并在后台的 Dart 侧处理它们,或许运用其他插件!

原生插件中可以经过 Dart 侧函数句柄调用 Dart 侧代码,也可以经过句柄运用其他插件。

如上所述,callbackDispatcher 仅仅 Dart 后台阻隔的进口点。

让咱们将上面的步骤分解为代码示例:

在 main.dart 中创立 callbackDispatcher 回调分发器

Flutter 后台任务

在上面的代码片段中,在 main.dart 中创立了appCallbackDispatcher 无参大局函数,它将成为 Dart 端的次进口点,可直接在本地调用,并在后台阻隔中运转。

了解:一个大局函数,运转在后台线程中。

注意 @pragma(‘vm:entry-point’) 注释是必须的,因为这个函数在 Dart 侧没有调用(它直接从本地调用),所以 AOT tree-shaking 编译器在生产构建时可能会将其删去。这个注释可以防止编译器删去这个函数。

让咱们转到插件侧看看它的姿态:

在插件 Dart 代码中获取 RawHandle

Flutter 后台任务

在上面的代码示例中,咱们可以看到一个经典的 Flutter 插件 Dart 端。这里感兴趣的是registerCallbackDispatcher API,它是从运用程序的main()函数中运用 callbackDispatcher作为参数调用的 API。然后,在第 13 到 15 行,运用PluginUtilities和 toRawHandle()办法获取其RawHandle

然后,在第 17 行,运用 methodChannel 将其转发到本地端。在图表中,这一部分对应于步骤 2 和 2’。

将 RawHandle 保存到耐久性存储中(本地端)

让咱们切换到插件本机端,看看它如何处理 registerCallbackDispatcher api

上面的代码示例分为两个部分:

Flutter 后台任务

Flutter 后台任务

  1. 在第一部分中,咱们看到了 MyPlugin.kt 文件,运用 Kotlin 编写的本机插件。咱们对“registerCallbackDispatcher”api 感兴趣,它是从 Dart 端调用的,在第 18 行,获得了作为参数传递的 dispatcherHandle。在第 21 行将其保存在一个 SharedPreference 耐久存储中。
  2. 第二部分仅仅一个辅佐类,用于保存和读取SharedPreferences中的数据。

这个解说是针对咱们图表中的 2”。

从后台发动 Dart 引擎

这便是故事的中心部分,咱们想从后台发动 Dart 引擎和 VM,但不发动主阻隔和 UI 部分。 如图 3 中所示,它说的是后台进程初始化器。 为简单起见,我挑选了一个 BootComplete BroadcastReceiver,在手机重新发动时发动 Dart VM,但取决于您的运用程序要求,您可以决定何时发动 Dart VM 的正确机遇:

Flutter 后台任务

在上面的代码中,咱们看到一个典型的 BroadcastReceiver,它在手机完结发动时调用。从 onReceive 中,咱们开端并调用咱们的 dart 回调分配器,分为两个首要步骤(图中的 4 和 5)。

  1. initializeFlutterEngine method:
  • 创立一个 FlutterLoader 对象并查看其是否已初始化
  • 在第 19-20 行开端并等候初始化完结
  • 获取运用程序的BundlePath,即运用程序的根路径
  1. executeDartCallback:
  • 在第 30 行创立 FlutterEngine 对象
  • 接下来在第 31 行,获取咱们之前在 SharedPreferences 中保存的**callbackDispatcher**句柄。查看句柄是否有效,然后运用 RawHandle 作为参数获取CallbackInfo(第 34 行)
  • 一旦咱们有了callbackInfo,咱们就运用 DartEngine.dartExecutor 在 Dart 端调用 callbackDispatcher 回调函数!图中的第 5 部分。

这将直接从本地代码在后台调用 Dart 侧的callbackDispatcher

总归,一旦手机重新发动,它将在后台发动 Dart 引擎。

如前所述,callbackDispatcher仅仅类似于 main()函数的辅佐进口。一旦发动,Dart API 和第三方插件就会可用,因此咱们可以在后台阻隔中运转任何 Dart 逻辑或与其他插件交互,而 UI 部分则处于中止状况!

例如,咱们自己的插件可以供给一个 EventChannel,为咱们挑选的任何事情供给事情流,此事情流可以在 callbackDispatcher 中被监听,并在 Dart 端后台获取事情。

需求阐明的是,以下部分与上述布景阻隔理论无关,这仅仅一个一般的插件功能,供给 Dart API 以从本地端发送和获取音讯。

仅有的区别是一旦它在后台被调用,咱们可以从回调调度程序与其交互。

让咱们看一些代码,然后我会解说它

Flutter 后台任务

Flutter 后台任务

Flutter 后台任务

上面的代码分为三个部分:

  1. 第一部分是插件 API,在代码最终供给了一个 API 来监听经过 EventChannel 传递的音讯,还有其他 API,例如发动监督设备充电器和电池状况。这些事情将经过 EventChannel 发送回来。
  2. 第二部分是插件本地端,在第 14 和 15 行,设置专类别的 StreamHandler。
  3. 最终是 PluginEventEmitter 类,这是将音讯发送到 Dart 端的类。

在 PluginEventEmitter 类的最终,界说了一个密封类,用于发送到 dart 的事情,在这个例子中有两个事情:BootComplete 和 BatteryLevelStatus

PluginEventEmitter 还会缓存事情,直到 dart 侧在 EventChannel 上有监听。

看看如何在 callbackDispatcher 中运用它:

Flutter 后台任务

在回调调度程序中(在发动完结后从本地调用),咱们现在注册到自己的插件事情,然后调用startPowerChangesListener并在侦听器中捕获事情。

所以,当咱们重启手机时,callbackDispatcher 将被调用,而且所有这些将在后台运转!只要进程是活动的(这是另一篇文章的主题..),事情将继续在后台传递给监听器!

示例项目源代码

请参阅我的github上的示例项目,其中包含完整的源代码!

这种办法有它的缺陷,需求至少打开一次运用程序以注册 callbackRawHandle 回调函数。

我必须说,在开端时,我依然发现这种办法不是最简单了解和完成的(隐涩难明),我希望在未来,Flutter 团队可以提出更简单的解决方案


太棒了!鼓舞自己坚持到底。我希望我为你投入的时间增加了一些价值。

本文原创 听蝉,假如觉得文章对你有协助,点赞、保藏、重视、评论,一键四连支撑,你的支撑便是我创造最大的动力。