无法理解“'Window_sendPlatformMessage' (4 arguments) cannot be found”错误的 Flutter Isolate 解决方法

Unable to understand Flutter Isolate workaround for "'Window_sendPlatformMessage' (4 arguments) cannot be found" error

我正在将一个非常简单的标签打印应用程序从 Swift 转换为 Flutter。此应用程序的一个主要组件是用于离线访问产品的本地缓存数据库。

虽然我可以在主线程上下载并缓存所有 12,000 多个产品,但对于如此繁重的操作来说,这是一个非常糟糕的解决方案,我真的很想避免这种情况,除非万不得已。即使作为最后的手段,如果后台线程无法用于将在线数据缓存到本地数据库等基本任务,我也很难说服我的雇主让我从 Swift 迁移到 Flutter。

因此,我一直在探索 Isolates。在此过程中,每当我在 Isolate 上尝试使用 sqflite and path_provider 缓存数据时,我 运行 就会进入错误 "'Window_sendPlatformMessage' (4 arguments) cannot be found"
现在我正在阅读 github and that Isolates don't support the use of plugins (possibly packages?) except in a wonky workaround kind of way. I've tried a plugin,当我尝试使用它时,它只会让我的应用程序崩溃,并显示一个非常神秘的堆栈跟踪,所以看起来解决方法是唯一的方法。

我大约一周前才开始使用 flutter 进行构建,大约两天前才开始使用 Isolates,所以我目前对所有内容的基本了解都很肤浅。在 flutter repo 上,有一个 comment 似乎概述了插件问题的解决方法,我希望它允许我使用 path_providersqflite 来缓存我正在处理的数据从我的网站获取 API.

更有经验的 Flutter 开发人员能否将此解释分解为婴儿咬伤?


编辑:

正如下面的回答所述,sqflite 已经是异步的,并且显然在与主线程不同的线程上运行,所以看起来我的具体情况将通过对我的飞镖使用计算来解决-纯 API 调用以获取 JSON 数据并使用常规 sqflite 进行存储。尽管如此,仍然需要初学者指南来解决不稳定的 Isolate 解决方法,因此我将问题悬而未决。

我不会准确回答你的问题(如何使用 isolate),但我建议此时不要使用 isolate。 sqflite 在后台线程中运行。是的,准备数据将在主线程中进行,因此绝对不要一次保存 12000 个项目,而是 downloading/saving 个项目 50 个(或 100 或 1000 个,具体取决于每个项目的大小 - 进行实验)交易(甚至更好的批次)应该没问题。

对于大量项目的密集计算(可能包括 json 解码下载的数据或准备地图以保存 sqflite),您可以使用 compute flutter 方法。

如果您需要显示完整列表(如果需要分页),您可能需要进行一些智能编码,但我认为没有什么可以阻止您做您需要做的事情 'in the background',即不阻塞 UI.

尝试解释

我也发现该线程中解决方法 post 的措辞非常混乱,我也无法真正理解它,但是 poster 似乎是什么建议是您使用 setMockMessageHandler 拦截来自 invokeMethod.

的数据

setMockMessageHandler 覆盖处理函数,否则处理函数会获取编码数据并将其传递给本机端,从而允许您使用自己的函数。因此,当您为通道设置模拟消息处理程序时,您可以 "hijack" 数据并通过 SendPort 将其传递回您的主隔离区。

一旦您在主隔离区收到消息,您就可以使用 BinaryMessenger.send 将数据发送到其预定目的地。如果您处于主要隔离区以外的隔离区,这一步就会中断,所以现在您已经绕过了这个问题。

一旦您将数据返回到主线程,您将其传递回隔离并使用您使用 BinaryMessages.setMessageHandler 创建的自定义消息处理程序对其进行解码。

解决方法包

如果您仍然需要它,我使用与您询问的解决方法非常相似的解决方法制作了一个程序包 (Isolate Handler)。

当然,任何依赖 setMockMessageHandler 的解决方法的缺点是您确实需要为其提供您将使用的频道名称。您可以通过查找设置 MethodChannel 的位置在源文件中找到这些频道名称。

似乎 sqflite 使用 com.tekartik.sqflite 而 path_provider 使用 plugins.flutter.io/path_provider,所以为了在带有 Isolate Handler 的隔离中使用它们,您需要执行以下操作:

IsolateHandler().spawn(
    entryPoint,
    channels: [
      MethodChannel('com.tekartik.sqflite'),
      MethodChannel('plugins.flutter.io/path_provider'),
    ]
);

void entryPoint(HandledIsolateContext context) {
  final messenger = HandledIsolate.initialize(context);
  // <Your previous isolate entry point here>
}

2019-09-04 更新:

我最终通过从本机端开始隔离来为自己解决了这个问题。我从官方 Flutter 插件 android_alarm_manager. While that plugin is specific to Android and that was enough for my specific project, it is possible to do it in iOS as well as shown in the now-archived location_background_plugin 以及下面引用的 Medium post 中获得灵感。

一位 Flutter 开发人员关于从本机端启动隔离的过程的非常全面(如果有点过分关注地理围栏)post available on Medium

post中采取的步骤摘要:

  1. (GitHub) 使用 PluginUtilities.getCallbackHandle
  2. 在 Dart 端设置一个引用回调调度程序的插件
  3. (GitHub) 创建回调调度程序本身,它:
    • 初始化您要访问的 MethodChannel。
    • 调用 WidgetsFlutterBinding.ensureInitialized() 来设置 MethodChannel 所需的内部状态。
    • 在 MethodChannel 上调用 setMethodCallHandler 以侦听来自插件本机端的后台事件,并在事件触发时调用回调。
    • 最终使用 invokeMethod.
    • 提醒本机端插件回调处理程序已准备好处理事件
  4. 实施本机代码以执行后台执行。
  5. 设置权限以允许作为服务在后台执行。
    • Android
      • (GitHub) 将 AndroidManifest.xml 的插件对象注册为服务。
      • (GitHub) 创建自定义 FlutterActivity.
      • (GitHub)更新AndroidManifest.xml中的application字段,设置为新创建的FlutterActivity.
    • iOS
      • (GitHub)。修改Info.plist请求权限
      • (GitHub) 从应用程序的 AppDelegate.
      • 设置对应用程序插件注册者的引用
  6. (GitHub) 在应用程序启动时在 Dart 端初始化插件(例如从 main。)

结论

这绝不是一个简单的解决方案,我仍然希望 Flutter 团队能提供更好的解决方案,但它是一个有效的解决方案,至少没有问题或意外行为。

我认为这是目前唯一可以投入生产的解决方案。