原文地址: 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 代码,需求履行几个步骤,在详细介绍代码前,我想用图表来展示它,然后解说它:
让咱们来看看这个图表并解说每个部分,如您所见,有六个首要步骤:
- 在 Dart 中界说一个无参 callbackDispatcher 大局函数,它将作为一个次进口点在后台阻隔中运转,并直接从本地端调用。
- 这部分也有三个步骤:
- 当运用程序初次发动时,将callbackDispatcher函数经过一个 api 的参数传递给插件
- 在插件中,运用 PluginUtils::toRawHandle 办法生成 callbackDispatcher的RawHandle,并经过 MethodChannel 将其转发到插件的本地端(2’)。
上述进程在 Dart 侧。
- 将 RawHandle 值(一个长整数)保存在本地端的耐久存储中,以便将来可以运用 — 2’’
long 值可以了解成 Dart 中的回调函数的内存地址,传给了本地端。
以上部分可以完结后,咱们将RawHandle保存在耐久存储中,当运用程序在后台醒来时,存储中 RawHandle 可用,并将用于直接从本地端调用callbackDispatcher。
-
当运用在后台唤醒时(例如:发动完结-后台进程初始化器),从耐久化存储中获取 RawHandle。
-
在后台初始化FlutterEngine和FlutterLoader
5.经过 RawHandle 获取FlutterCallbackInfo
-
运用DartExecutor和callbackInfo(来自第 5 步)调用executeDartCallback。这样就可以调用在 Dart 侧的callbackDispatcher函数了。
-
当 callbackDispatcher 被调用时,你可以在插件中注册其他事情并在后台的 Dart 侧处理它们,或许运用其他插件!
原生插件中可以经过 Dart 侧函数句柄调用 Dart 侧代码,也可以经过句柄运用其他插件。
如上所述,callbackDispatcher 仅仅 Dart 后台阻隔的进口点。
让咱们将上面的步骤分解为代码示例:
在 main.dart 中创立 callbackDispatcher 回调分发器
在上面的代码片段中,在 main.dart 中创立了appCallbackDispatcher 无参大局函数,它将成为 Dart 端的次进口点,可直接在本地调用,并在后台阻隔中运转。
了解:一个大局函数,运转在后台线程中。
注意 @pragma(‘vm:entry-point’) 注释是必须的,因为这个函数在 Dart 侧没有调用(它直接从本地调用),所以 AOT tree-shaking 编译器在生产构建时可能会将其删去。这个注释可以防止编译器删去这个函数。
让咱们转到插件侧看看它的姿态:
在插件 Dart 代码中获取 RawHandle
在上面的代码示例中,咱们可以看到一个经典的 Flutter 插件 Dart 端。这里感兴趣的是registerCallbackDispatcher API,它是从运用程序的main()函数中运用 callbackDispatcher作为参数调用的 API。然后,在第 13 到 15 行,运用PluginUtilities和 toRawHandle()办法获取其RawHandle。
然后,在第 17 行,运用 methodChannel 将其转发到本地端。在图表中,这一部分对应于步骤 2 和 2’。
将 RawHandle 保存到耐久性存储中(本地端)
让咱们切换到插件本机端,看看它如何处理 registerCallbackDispatcher api
上面的代码示例分为两个部分:
- 在第一部分中,咱们看到了 MyPlugin.kt 文件,运用 Kotlin 编写的本机插件。咱们对“registerCallbackDispatcher”api 感兴趣,它是从 Dart 端调用的,在第 18 行,获得了作为参数传递的 dispatcherHandle。在第 21 行将其保存在一个 SharedPreference 耐久存储中。
- 第二部分仅仅一个辅佐类,用于保存和读取SharedPreferences中的数据。
这个解说是针对咱们图表中的 2”。
从后台发动 Dart 引擎
这便是故事的中心部分,咱们想从后台发动 Dart 引擎和 VM,但不发动主阻隔和 UI 部分。 如图 3 中所示,它说的是后台进程初始化器。 为简单起见,我挑选了一个 BootComplete BroadcastReceiver,在手机重新发动时发动 Dart VM,但取决于您的运用程序要求,您可以决定何时发动 Dart VM 的正确机遇:
在上面的代码中,咱们看到一个典型的 BroadcastReceiver,它在手机完结发动时调用。从 onReceive 中,咱们开端并调用咱们的 dart 回调分配器,分为两个首要步骤(图中的 4 和 5)。
- initializeFlutterEngine method:
- 创立一个 FlutterLoader 对象并查看其是否已初始化
- 在第 19-20 行开端并等候初始化完结
- 获取运用程序的BundlePath,即运用程序的根路径
- 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 以从本地端发送和获取音讯。
仅有的区别是一旦它在后台被调用,咱们可以从回调调度程序与其交互。
让咱们看一些代码,然后我会解说它
上面的代码分为三个部分:
- 第一部分是插件 API,在代码最终供给了一个 API 来监听经过 EventChannel 传递的音讯,还有其他 API,例如发动监督设备充电器和电池状况。这些事情将经过 EventChannel 发送回来。
- 第二部分是插件本地端,在第 14 和 15 行,设置专类别的 StreamHandler。
- 最终是 PluginEventEmitter 类,这是将音讯发送到 Dart 端的类。
在 PluginEventEmitter 类的最终,界说了一个密封类,用于发送到 dart 的事情,在这个例子中有两个事情:BootComplete 和 BatteryLevelStatus
PluginEventEmitter 还会缓存事情,直到 dart 侧在 EventChannel 上有监听。
看看如何在 callbackDispatcher 中运用它:
在回调调度程序中(在发动完结后从本地调用),咱们现在注册到自己的插件事情,然后调用startPowerChangesListener并在侦听器中捕获事情。
所以,当咱们重启手机时,callbackDispatcher 将被调用,而且所有这些将在后台运转!只要进程是活动的(这是另一篇文章的主题..),事情将继续在后台传递给监听器!
示例项目源代码
请参阅我的github上的示例项目,其中包含完整的源代码!
这种办法有它的缺陷,需求至少打开一次运用程序以注册 callbackRawHandle 回调函数。
我必须说,在开端时,我依然发现这种办法不是最简单了解和完成的(隐涩难明),我希望在未来,Flutter 团队可以提出更简单的解决方案。
太棒了!鼓舞自己坚持到底。我希望我为你投入的时间增加了一些价值。
本文原创 听蝉,假如觉得文章对你有协助,点赞、保藏、重视、评论,一键四连支撑,你的支撑便是我创造最大的动力。