Flutter: 无法从另一个隔离调用平台通道方法

创建于 2018-01-05  ·  133评论  ·  资料来源: flutter/flutter

当我尝试从自定义生成的隔离中调用平台通道方法时,应用程序严重崩溃(在 iOS 和 Android 上)。 我试图弄清楚这是否是预期的。

如果不是,那么在某处可能值得一提。

无论如何,我认为这可能是一个很大的限制。 有没有办法能够从辅助隔离中调用平台插件?

P5 annoyance crowd gold engine framework passed first triage plugin crash new feature

最有用的评论

我有各种用例,例如:

  • 下载并解析大量数据(dart:convert 方法不是异步的),然后使用 sqflite 插件(使用平台绑定)将其存储到 sqlite db;
  • 在将数据提供给 ui 之前预取和解析数据;
  • 加密/解密和从/向文件读取/写入数据(加密的东西是通过方法通道完成的,以便使用平台安全库);

一般来说,您可能会使用声明一些公共方法的依赖项,您并不真正知道这些方法最终是否会使用特定于平台的代码来完成它们的工作(例如,我正在考虑 flutterfire); 恕我直言,使用它们应该是一个可以随时间变化的实现细节,而不是一成不变的东西。

您的解决方法目前似乎还可以,实施起来也相对轻松,因为数据已经以某种方式编码以传递到方法通道,因此也可以通过隔离端口轻松传递,但我的猜测是性能将不是最佳的.

但是,我在实现过程中遇到了一点困难:您能否提供一个简单方法的示例,说明在主隔离上从辅助隔离上调用静态方法的简单方法?

谢谢

所有133条评论

cc @mravn-google 进行分类

引擎代码在尝试从辅助隔离发送平台消息时取消引用 null 并崩溃。 我还没有确定确切的位置; 我得到了一块墓碑,但需要学习如何解释这样的事情。

作为一种(笨拙的)解决方法,辅助隔离可以要求主要隔离发送消息。

@sroddy我可以问一下您要使用辅助隔离来完成什么吗?

我有各种用例,例如:

  • 下载并解析大量数据(dart:convert 方法不是异步的),然后使用 sqflite 插件(使用平台绑定)将其存储到 sqlite db;
  • 在将数据提供给 ui 之前预取和解析数据;
  • 加密/解密和从/向文件读取/写入数据(加密的东西是通过方法通道完成的,以便使用平台安全库);

一般来说,您可能会使用声明一些公共方法的依赖项,您并不真正知道这些方法最终是否会使用特定于平台的代码来完成它们的工作(例如,我正在考虑 flutterfire); 恕我直言,使用它们应该是一个可以随时间变化的实现细节,而不是一成不变的东西。

您的解决方法目前似乎还可以,实施起来也相对轻松,因为数据已经以某种方式编码以传递到方法通道,因此也可以通过隔离端口轻松传递,但我的猜测是性能将不是最佳的.

但是,我在实现过程中遇到了一点困难:您能否提供一个简单方法的示例,说明在主隔离上从辅助隔离上调用静态方法的简单方法?

谢谢

@sroddy感谢您提供背景信息。

关于在主隔离上调用静态方法:不直接支持,必须使用端口实现。 这个包可以提供那个功能,例如here 。 不过自己没试过。

但是我们应该在这里寻找一个更通用的解决方案,当平台通道通信作为插件或其他库的实现的一部分完成时也可以工作。

我们还没有足够的钩子,但是如果我们可以暂时假设您有一组已知的通道名称,这些通道名称将用于辅助隔离而不是主隔离,那么您应该能够一般地为这些通道重新配置平台消息处理,透明地实现二进制消息的必要转发和两个隔离之间的回复。 插件永远不会知道其中的区别。

解决方案概述如下。

假设您在主隔离上设置端口 M1 和 R1,在辅助隔离上设置 M2、R2。 M 表示消息,R 表示回复。

  • 在您的辅助隔离中,对每个通道使用BinaryMessages.setMockMessageHandler将平台的消息转发到 M1(对使用 BinaryMessage.send 和该通道的插件透明)。 存储回复回调并使用处理程序配置 R2,该处理程序在收到消息回复时调用正确的处理程序。 使用转发到BinaryMessages.handlePlatformMessage的处理程序配置 M2。 在那里实现回复回调以将回复转发到 R1。
  • 在你的主要隔离区对称。 使用将平台的消息转发到BinaryMessages.send的处理程序配置 M1。 设置将回复从平台转发到 R2 的回复回调。 还为每个通道调用BinaryMessages.setMessageHandler来设置来自平台的传入消息的处理程序,该处理程序转发到 M2。 存储回复回调并使用处理程序配置 R1,该处理程序在收到来自辅助隔离的回复时调用正确的处理程序。

@Hixie我建议转到下一个里程碑。 似乎存在一种解决方法。 一个好的解决方案需要一些 API 设计工作和迭代。

我也遇到了同样的问题,请问有这个问题的消息吗?

@mravn-google 你对此有什么更新吗? 我遇到了同样的问题(我需要在平台端生成例如 RSA 2048 密钥对,这在旧设备或加密/解密数据上需要一段时间)。 我宁愿避免创建线程或服务,因为这会使我在平台方面的工作加倍,因为它将在 Android 和 iOS 平台上实现。 我们有什么简单的方法可以从 Dart 异步运行这些特定于平台的操作吗? 您的解决方法似乎很好,但是我似乎在实现它时遇到了一些问题,因为我对 Flutter 和 Dart 还很陌生(我不确定如何配置为 Isolate 转发消息的处理程序等等),抱歉关于那个。

我遇到了同样的问题。

我需要在 Android/iOS 端生成一个 PDF,这需要一段时间,如果我从那里运行调用,compute(...) 方法会使应用程序崩溃。

我遇到了这个问题,现在我被卡住了。

我想在后台隔离中从共享首选项中读取 accessToken,即使应用程序不在前台(主隔离可能没有运行,我不太确定)。

那么,有人可以告诉我从后台隔离中读取持久数据的解决方案吗?
我现在真的很麻烦。

/cc @bkonyi这似乎与您正在处理的事情有关。

我遇到了同样的问题。 我们的产品基于 CPU 密集型。 我们需要在隔离中使用平台方法。 你能告诉我们解决这个问题的另一种方法吗?

@Thomson-Tsui 我遇到了类似的问题,基于@bkonyi和他的 FlutterGeofencing 示例的工作提炼,我设法在我与 flutter_blue 和其他插件一起使用的隔离中得到了一些工作。 它未经测试,但请试一试。

我有同样的问题,我认为这很重要

有没有人解决过这个问题?

我需要在后台以不同的间隔播放音频。 最简单的方法是使用单独的 Isolate 管理音频,但如果没有此功能,将需要一些非常奇怪的端口工作

我假设当您从 dart 运行隔离时,本机库不会链接。

例如,当从 java 运行 Flutter 代码时,会调用这个方法:

   private native void nativeRunBundleAndSnapshotFromLibrary (
       long nativePlatformViewId,
       <strong i="7">@NonNull</strong> String [] prioritizedBundlePaths,
       <strong i="8">@Nullable</strong> String entrypointFunctionName,
       <strong i="9">@Nullable</strong> String pathToEntrypointFunction,
       <strong i="10">@NonNull</strong> AssetManager manager
   );

所有的魔法都发生在其中)

如果您创建多个 FlutterNativeView,并且在每次运行中:

  public void runFromBundle (FlutterRunArguments args)

然后我们得到一些“飞镖孤立”

我们也面临这个问题。 我们需要从所有依赖平台通道的背景隔离(例如位置、噪音等)中收集数据。 @mravn-google -- 有任何关于 API 更新的消息可以解决这个问题吗?

这在工具或媒体应用程序中确实是一个严重的问题。
平台线程不是一个简单的方法。

我也遇到过类似的问题,现在卡住了。

+1(我的项目也非常需要隔离中的平台通道)

我需要在背景中渲染图像。 它必须在隔离中完成,因为如果在主线程中完成,每个图像需要几秒钟并阻塞 ui。 我被困在dart:ui.PictureRecorder的第一次本机调用中,并假设每个图形调用(使用本机函数)也不起作用。 由于我在画布中使用了很多函数,因此使用回调端口会很痛苦。

高度赞赏解决方案。

+1 我遇到了这个问题

@mravn-google 似乎已停止在奥胡斯为 Google 工作。 我想知道 Google Flutter 团队中是否还有其他人正在研究这个问题。 IMO,这是认真使用 Flutter 构建弹性和健壮的 Flutter 应用程序的主要展示停止器。

也许@sethladd可以给出一个状态?

+1

对此有任何更新吗?

+1

有一天你遇到了一个你知道如何自己解决但只有等待是我成为 Flutter 工程师的最后希望。

我们需要对新的颤振/飞镖用户的解决方法进行更详细的解释。 两天前我才开始使用isolates,上周才开始使用flutter/dart,看起来我将不得不使用主线程来处理一些劳动密集型的任务,这使得学习flutter 成为一个糟糕的解决方案。 如果没有可用的多线程,我最好学习 Kotlin 并两次构建我的应用程序。 我希望你们能得到一些初学者可以理解的东西,我真的很想能够向我的雇主证明学习颤振是合理的,这样我就可以在工作中使用它。
如果不在这里,也许在 StackOverflow 上,我在这里发布了一个问题: https ://stackoverflow.com/q/57466952/6047611

+1 希望尽快修复

+1 我的项目需要隔离中的平台渠道。

我遇到了同样的问题,并使用类似于@mravn-google 发布的解决方法制作了一个包( Isolate Handler )。 但是,Isolate Handler 确实允许您使用来自主隔离的通道以及其他隔离。 它支持从隔离内部直接调用MethodChannel ,但目前不支持EventChannel流。 如果事实证明这是人们需要的东西,我会考虑增加对他们的支持。

解决方法的一个缺点是需要为隔离处理程序提供通道名称。 这在我的项目中不是问题,因为我自己编写本机代码,但否则您必须查看您正在使用的任何插件的源代码才能找到它们。

上面已经发布的另一个选项是FlutterIsolate ,它使用不同的方法,也值得研究。

希望 Flutter 开发人员能尽快为这个问题提供一个完整且适当的解决方案。

编辑:

我最终更干净地解决了我的问题,方法是从本机端开始隔离,而不是使用类似于 Flutter 第一方Android 警报管理器插件的方法。

我唯一需要做的就是将我的原生代码转换为插件,这样它们就可以在原生应用程序中注册,这是一个相对轻松的迁移。 如果您使用来自 pub.dev 的插件,它会更简单,因为它们应该可以无缝工作。

+1 这里有什么更新吗?

+1

我在这里注意到的有趣的事情:
几个插件似乎是在假设方法通道调用将消息从 Dart 传递到后台线程的情况下编写的; 相关地,有一些非常困惑的人遵循了那个 bg 地理围栏示例,现在想知道如何回到主线程......
该插件代码在执行短任务时不会导致“卡顿”,但默认情况下它们在平台线程上运行,在 android 上过去称为“UI 线程”; 这绝对会导致在高工作量期间放弃手势,我知道它也会阻止所有其他消息通道。
然后,您将受到插件作者工程的摆布; 只有当他们将繁重的工作转移到本机代码中的另一个线程时,您才会运行在方法通道上等待的代码; 这些消息通道必须在平台线程上运行。
虽然我确实理解好的 chinmaygarde在这里涉及的基本原理,但我同意:

  • 大多数平台 API 调用速度很快,通常需要在主线程上进行;
  • 在本机代码中考虑线程更有意义
  • 在框架方法覆盖中找到回到“主线程”的方法显然会让习惯于 Android Java 的人感到困惑

    • 我只能想象当他们第一次进行仅平台线程的 API 调用时,这将如何成倍增加。

我觉得由此产生的架构为插件作者打开了一个重要的反模式。 乍一看,我认为即使是 firebase ml 插件也使用平台线程进行处理。
所以我希望对从事繁重工作的人发出某种强烈的警告; 如果消息通道调用的返回时间超过约 20 毫秒,默认示例应用程序中可能会生成屏幕上的错误,可能更多的是在每个人的脸上; 老实说,无论什么能说服那些进行 db 调用 &c 的人。 去拿一个线程。

哎,天下没有免费的午餐,这是肯定的……

我最终使用 Java 和 Swift 使用本机平台线程实现了繁重的工作量。 没有找到解决方法。

@AndruByrne这确实是非常有趣的信息,我没有意识到这一点,就像事后看来一样明显。 我已经有一个与我的插件相关的问题,用户试图将其用作在后台执行繁重任务的一种方式,并且它锁定了 UI。

我同意应该有一个警告,我会在我的插件中添加一个。

+1 需要这个

不幸的是——Flutter 还没有准备好迎接黄金时段,因为它缺少一些非常基本但又非常关键的东西。 并非所有 Flutter 应用程序都可以通过简单的 async/await 摆脱困境。 对于繁重的工作,Isolates 是必不可少的,而不能允许来自另一个 Isolate 的平台通道方法调用的插件使它们变得毫无意义和多余。 Xamarin 从第一天起就将其内置到框架中 - 当然现在应该对解决方案进行投票。

+1 需要这个

+1

我也需要从另一个隔离中调用平台通道方法!

+1

+1

+1

+1 这是必须的

+1
我有一个必须调用的本机 SDK。 拥有像BackgroundFlutterMethodChannel这样的东西会是一件好事。

+1

+1

有没有人知道 _crashes badly_ 实际描述的是什么? 我正在研究实现本机库的概念验证,并且在通过方法通道来回发送消息时遇到了看门狗超时(0x8badf00d)和其他奇怪的错误。

我怀疑这是由从其他线程调用的某些方法 [和结果] 引起的,但我无法在查明确切问题方面取得任何进展。

+1

+1

+1

+1

+1 需要这个

+1也需要这个!

我认为这个问题至少应该作为 P2 优先。
我能想到的平台渠道的用途是

  • 调用特定于平台的 API
  • 在后台运行特定于平台的繁重任务并在完成时报告

这是一个主要的障碍。

flutter_downloader插件处理后台隔离代码。 我认为值得一看。

我希望这个问题在即将发布的版本中得到解决,但这个错误仍然存​​在于 1.12 版本中!

我们的客户端代码主要位于通道的另一端,并且是用 kotlin 编写的,并且该应用程序几乎会卡在每个单独的 http 连接上

Gooooooooooooooogle?!!!!!!!!!!!!!!!?!?!?!?

这个插件可能有帮助flutter_isolate

+1 我们需要这个...来吧

+1

对我来说最大的问题是,Dart 和本机平台之间的通信发生在主线程上——如果不滞后 UI 或将繁琐的代码写入页面数据,就不可能发送大量数据。

+1 需要这个

将繁琐的代码写入页面数据。

@lukaszciastko将代码写入页面数据是什么意思。 发送大量数据时,有没有办法解决 UI 滞后的问题?

@YaredTaddese

根据您的 ISOLATE 所做工作的复杂性以及您的 UI 和 ISOLATE 之间的相互通信 - 在您的 UI 代码中,可以通过在调用 ISOLATE 之前预先构建 ISOLATE 完成其工作所需的数据来解决此问题。 我在当前的 Flutter 应用程序中有丰富的 PDF 生成功能——用户可以选择多个 PDF 来生成。

每个可以生成 1 到 10,000 页。 我的 UI 是响应式的,当每个作业完成时,都会构建一个显示报告的 TAB - 当作业通过 TOAST 完成并且 UI 不会停止时,用户会收到通知 - 用户不知道后台有服务繁忙生成丰富的PDF。

请注意,我的 PDF 生成是 ISOLATE。 在 ISOLATE 中——我还需要从 CLOUD FIRESTORE 下载图像并嵌入 PDF——这一切都是无缝进行的。

@MsXam ,但我无法从另一个隔离中调用特定于平台的函数...这使我在主隔离中调用特定于平台的函数...这导致 UI 滞后

我有同样的问题,我认为这很重要

@YaredTaddese

根据您的 ISOLATE 所做工作的复杂性以及您的 UI 和 ISOLATE 之间的相互通信 - 在您的 UI 代码中,可以通过在调用 ISOLATE 之前预先构建 ISOLATE 完成其工作所需的数据来解决此问题。 我在当前的 Flutter 应用程序中有丰富的 PDF 生成功能——用户可以选择多个 PDF 来生成。

每个可以生成 1 到 10,000 页。 我的 UI 是响应式的,当每个作业完成时,都会构建一个显示报告的 TAB - 当作业通过 TOAST 完成并且 UI 不会停止时,用户会收到通知 - 用户不知道后台有服务繁忙生成丰富的PDF。

请注意,我的 PDF 生成是 ISOLATE。 在 ISOLATE 中——我还需要从 CLOUD FIRESTORE 下载图像并嵌入 PDF——这一切都是无缝进行的。

您是在调用特定于平台的代码吗?

有没有人有一个从隔离调用平台通道的解决方法?它破坏了从 UI 线程这样做的想法。

我被困在同一件事上。

我正在使用 kotlin 代码和 getStream.io API 调用方法通道,但这太慢了,而且我的 Flutter UI 冻结,我想向它添加计算,但我遇到了同样的错误。

我怎样才能解决这个问题?

我也遇到了这个。 我正在制作一个新应用程序的原型,实际上这个问题对我来说可能是个大问题,并导致我研究其他非 Flutter 解决方案,这会让我感到难过。

正如对该问题的大量回复所表明的那样,这是一个主要问题,并且几乎击败了 Flutter 应用程序上下文中隔离的主要用例。

我也遇到了这个。 我正在制作一个新应用程序的原型,实际上这个问题对我来说可能是个大问题,并导致我研究其他非 Flutter 解决方案,这会让我感到难过。

正如对该问题的大量回复所表明的那样,这是一个主要问题,并且几乎击败了 Flutter 应用程序上下文中隔离的主要用例。

是的,这是个大问题,需要尽快解决

我已经学习了 Flutter 10 天,到目前为止我很喜欢它,但是经过一段时间的实验,你开始遇到问题,看到了缺点,这个问题,从 2018 年 1 月 5 日开始,oof...

请修复此错误。

希望尽快支持。

@ jpsarda@klaszlo8207不要屏住呼吸。 的,直到今天,有一个解决问题的方法: https://pub.dev/packages/isolate_handler。 程序员今天不得不拔掉它,因为 Flutter 中最近的一次更改(仅几天前)使得无法再遵循这条路线。

但是,您必须知道,这并不真正意味着并行性。 它可能看起来是这样,并且看起来在移动应用程序中很重要,但平台代码无论如何都在主线程上运行,因此将一个长任务从隔离区委托回主线程并没有真正完成任何事情。

每当您使用异步函数时,默认情况下让 dart 处理多线程会很好。

@spiderion Async 从来都不是多线程的,就像在许多其他平台中一样。 不要误解我,我不会说平台通道限制不是一个大问题,因为它是(我的应用程序一直在使用我昨天提到的插件,现在我必须寻找解决方法),但是 async/从第一天开始,await 就与多线程无关。

@zoechi我们很想知道这个问题是否有任何更新,或者是否有任何修复它的计划?
谢谢你。

@spiderion他们无法真正修复它,它遵循隔离的工作方式。 他们背后没有 UI 引擎,因此没有平台通道通信。 但是,有两个插件可以帮助您:

  • https://pub.dev/packages/flutter_isolate提供了一个替代隔离,它可以与插件通信,因为它创建了自己的 UI 支持(没有你看到或必须处理的,只是技术上的),

  • https://pub.dev/packages/isolate_handler我们刚刚修改为依赖上面的包,因为最近的 Flutter 更改使得它以前使用的方式变得不可能。 使用这个包而不是flutter_isolate本身的优点是它增加了处理能力,你可以启动几个隔离,跟踪它们,你不必在隔离和主之间建立自己的通信线程(您必须手动处理原始库存IsolateFlutterIsolate ),因为它被抽象出来并且随时可用。

我使用第二个非常成功。 实际上,我已经使用它很长一段时间了,当 Flutter 发生重大变化时,我帮助它的程序员继续使用新的解决方案,只是因为我的应用程序也坏了。 :-)

所以,我认为期望核心解决方案是不合理的,尤其是大多数用例不需要平台通道通信,所以支持 UI 引擎并不是他们想要添加到每个隔离的东西。 当您需要额外的功能时,只需使用现有的插件。

@deakjahn感谢您的快速回答。

您提供的解决方案看起来非常有用。 但是,我不知道它是否可以解决我的问题。 我目前在应用程序中遇到用户创建故事的情况,可以说类似于 Instagram。 在这种情况下,该应用程序需要在 Firebase 存储上上传视频和文件列表,这可能需要很长时间才能上传(如果连接速度很慢,大约需要 15 分钟),然后在文件上传后,应用程序需要创建Firebase 云 Firestore 上的文档。 我遇到的问题是,如果用户在文件上传时终止了应用程序,那么完整的任务就没有完全执行。

这不是隔离器的真正责任,它只是按照您的指示去做。 据我所知,Firebase 可以恢复停止的卸载,尽管我自己从未使用过它。

我正在尝试运行 getBatteryLevel 示例:
https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-java-tab
每次我尝试在 android 设备上运行它时它都会崩溃。 有人可以帮忙吗? 我什至不知道如何检查错误,因为没有日志,它只是继续在无限循环中运行,而且我使用的是完全相同的代码。

将此问题标记为 P5 ??? 🥵

请查看之前的评论。 这似乎不太可能在框架中进行更改,因为它需要大多数隔离不需要的机制,因此添加该开销没有意义。 对于您确实需要它的情况,有一些软件包可以解决它。

用户不关心机制; 这有多难。 他们只需要结果。 这可能是不可能做到的。 但是如果用户需要它,Flutter 应该提供它......

我了解该实现存在局限性,并且可能需要大量时间才能将此功能添加到现有隔离或创建具有此功能的新 API。 我只是认为这应该比 P5 具有更高的优先级。

此外,我阅读了您的所有评论,我觉得您只是在宣传您创建的软件包。

不,不是我创造了它,但几周前我确实接受了它的所有权,是的。 我不认为这在这个线程中是保密的。 除此之外,我还提到了两个包(在一篇文章中,实际上只有一次),它们都能够提供你想要的东西,我与第二个包没有任何关系,除了依赖它迄今为止所做的出色工作。

如果您阅读它,我自己首先使用这些的唯一原因是我需要您要求的相同功能。 我的应用程序已经依赖它一年多了,没有任何问题。 此外,与偶尔使用隔离的开发人员相比,对隔离的内部工作更加熟悉(加上我开始在 Flutter Web 中开发他们的对应部分),这让我认为我写的是真的:这个功能不会纳入框架,因为有不少反对将其纳入的论据。 不是因为这会花费大量时间,根本不是。 这将花费相对较少的时间。 仅仅因为它会给所有隔离增加后端复杂性,而不是其中只有少数人实际需要和使用。

@deakjahn感谢这些提示,每当我在本机端收到通知时,使用isolate_handler 会帮助我调用一个方法并将数据发送到飞镖端吗? (我正在使用 twilio 原生 sdks 并在原生端接收推送通知)

如果这意味着您有一个用于通过平台通道进行通信的 patform 插件,那么是的,这两个包中的任何一个都会有所帮助。 如果它在常规代码中工作,那么这些包也将使其在隔离中工作。

@deakjahn感谢您的快速回复! 我的问题是我无法使用常规平台通道来做到这一点,我不确定,但我认为 twilio sdk 处理在单独的隔离中而不是在主线程上监听推送通知,我认为这是我的问题。 我想知道如果我每次使用两个包之一收到通知时调用该方法是否会起作用?非常感谢。 (如果不可能,我正在考虑切换到本机应用程序)

我什至不知道 Twilio 是什么(好吧,我用谷歌搜索了它:-)),所以我真的不能确定。 但无论如何,如果你想连接任何本机代码,无论如何你都必须使用平台通道。 要么您使用某人为使用此 Twilio 而制作的现有插件,要么您自己创作(在这种情况下,您要么创建几乎相同的插件,只是不要发布它并在本地引用它),或者您只需复制相关插件代码到您自己的应用程序中(它不会是外部依赖项,但除此之外,它实际上将具有相同的代码,无论如何)。

而且,如果您确实有一个平台通道,那么前面的答案适用:如果您已经可以从常规 Flutter 应用程序的主线程中使用平台通道和插件,那么您可以使用任何两个包。

所以这个问题自 2018 年 1 月 5 日起就存在,并且仍然无法通过额外的努力。 提供的解决方案很好 - 但桌面上还没有任何工作。 因此,对于任何想尝试 Flutter 在桌面上的工作情况并查看未来产品是否有可能的人来说,都会完全卡在这里。
几乎任何 App 都需要隔离,因为总是有一些巨大的计算量,这么重要的 Api 设计缺陷怎么可能没有发生呢?

我在我的插件System Alert Window中为此添加了一条出路

这里提到了解决方案隔离通信
尽管该示例提供了特定于系统警报窗口插件的讨论,但它可以很容易地复制到其他插件。 而且它不需要其他花哨的插件来使它工作!

这仍然是一个很大的问题。 这意味着无法直接从在后台/前台运行的隔离中调用插件,因为您必须向应用范围发送消息才能对插件执行任何操作。 因此,如果您的应用程序没有运行,则无法使用背景中的插件做任何事情。

因此,例如,如果您想在应用程序关闭时在 FCM 插件的 backgroundMessageHandler 中使用共享首选项做任何事情,它将抛出 MissingPluginException。

或者,如果您不想使用系统警报窗口打开应用程序。 警报窗口在不同的隔离区上运行。 所以你必须在你的应用范围内运行代码。 这是有问题的,因为该应用程序当前已关闭。

可能还有许多其他的场景,这是一个巨大的问题。

@michael-ottink 您描述的场景正是不应该出现问题的场景。 在后台执行代码的插件通常会使用自己的插件注册表实例化一个全新的 FlutterEngine,并且在其中运行的隔离将能够通过平台通道直接与这些插件通信。

顺便说一句,这意味着如果您需要隔离来使用插件,简单的方法就是将该隔离包装在 FlutterEngine 中,它将神奇地继承这些权力。 这就是 flutter_isolate 包在 Android 和 iOS 上为您所做的(尽管我同意最好有一个适当的修复而不是解决方法。今天可用的解决方法有瓶颈、开销或其他可能维护不足或缺乏跨所有平台。)

@ryanheise所以 flutter_isolate 对我有用吗? 我如何无法从文档中真正分辨出来。 我需要做什么才能使其适用于我的 FCM onBackgroundMessage? 所以我需要在我的 onBackgroundMessageHandler 中生成一个新的隔离? 但是我不能在那里使用插件,那么我将如何使用 flutter_isolate 呢?

您的问题是 firebase_messaging 很长时间没有更新,并且远远落后于最新的后台执行 API。 如果他们更新了他们的插件,您将不再有这个问题。 再次澄清一下,您描述的那种场景正是不应该出现问题的场景,因为如果正确实施,后台隔离应该已经可以访问插件。 firebase_messaging 插件不是根据最新的 API 实现的,这就是它不适合你的原因。 您可以提交该项目的错误报告。

我急需这方面的帮助。 有没有办法我可以做到这一点。 我明天有我的第一个严肃的dealine,我就是想不通。

尽一切努力确保正在加载插件。 我不知道您是否按照 README 中关于如何启用后台消息的说明进行操作,但您需要创建一个自定义 Application 类,该类挂钩到插件后台 FlutterNativeView 的初始化。 如果您还没有这样做,则没有任何插件可供您使用。 如果这仍然不起作用,您可以尝试将您的项目降级到旧的 v1 风格的插件架构(并冒着破坏其他需要 v2 的插件的风险)。

我像这样设置我的 Application.kt

import `in`.jvapps.system_alert_window.SystemAlertWindowPlugin
import io.flutter.app.FlutterApplication
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback
import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService

import android.os.Build
import android.app.NotificationManager
import android.app.NotificationChannel

public class Application: FlutterApplication(), PluginRegistrantCallback {

   override fun onCreate() {
     super.onCreate()
     FlutterFirebaseMessagingService.setPluginRegistrant(this)
     createNotificationChannels()
     SystemAlertWindowPlugin.setPluginRegistrant(this)
   }

   override fun registerWith(registry: PluginRegistry) {
     FirebaseCloudMessagingPluginRegistrant.registerWith(registry)
     SystemAlertWindowPlugin.registerWith(registry.registrarFor("in.jvapps.system_alert_window"))
   }

   fun createNotificationChannels() {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = "groupChannel"
        val descriptionText = "This is the group channel"
        val importance = NotificationManager.IMPORTANCE_HIGH
        val mChannel = NotificationChannel("59054", name, importance)
        mChannel.description = descriptionText
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(mChannel)
    }
  }
}

最好将其发布在 firebase_messaging 问题页面上,因为它与此问题无关。

我可以制作一个 PR,它将为“SchedulerBinding”提供一个新的“spawnIsolate”方法。

然后就可以在这个隔离中调用平台方法。

这只会在您需要调用平台方法并获得响应时为您提供帮助。

import 'dart:async';
import 'dart:isolate';

import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
import 'package:path_provider/path_provider.dart';

Future<void> _test(SendPort sendPort) async {
  final dir = await getTemporaryDirectory();
  sendPort.send(dir);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final Completer<Object> completer = Completer<Object>();
  final RawReceivePort receivePort = RawReceivePort(completer.complete);

  final Isolate isolate = await ServicesBinding.instance.spawnIsolate(
    _test,
    receivePort.sendPort,
  );

  print(await completer.future);

  receivePort.close();
  isolate.kill();
}

日志

Performing hot restart...
Syncing files to device Pixel 4...
Restarted application in 722ms.
I/flutter (11705): Directory: '/data/user/0/com.example.bug/cache'

我可以制作一个 PR,它将为“SchedulerBinding”提供一个新的“spawnIsolate”方法。

然后就可以在这个隔离中调用平台方法。

这只会在您需要调用平台方法并获得响应时为您提供帮助。

SchedulerBinding.instance.spawnIsolate

有机会测试一下吗? 如果它符合我的需要等..

@奈利克
替换此文件(版本 1.17.5 的实际值)


绑定.dart

```dart // 版权所有 2014 The Flutter 作者。 版权所有。
// 此源代码的使用受 BSD 风格的许可证的约束,该许可证可以
// 在许可证文件中找到。

导入“飞镖:异步”;
导入“飞镖:隔离”;
导入“飞镖:typed_data ”;
导入' dart:ui '作为ui;

导入'包:flutter/foundation.dart ';

导入'asset_bundle.dart';
导入'binary_messenger.dart';
导入'system_channels.dart';

/// 监听平台消息并将它们定向到 [defaultBinaryMessenger]。
///
/// [ServicesBinding] 还注册了一个 [LicenseEntryCollector],它公开
/// 存储在资产根目录下的LICENSE文件中的许可证
/// 捆绑,并实现ext.flutter.evict服务扩展(参见
/// [驱逐])。
BindingBase 上的 mixin ServicesBinding {
@覆盖
无效初始化实例(){
super.initInstances();
_instance = 这个;
_defaultBinaryMessenger = createBinaryMessenger();
window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
初始化许可证();
SystemChannels.system.setMessageHandler(handleSystemMessage);
}

/// 当前的[ServicesBinding],如果已经创建的话。
静态服务绑定获取实例 => _instance;
静态服务绑定_instance;

/// [BinaryMessenger] 的默认实例。
///
/// 这用于从应用程序向平台发送消息,并且
/// 跟踪每个通道上注册了哪些处理程序,以便
/// 它可以将传入的消息分派给注册的处理程序。
BinaryMessenger 获取 defaultBinaryMessenger => _defaultBinaryMessenger;
BinaryMessenger _defaultBinaryMessenger;

/// 创建一个可用于发送的默认 [BinaryMessenger] 实例
/// 平台消息。
@受保护
BinaryMessenger createBinaryMessenger() {
return const _DefaultBinaryMessenger._();
}

/// 处理程序调用在 [SystemChannels.system] 上收到的消息
/// 消息通道。
///
/// 其他绑定可能会覆盖它以响应传入的系统消息。
@受保护
@mustCallSuper
未来处理系统消息(对象系统消息)异步{}

/// 将相关许可证添加到 [LicenseRegistry]。
///
/// 默认情况下,[ServicesBinding] 的 [initLicenses] 实现添加
/// flutter工具在编译期间收集的所有许可证。
@受保护
@mustCallSuper
无效的initLicenses(){
LicenseRegistry.addLicense(_addLicenses);
}

溪流_addLicenses() 异步* {
// 我们在这里使用定时器(而不是调度程序绑定中的 scheduleTask)
// 因为服务层不能使用调度器绑定(调度器
// 绑定使用服务层来管理其生命周期事件)。 计时器
// 无论如何都是 scheduleTask 在后台使用的。 唯一的区别是
// 这些将只是接下来运行,而不是相对于优先级
// 可能正在运行的其他任务。 在这里使用_something_来打破
// 这分成两部分很重要,因为隔离需要一段时间来复制
// 此刻的数据,如果我们在同一个事件循环中接收到数据
// 当我们将数据发送到下一个隔离时,我们肯定是迭代
// 将丢失帧。 另一个解决方案是让工作全部完成
// 发生在一个隔离区,我们最终可能会去那里,但首先我们是
// 看看隔离通信是否可以变得更便宜。
// 见: https ://github.com/dart-lang/sdk/issues/31959
// https://github.com/dart-lang/sdk/issues/31960
// TODO(ianh): 一旦这些错误得到修复,就消除这种复杂性。
最终完成者rawLicenses = 完成者();
Timer.run(() 异步 {
rawLicenses.complete(rootBundle.loadString('LICENSE', cache: false));
});
等待 rawLicenses.future;
最终完成者> parsedLicenses = 完成者>();
Timer.run(() 异步 {
parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
});
等待 parsedLicenses.future;
产量* 流.fromIterable(等待已解析许可证.future);
}

// 这是在上面 _addLicenses 创建的另一个隔离中运行的。
静态列表_parseLicenses(String rawLicenses) {
最终字符串 _licenseSeparator = '\n' + ('-' * 80) + '\n';
最终名单结果 =[];
最终名单许可证 = rawLicenses.split(_licenseSeparator);
for(许可证中的最终字符串许可证){
final int split = license.indexOf('\n\n');
如果(拆分> = 0){
结果.add(LicenseEntryWithLineBreaks(
license.substring(0, split).split('\n'),
license.substring(split + 2),
));
} 别的 {
结果.add(LicenseEntryWithLineBreaks(const[], 执照));
}
}
返回结果;
}

@覆盖
无效的 initServiceExtensions() {
super.initServiceExtensions();

assert(() {
  registerStringServiceExtension(
    // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
    // the rootBundle cache and cause the entire image cache to be cleared.
    // This is used by hot reload mode to clear out the cache of resources
    // that have changed.
    name: 'evict',
    getter: () async => '',
    setter: (String value) async {
      evict(value);
    },
  );
  return true;
}());

}

/// 响应ext.flutter.evict服务扩展调用。
///
/// 这是由flutter工具在热重载期间使用的,以便任何图像
/// 从缓存中清除磁盘上已更改的内容。
@受保护
@mustCallSuper
无效驱逐(字符串资产){
rootBundle.evict(资产);
}

未来产卵隔离(
未来或入口点(T 消息),
T 消息,{
布尔暂停 = 假,
布尔错误是致命的,
SendPort onExit,
SendPort onError,
字符串调试名称,
}) {
断言(
_isMainIsolate,
'不能进行多级分离',
);

final RawReceivePort messageReceiver = RawReceivePort(
  (Object receivedMessage) async {
    if (receivedMessage is SendPort) {
      receivedMessage.send(
        _IsolateStarter<T>(
          ui.PluginUtilities.getCallbackHandle(entryPoint),
          message,
        ),
      );
    } else if (receivedMessage is _Message) {
      final ByteData result = await defaultBinaryMessenger.send(
        receivedMessage.channel,
        receivedMessage.message,
      );
      receivedMessage.sendPort.send(result);
    }
  },
);
RawReceivePort onExitReceiver;
onExitReceiver = RawReceivePort(
  (Object message) {
    onExit?.send(message);

    onExitReceiver.close();
    messageReceiver.close();
  },
);

return Isolate.spawn(
  _startIsolate,
  messageReceiver.sendPort,
  paused: paused,
  errorsAreFatal: true,
  onExit: onExitReceiver.sendPort,
  onError: onError,
  debugName: debugName,
);

}
}

未来_startIsolate(发送端口发送端口)异步 {
_sendPortToMainIsolate = 发送端口;
_IsolateBinding();

最终完成者<_IsolateStarter> 完成者 =
完成者<_IsolateStarter>();

最终 RawReceivePort 接收端口 = RawReceivePort(
(对象隔离启动器){
断言(isolateStarter 是 _IsolateStarter);
completer.complete(isolateStarter as _IsolateStarter);
},
);

sendPort.send(receivePort.sendPort);

最终_IsolateStarter隔离启动器 = 等待完成者。未来;

接收端口.close();

最终函数函数 =
ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

等待函数(isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

布尔获取 _isMainIsolate => _sendPortToMainIsolate == null;

_IsolateStarter 类{
_IsolateStarter(this.callbackHandle, this.message);

最终的 ui.CallbackHandle 回调句柄;
最后的 T 消息;
}

类_消息{
_信息(
这个.channel,
这条信息,
this.sendPort,
);

最终字符串通道;
最终的 ByteData 消息;
最终的发送端口发送端口;
}

_IsolateBinding 类使用 ServicesBinding {} 扩展 BindingBase

/// [BinaryMessenger] 的默认实现。
///
/// 这个信使将消息从应用端发送到平台端,然后
/// 将来自平台端的传入消息分派到适当的
/// 处理程序。
类 _DefaultBinaryMessenger 扩展 BinaryMessenger {
const _DefaultBinaryMessenger._();

// 来自平台插件的传入消息的处理程序。
// 这是静态的,所以这个类可以有一个 const 构造函数。
静态最终地图_handlers =
{};

// 拦截和响应传出消息的模拟处理程序。
// 这是静态的,所以这个类可以有一个 const 构造函数。
静态最终地图_mockHandlers =
{};

未来_sendPlatformMessage(String channel, ByteData message) {
最终完成者完成者 = 完成者();

if (_isMainIsolate) {
  // ui.window is accessed directly instead of using ServicesBinding.instance.window
  // because this method might be invoked before any binding is initialized.
  // This issue was reported in #27541. It is not ideal to statically access
  // ui.window because the Window may be dependency injected elsewhere with
  // a different instance. However, static access at this location seems to be
  // the least bad option.
  ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
    try {
      completer.complete(reply);
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context:
            ErrorDescription('during a platform message response callback'),
      ));
    }
  });
} else {
  RawReceivePort receivePort;
  receivePort = RawReceivePort(
    (Object message) async {
      assert(message is ByteData);
      completer.complete(message as ByteData);
      receivePort.close();
    },
  );
  _sendPortToMainIsolate.send(
    _Message(channel, message, receivePort.sendPort),
  );
}

return completer.future;

}

@覆盖
未来处理平台消息(
字符串通道,
字节数据数据,
ui.PlatformMessageResponseCallback 回调,
) 异步 {
字节数据响应;
尝试 {
最终 MessageHandler 处理程序 = _handlers[channel];
如果(处理程序!= null){
响应 = 等待处理程序(数据);
} 别的 {
ui.channelBuffers.push(通道,数据,回调);
回调=空;
}
} 捕捉(异常,堆栈){
FlutterError.reportError(FlutterErrorDetails(
异常:异常,
堆栈:堆栈,
图书馆:'服务图书馆',
context: ErrorDescription('在平台消息回调期间'),
));
} 最后 {
如果(回调!= null){
回调(响应);
}
}
}

@覆盖
未来发送(字符串通道,字节数据消息){
最终 MessageHandler 处理程序 = _mockHandlers[channel];
如果(处理程序!= null)
返回处理程序(消息);
返回_sendPlatformMessage(频道,消息);
}

@覆盖
void setMessageHandler(字符串通道,MessageHandler 处理程序){
如果(处理程序 == 空)
_handlers.remove(通道);
别的
_handlers[channel] = 处理程序;
ui.channelBuffers.drain(channel, (ByteData 数据, ui.PlatformMessageResponseCallback 回调) async {
等待handlePlatformMessage(通道,数据,回调);
});
}

@覆盖
void setMockMessageHandler(字符串通道,MessageHandler 处理程序){
如果(处理程序 == 空)
_mockHandlers.remove(通道);
别的
_mockHandlers[channel] = 处理程序;
}
}
```

我有颤振 1.21 所以我尝试更新所有代码以进行编译。
现在还有一个问题,因为 _IsolateBinding 缺少很多 BindingBase 方法的实现......

我喜欢

Error: The non-abstract class '_IsolateBinding' is missing implementations for these members:
 - BindingBase with ServicesBinding.SchedulerBinding.addPersistentFrameCallback

大约 30 次。

最后控制台打印

/C:/flutter/packages/flutter/lib/src/services/binding.dart:341:7: Error: 'BindingBase' doesn't implement 'SchedulerBinding' so it can't be used with 'ServicesBinding'.
 - 'BindingBase' is from 'package:flutter/src/foundation/binding.dart' ('/C:/flutter/packages/flutter/lib/src/foundation/binding.dart').
 - 'SchedulerBinding' is from 'package:flutter/src/scheduler/binding.dart' ('/C:/flutter/packages/flutter/lib/src/scheduler/binding.dart').
 - 'ServicesBinding' is from 'package:flutter/src/services/binding.dart' ('/C:/flutter/packages/flutter/lib/src/services/binding.dart').
class _IsolateBinding extends BindingBase with ServicesBinding {}

我不是 100% 确定 $# _startIsolate _IsolateBinding()的调用是做什么的,但没有我得到常见的ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.错误。

我试图解决这个问题,但我认为我对 mixins 的了解还不是很好。
知道如何解决吗?


新文件看起来像这样(到目前为止)

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// <strong i="24">@dart</strong> = 2.8

import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';

import 'asset_bundle.dart';
import 'binary_messenger.dart';
import 'restoration.dart';
import 'system_channels.dart';

/// Listens for platform messages and directs them to the [defaultBinaryMessenger].
///
/// The [ServicesBinding] also registers a [LicenseEntryCollector] that exposes
/// the licenses found in the `LICENSE` file stored at the root of the asset
/// bundle, and implements the `ext.flutter.evict` service extension (see
/// [evict]).
mixin ServicesBinding on BindingBase, SchedulerBinding {
  <strong i="25">@override</strong>
  void initInstances() {
    super.initInstances();
    _instance = this;
    _defaultBinaryMessenger = createBinaryMessenger();
    _restorationManager = createRestorationManager();
    window.onPlatformMessage = defaultBinaryMessenger.handlePlatformMessage;
    initLicenses();
    SystemChannels.system.setMessageHandler(handleSystemMessage);
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
  }

  /// The current [ServicesBinding], if one has been created.
  static ServicesBinding get instance => _instance;
  static ServicesBinding _instance;

  /// The default instance of [BinaryMessenger].
  ///
  /// This is used to send messages from the application to the platform, and
  /// keeps track of which handlers have been registered on each channel so
  /// it may dispatch incoming messages to the registered handler.
  BinaryMessenger get defaultBinaryMessenger => _defaultBinaryMessenger;
  BinaryMessenger _defaultBinaryMessenger;

  /// Creates a default [BinaryMessenger] instance that can be used for sending
  /// platform messages.
  <strong i="26">@protected</strong>
  BinaryMessenger createBinaryMessenger() {
    return const _DefaultBinaryMessenger._();
  }

  /// Called when the operating system notifies the application of a memory
  /// pressure situation.
  ///
  /// This method exposes the `memoryPressure` notification from
  /// [SystemChannels.system].
  <strong i="27">@protected</strong>
  <strong i="28">@mustCallSuper</strong>
  void handleMemoryPressure() { }

  /// Handler called for messages received on the [SystemChannels.system]
  /// message channel.
  ///
  /// Other bindings may override this to respond to incoming system messages.
  <strong i="29">@protected</strong>
  <strong i="30">@mustCallSuper</strong>
  Future<void> handleSystemMessage(Object systemMessage) async {
    final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
    final String type = message['type'] as String;
    switch (type) {
      case 'memoryPressure':
        handleMemoryPressure();
        break;
    }
    return;
  }

  /// Adds relevant licenses to the [LicenseRegistry].
  ///
  /// By default, the [ServicesBinding]'s implementation of [initLicenses] adds
  /// all the licenses collected by the `flutter` tool during compilation.
  <strong i="31">@protected</strong>
  <strong i="32">@mustCallSuper</strong>
  void initLicenses() {
    LicenseRegistry.addLicense(_addLicenses);
  }

  Stream<LicenseEntry> _addLicenses() async* {
    // We use timers here (rather than scheduleTask from the scheduler binding)
    // because the services layer can't use the scheduler binding (the scheduler
    // binding uses the services layer to manage its lifecycle events). Timers
    // are what scheduleTask uses under the hood anyway. The only difference is
    // that these will just run next, instead of being prioritized relative to
    // the other tasks that might be running. Using _something_ here to break
    // this into two parts is important because isolates take a while to copy
    // data at the moment, and if we receive the data in the same event loop
    // iteration as we send the data to the next isolate, we are definitely
    // going to miss frames. Another solution would be to have the work all
    // happen in one isolate, and we may go there eventually, but first we are
    // going to see if isolate communication can be made cheaper.
    // See: https://github.com/dart-lang/sdk/issues/31959
    //      https://github.com/dart-lang/sdk/issues/31960
    // TODO(ianh): Remove this complexity once these bugs are fixed.
    final Completer<String> rawLicenses = Completer<String>();
    scheduleTask(() async {
      rawLicenses.complete(await rootBundle.loadString('NOTICES', cache: false));
    }, Priority.animation);
    await rawLicenses.future;
    final Completer<List<LicenseEntry>> parsedLicenses = Completer<List<LicenseEntry>>();
    scheduleTask(() async {
      parsedLicenses.complete(compute(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses'));
    }, Priority.animation);
    await parsedLicenses.future;
    yield* Stream<LicenseEntry>.fromIterable(await parsedLicenses.future);
  }

  // This is run in another isolate created by _addLicenses above.
  static List<LicenseEntry> _parseLicenses(String rawLicenses) {
    final String _licenseSeparator = '\n' + ('-' * 80) + '\n';
    final List<LicenseEntry> result = <LicenseEntry>[];
    final List<String> licenses = rawLicenses.split(_licenseSeparator);
    for (final String license in licenses) {
      final int split = license.indexOf('\n\n');
      if (split >= 0) {
        result.add(LicenseEntryWithLineBreaks(
          license.substring(0, split).split('\n'),
          license.substring(split + 2),
        ));
      } else {
        result.add(LicenseEntryWithLineBreaks(const <String>[], license));
      }
    }
    return result;
  }

  <strong i="33">@override</strong>
  void initServiceExtensions() {
    super.initServiceExtensions();

    assert(() {
      registerStringServiceExtension(
        // ext.flutter.evict value=foo.png will cause foo.png to be evicted from
        // the rootBundle cache and cause the entire image cache to be cleared.
        // This is used by hot reload mode to clear out the cache of resources
        // that have changed.
        name: 'evict',
        getter: () async => '',
        setter: (String value) async {
          evict(value);
        },
      );
      return true;
    }());
  }

  /// Called in response to the `ext.flutter.evict` service extension.
  ///
  /// This is used by the `flutter` tool during hot reload so that any images
  /// that have changed on disk get cleared from caches.
  <strong i="34">@protected</strong>
  <strong i="35">@mustCallSuper</strong>
  void evict(String asset) {
    rootBundle.evict(asset);
  }

  Future<Isolate> spawnIsolate<T>(
      FutureOr<void> entryPoint(T message),
      T message, {
        bool paused = false,
        bool errorsAreFatal,
        SendPort onExit,
        SendPort onError,
        String debugName,
      }) {
    assert(
    _isMainIsolate,
    'Can\'t make multiple levels of isolates',
    );

    final RawReceivePort messageReceiver = RawReceivePort(
          (Object receivedMessage) async {
        if (receivedMessage is SendPort) {
          receivedMessage.send(
            _IsolateStarter<T>(
              ui.PluginUtilities.getCallbackHandle(entryPoint),
              message,
            ),
          );
        } else if (receivedMessage is _Message) {
          final ByteData result = await defaultBinaryMessenger.send(
            receivedMessage.channel,
            receivedMessage.message,
          );
          receivedMessage.sendPort.send(result);
        }
      },
    );
    RawReceivePort onExitReceiver;
    onExitReceiver = RawReceivePort(
          (Object message) {
        onExit?.send(message);

        onExitReceiver.close();
        messageReceiver.close();
      },
    );

    return Isolate.spawn(
      _startIsolate,
      messageReceiver.sendPort,
      paused: paused,
      errorsAreFatal: true,
      onExit: onExitReceiver.sendPort,
      onError: onError,
      debugName: debugName,
    );
  }



  // App life cycle

  /// Initializes the [lifecycleState] with the [Window.initialLifecycleState]
  /// from the window.
  ///
  /// Once the [lifecycleState] is populated through any means (including this
  /// method), this method will do nothing. This is because the
  /// [Window.initialLifecycleState] may already be stale and it no longer makes
  /// sense to use the initial state at dart vm startup as the current state
  /// anymore.
  ///
  /// The latest state should be obtained by subscribing to
  /// [WidgetsBindingObserver.didChangeAppLifecycleState].
  <strong i="36">@protected</strong>
  void readInitialLifecycleStateFromNativeWindow() {
    if (lifecycleState != null) {
      return;
    }
    final AppLifecycleState state = _parseAppLifecycleMessage(window.initialLifecycleState);
    if (state != null) {
      handleAppLifecycleStateChanged(state);
    }
  }

  Future<String> _handleLifecycleMessage(String message) async {
    handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message));
    return null;
  }

  static AppLifecycleState _parseAppLifecycleMessage(String message) {
    switch (message) {
      case 'AppLifecycleState.paused':
        return AppLifecycleState.paused;
      case 'AppLifecycleState.resumed':
        return AppLifecycleState.resumed;
      case 'AppLifecycleState.inactive':
        return AppLifecycleState.inactive;
      case 'AppLifecycleState.detached':
        return AppLifecycleState.detached;
    }
    return null;
  }

  /// The [RestorationManager] synchronizes the restoration data between
  /// engine and framework.
  ///
  /// See the docs for [RestorationManager] for a discussion of restoration
  /// state and how it is organized in Flutter.
  ///
  /// To use a different [RestorationManager] subclasses can override
  /// [createRestorationManager], which is called to create the instance
  /// returned by this getter.
  RestorationManager get restorationManager => _restorationManager;
  RestorationManager _restorationManager;

  /// Creates the [RestorationManager] instance available via
  /// [restorationManager].
  ///
  /// Can be overriden in subclasses to create a different [RestorationManager].
  <strong i="37">@protected</strong>
  RestorationManager createRestorationManager() {
    return RestorationManager();
  }
}

Future<void> _startIsolate<T>(SendPort sendPort) async {
  _sendPortToMainIsolate = sendPort;
  _IsolateBinding();

  final Completer<_IsolateStarter<T>> completer =
  Completer<_IsolateStarter<T>>();

  final RawReceivePort receivePort = RawReceivePort(
        (Object isolateStarter) {
      assert(isolateStarter is _IsolateStarter<T>);
      completer.complete(isolateStarter as _IsolateStarter<T>);
    },
  );

  sendPort.send(receivePort.sendPort);

  final _IsolateStarter<T> isolateStarter = await completer.future;

  receivePort.close();

  final Function function =
  ui.PluginUtilities.getCallbackFromHandle(isolateStarter.callbackHandle);

  await function(isolateStarter.message);
}

SendPort _sendPortToMainIsolate;

bool get _isMainIsolate => _sendPortToMainIsolate == null;

class _IsolateStarter<T> {
  _IsolateStarter(this.callbackHandle, this.message);

  final ui.CallbackHandle callbackHandle;
  final T message;
}

class _Message {
  _Message(
      this.channel,
      this.message,
      this.sendPort,
      );

  final String channel;
  final ByteData message;
  final SendPort sendPort;
}

//TODO not working
class _IsolateBinding extends BindingBase with ServicesBinding {}

/// The default implementation of [BinaryMessenger].
///
/// This messenger sends messages from the app-side to the platform-side and
/// dispatches incoming messages from the platform-side to the appropriate
/// handler.
class _DefaultBinaryMessenger extends BinaryMessenger {
  const _DefaultBinaryMessenger._();

  // Handlers for incoming messages from platform plugins.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _handlers =
  <String, MessageHandler>{};

  // Mock handlers that intercept and respond to outgoing messages.
  // This is static so that this class can have a const constructor.
  static final Map<String, MessageHandler> _mockHandlers =
  <String, MessageHandler>{};

  Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
    final Completer<ByteData> completer = Completer<ByteData>();

    if (_isMainIsolate) {
      // ui.window is accessed directly instead of using ServicesBinding.instance.window
      // because this method might be invoked before any binding is initialized.
      // This issue was reported in #27541. It is not ideal to statically access
      // ui.window because the Window may be dependency injected elsewhere with
      // a different instance. However, static access at this location seems to be
      // the least bad option.
      ui.window.sendPlatformMessage(channel, message, (ByteData reply) {
        try {
          completer.complete(reply);
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'services library',
            context:
            ErrorDescription('during a platform message response callback'),
          ));
        }
      });
    } else {
      RawReceivePort receivePort;
      receivePort = RawReceivePort(
            (Object message) async {
          assert(message is ByteData);
          completer.complete(message as ByteData);
          receivePort.close();
        },
      );
      _sendPortToMainIsolate.send(
        _Message(channel, message, receivePort.sendPort),
      );
    }

    return completer.future;
  }

  <strong i="38">@override</strong>
  Future<void> handlePlatformMessage(
      String channel,
      ByteData data,
      ui.PlatformMessageResponseCallback callback,
      ) async {
    ByteData response;
    try {
      final MessageHandler handler = _handlers[channel];
      if (handler != null) {
        response = await handler(data);
      } else {
        ui.channelBuffers.push(channel, data, callback);
        callback = null;
      }
    } catch (exception, stack) {
      FlutterError.reportError(FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'services library',
        context: ErrorDescription('during a platform message callback'),
      ));
    } finally {
      if (callback != null) {
        callback(response);
      }
    }
  }

  <strong i="39">@override</strong>
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }

  <strong i="40">@override</strong>
  void setMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _handlers.remove(channel);
    else
      _handlers[channel] = handler;
    ui.channelBuffers.drain(channel, (ByteData data, ui.PlatformMessageResponseCallback callback) async {
      await handlePlatformMessage(channel, data, callback);
    });
  }

  <strong i="41">@override</strong>
  void setMockMessageHandler(String channel, MessageHandler handler) {
    if (handler == null)
      _mockHandlers.remove(channel);
    else
      _mockHandlers[channel] = handler;
  }

  <strong i="42">@override</strong>
  bool checkMessageHandler(String channel, MessageHandler handler) => _handlers[channel] == handler;

  <strong i="43">@override</strong>
  bool checkMockMessageHandler(String channel, MessageHandler handler) => _mockHandlers[channel] == handler;
}

@奈利克
目前,我无法更新 Flutter 以帮助您

我想知道为什么颤振团队实现的其他颤振工具(比如 devtools)从来没有遇到过这个障碍。
他们似乎只有在被他们阻止时才会修复错误。

这不是一个严重的新功能,它是一个框架阻止程序。
所以根据这个https://github.com/flutter/flutter/issues/18761#issuecomment -639248761它应该是P3

我想知道为什么颤振团队实现的其他颤振工具(比如 devtools)从来没有遇到过这个障碍。
他们似乎只有在被他们阻止时才会修复错误。

老实说,因为我认为这个问题对现实世界应用程序的影响被高估了。 我只是在关注这个问题,因为它会让事情变得更容易,但是有“解决方法”。 生产中的许多应用程序和插件都在使用后台功能。

我也不确定某些 +1 是否只是不太了解问题所在。 (如 https://github.com/flutter/flutter/issues/13937#issuecomment-635683123 )。 Imo 后台隔离和无头运行时(以及如何管理它)的官方文档仍然有点缺乏,firebase 消息传递的东西是一场噩梦,但这与框架、引擎无关🤷‍♂️

说不定就别再抨击flutter团队了,有惊人的进步,这也不是解决不了的事情……而且如果有PR,不如直接提交给flutter团队考虑吧🤔

为隔离实现此行为存在问题。

例子:

有一个主隔离和一个引擎。
如果隔离器需要某些东西,它会询问引擎。
如果引擎需要某些东西,它会询问隔离器。

现在我们的情况是:
有 2 个隔离器和一个引擎。
如果隔离器需要某些东西,它会询问引擎。
如果引擎需要什么,它应该怎么做?
两个都问? 那么该拿谁的答案呢?
或者只问一个隔离? 然后是哪一个?

@hpoul我不得不同意不同意,因为这里的很多人都是 android/ios 编程的初学者,他们选择了他们的第一个应用程序开发框架来颤振。

因此,重要的是有人意识到这一点并修复或建议官方解决方法,其中包含此类问题的示例,您被框架阻止并强制执行复杂的“解决方法”,因为显然初学者不知道要学习什么,甚至从哪里开始,因为这里的评论或 stackoverflow 或其他博客上的任何地方都没有很好解释的解决方法。 只有一些东西指向一些可能正确实现也可能没有正确实现的包。

android 和 ios 中拥有大量使用服务、后台任务队列等经验且可以轻松解决此问题的人不是 Flutter 的目标人群。

像这样的离题评论,我敢肯定,在所有其他问题中都有错误的 +1,这并没有太大改变问题优先级的顺序,这个问题仍然是最重要的。

@phanirithvij

flutter_isolate 已在上面链接,自 2019 年 2 月以来一直存在。它也并不复杂。 我将复制看似简单的自述文件:

FlutterIsolate 允许在 Flutter 中创建一个能够使用 Flutter 插件的 Isolate。 它创建必要的平台特定位(Android 上的 FlutterBackgroundView 和 iOS 上的 FlutterEngine),以使平台通道能够在隔离内工作。

| | 安卓 | iOS | 说明 |
| :--------------- | :----------------: | :-----------------: | :-------------------------------- |
| FlutterIsolate.spawn(entryPoint,message) | :white_check_mark: | :white_check_mark: | 产生一个新的 FlutterIsolate |
| 颤振隔离.pause() | :white_check_mark: | :white_check_mark: | 暂停正在运行的隔离 |
| 颤振隔离.resume() | :white_check_mark: | :white_check_mark: | 恢复暂停的isoalte |
| flutterIsolate.kill() | :white_check_mark: | :white_check_mark: | 杀死一个孤立的|

我说直截了当,因为这些方法与“原始” Isolate类中的方法名称相同,所以如果您已经知道如何使用官方文档中的隔离,那么理解如何使用应该不难这作为一种替代品,至少对于上面列出的那些方法。

(注意:我也希望正式解决这个问题,原因我已经在之前的评论中说明过,其中一些我与你分享,但我也不会称这种解决方法“复杂”。)

作为对@nikitadol评论的回应,flutter_isolate 为每个隔离创建了一个单独的引擎,因此它不会遇到相同的问题。 当然,必须为每个隔离创建一个新引擎并不理想,这也是我确信 Flutter 团队可以做得更好的原因之一。 同样,通过主隔离管传递所有插件方法调用的替代解决方案首先会产生与隔离目标背道而驰的瓶颈,因此我相信 Flutter 团队也可以在这方面做得更好。

我同意那些说 Flutter 团队可能有更高优先级的问题需要首先解决的人的观点,但根据我们之前对插件架构的了解,解决这个问题也可能需要进行重大的架构更改,以及其他几个未解决的问题还需要进行重大的架构更改,并且权衡所有这些不同的要求可能需要一些时间才能解决这个特定问题。

@ryanheise

flutter_isolate 为每个隔离创建一个单独的引擎

所以我相信 Flutter 团队也可以在这方面做得更好。

你说 Flutter 团队可以做到这一点,但是怎么做呢?
我上面提出的问题仍然有效。
如果解决方案是创建一个新引擎,那么您可以简单地使用您的插件

(注意,flutter_isolate 不是我的插件,但因为我也需要这个功能,所以我决定为这个项目做出贡献。)

我所说的“更好的工作”的意思是,可能没有必要为了与插件通信而启动 FlutterEngine 中的所有机器,但目前,必要的基础设施都与引擎相关联,这就是为什么目前flutter_isolate 必须启动整个引擎才能访问插件。

(注意,flutter_isolate 不是我的插件,但因为我也需要这个功能,所以我决定为这个项目做出贡献。)

抱歉,我没有注意到这是一个叉子

flutter_isolate 必须启动整个引擎才能访问插件。

如果你指定'is_background_view = true',那么引擎不会开始渲染——这意味着引擎没有完全启动

对于 iOS 和 Android v1 和 Android v2,用于无头运行的精确 API 是不同的,但基本上这就是 flutter_isolate 已经在做的事情。 但是,普通隔离和 FlutterEngine 之间的开销仍然存在差异,因此进一步降低开销只是 Flutter 团队能够做的事情。 这不仅是启动时间的开销,也是内存使用的开销。

所以你只是建议优化一个新引擎的创建在后台工作?

那么这可能不适用于这个问题

我不是在暗示,我只是指出 flutter_isolate 解决方法有这个限制,因为开销是无法避免的。 我并不是建议 Flutter 团队尝试让解决方法更高效,我认为理想情况下,插件加载方式会发生架构上的变化,这样就不需要实例化 FlutterEngine。

如果引擎为1,并且isolates大于1,那么我们回到这个问题:

https://github.com/flutter/flutter/issues/13937#issuecomment -667314232

我并不是真的想为这种新的轻量级架构提出解决方案,但如果你问我,我可以想到轻量级解决方案的两种变体:

  1. 通过使方法通道接受来自多个客户端的连接,使方法通道有点像服务器套接字。 所以插件平台端的方法通道可以接受来自多个隔离的连接,然后一旦连接就可以向正确的隔离发送消息。
  2. 为同一个 FlutterEngine 中的每个隔离 BUT 创建一个新的插件注册表等,

如果你问我

是的,我问

可以将消息发送到正确的隔离区。

我认为这是一个糟糕的解决方案,因为“正确隔离”是一个相对概念

为同一个 FlutterEngine 中的每个隔离 BUT 创建一个新的插件注册表等,

这可能是一个不错的选择,但前提是引擎始终自行注册插件。

但这只发生在 GeneratedPluginRegistrant 中的所有插件。
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

创建额外的回调不是一个明显的行为。

我认为这是一个糟糕的解决方案,因为“正确隔离”是一个相对概念

这可能是一个糟糕的解决方案,但由于这个原因它不会是糟糕的。 “正确的隔离”是明确的,就像服务器套接字以明确的方式处理客户端连接一样。

这可能是一个不错的选择,但前提是引擎始终自行注册插件。

这将是一件好事,以确保总是发生。 目前该机制有点不稳定。

这可能是一个糟糕的解决方案,但由于这个原因它不会是糟糕的。 “正确的隔离”是明确的,就像服务器套接字以明确的方式处理客户端连接一样。

然后每个插件都必须考虑到可以从不同的隔离区访问它 - 不好

是的。

让我在这个讨论中加两分钱......我已经与 Flutter 进行了大约两年的商业合作,我学到的最重要的事情是Flutter 是一个 UI 工具包。 如果人们没有忘记这一点,那么这个声明就不会令人惊讶——它甚至在 Flutter 主网站上也这么说。

Flutter 是一个 UI 工具包,它擅长的就是 UI。 如果您尝试创建一个依赖昂贵数据处理或使用复杂算法并消耗大量数据的 CPU 密集型应用程序,那么您不应该为此使用 UI 工具包。 业务逻辑不应与 UI 混合。

当然,使用 Dart,我们可以做的不仅仅是 UI,但我们不要忘记这个工具包的主要目的。

我尝试了多种方法,包括使用无头 Flutter - 这被证明是一场彻底的灾难。 不是因为它不起作用,而是因为我们在向其他开发人员解释它的工作原理时遇到了问题。 它增加的维护负担是不值得的。 更不用说测试这样的解决方案了。 我上次尝试时,Flutter Driver 根本无法处理。

如果您的应用程序确实需要处理能力,恕我直言,您有三个选择:

  • 将您的逻辑移至服务器;
  • 将您的逻辑移动到本机平台 - 您可以为此使用 Kotlin Native - 并使用事件/方法通道仅向您的 UI 提供视图模型(我认为这就是 OLX 所做的:https://tech.olx.com/fast -prototypes-with-flutter-kotlin-native-d7ce5cfeb5f1);
  • 不要使用 Flutter 并寻找另一个框架(Xamarin/Blazor 可能非常适合,因为 C# 提供了比 Dart 更复杂的多线程环境)。

以上所有方法都有其缺点,但尝试扩展库/框架来完成它并非真正设计的工作也不理想。

是不是isolate_handler解决了这个问题?

@hasonguo它在后台使用flutter_isolate ,它只是添加了一个额外的层,通过添加用于通信的样板来使处理隔离(实际上是任何隔离)更容易,这样您就不需要每次都手动设置它时间。 之前它使用了一种不同的方式来解决这个问题,但是由于 Flutter 本身的变化,这使得它变得不可能,并且改为依赖于flutter_isolate

我们在兜圈子。 是的,有解决方案,实际上两个插件。 正如 Ryan 指出的那样,使通道通信成为可能会产生开销。 很有可能,正是这种开销使 Flutter 团队避免将其自动添加到原始Isolate中,因为在许多(大多数?)情况下您不需要支持。

尽管如此,它仍然不是一个干净的解决方案。 如果 Flutter 团队能够直接和自动地提供开箱即用的任何基本Isolate的通道通信,那么整个讨论将毫无意义。 然而,反之亦然:他们并不是真的为了改变而追求改变。 如果包或插件中有可行的解决方案,为什么要花时间增加核心的重量?

在同一个 FlutterEngine 中为每个隔离 BUT 创建一个新的插件注册表等,

这可能是一个不错的选择,但前提是引擎始终自行注册插件。

但这只发生在 GeneratedPluginRegistrant 中的所有插件时。
https://github.com/flutter/engine/blob/f7d241fd8a889fadf8ab3f9d57918be3d986b4be/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java#L330 -L344

创建额外的回调不是一个明显的行为。

第二个想法的另一个改进是在新的隔离中延迟加载插件。 通常,如果一个项目依赖于许多插件, GeneratedPluginRegistrant将非常大,并且取决于插件初始化例程的效率,这可能会导致一些开销。 究竟如何实现这一点是另一回事。 平台通道不归插件所有,因此当前通过通道发送消息不能用于触发插件的平台端被实例化。 因此,要么方法通道需要与插件相关联,要么需要有一个额外的机制,插件的 Dart 端可以调用以在创建第一个方法通道之前延迟初始化自身,并且该机制可以触发实例化隔离内该插件的平台端。

@TahaTesser

在您的示例中,错误与此问题无关

E/flutter ( 7814): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Invalid argument(s): Isolate.spawn expects to be passed a static or top-level function

嗨@nikitadol
谢谢,更新的示例,它不会引发异常,对吗? (我以前从来不需要使用)
您能否提供一个完整的可重现的最小代码示例
谢谢

@TahaTesser


代码示例

import 'package:battery/battery.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

Future<void> main() async {
  await printBatteryLevel();
  await compute(printBatteryLevel, null);
}

Future<void> printBatteryLevel([dynamic message]) async {
  WidgetsFlutterBinding.ensureInitialized();
  print(await Battery().batteryLevel);
}



日志

Launching lib/main.dart on Pixel 4 in debug mode...
Running Gradle task 'assembleDebug'...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk...
Waiting for Pixel 4 to report its views...
Debug service listening on ws://127.0.0.1:50709/-SPs_6AmL2Q=/ws
Syncing files to device Pixel 4...
I/flutter ( 8708): 39
E/flutter ( 8708): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Exception: UI actions are only available on root isolate.
E/flutter ( 8708): #0      Window._nativeSetNeedsReportTimings (dart:ui/window.dart:1003:86)
E/flutter ( 8708): #1      Window.onReportTimings= (dart:ui/window.dart:996:29)
E/flutter ( 8708): #2      SchedulerBinding.addTimingsCallback (package:flutter/src/scheduler/binding.dart:272:14)
E/flutter ( 8708): #3      SchedulerBinding.initInstances (package:flutter/src/scheduler/binding.dart:209:7)
E/flutter ( 8708): #4      ServicesBinding.initInstances (package:flutter/src/services/binding.dart:27:11)
E/flutter ( 8708): #5      PaintingBinding.initInstances (package:flutter/src/painting/binding.dart:23:11)
E/flutter ( 8708): #6      SemanticsBinding.initInstances (package:flutter/src/semantics/binding.dart:24:11)
E/flutter ( 8708): #7      RendererBinding.initInstances (package:flutter/src/rendering/binding.dart:32:11)
E/flutter ( 8708): #8      WidgetsBinding.initInstances (package:flutter/src/widgets/binding.dart:257:11)
E/flutter ( 8708): #9      new BindingBase (package:flutter/src/foundation/binding.dart:59:5)
E/flutter ( 8708): #10     new _WidgetsFlutterBinding&BindingBase&GestureBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #11     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #12     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #13     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #14     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #15     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #16     new _WidgetsFlutterBinding&BindingBase&GestureBinding&SchedulerBinding&ServicesBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #17     new WidgetsFlutterBinding (package:flutter/src/widgets/binding.dart)
E/flutter ( 8708): #18     WidgetsFlutterBinding.ensureInitialized (package:flutter/src/widgets/binding.dart:1229:7)
E/flutter ( 8708): #19     printBatteryLevel (package:bug/main.dart:11:25)
E/flutter ( 8708): #20     _IsolateConfiguration.apply (package:flutter/src/foundation/_isolates_io.dart:83:34)
E/flutter ( 8708): #21     _spawn.<anonymous closure> (package:flutter/src/foundation/_isolates_io.dart:90:65)
E/flutter ( 8708): #22     Timeline.timeSync (dart:developer/timeline.dart:163:22)
E/flutter ( 8708): #23     _spawn (package:flutter/src/foundation/_isolates_io.dart:87:35)
E/flutter ( 8708): #24     _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:304:17)
E/flutter ( 8708): #25     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12)
E/flutter ( 8708): 



颤振医生-v

[✓] Flutter (Channel stable, 1.20.4, on Mac OS X 10.15.6 19G2021, locale en-BY)
    • Flutter version 1.20.4 at /Users/nikitadold/development/flutter
    • Framework revision fba99f6cf9 (2 weeks ago), 2020-09-14 15:32:52 -0700
    • Engine revision d1bc06f032
    • Dart version 2.9.2

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.0)
    • Android SDK at /Users/nikitadold/Library/Android/sdk
    • Platform android-30, build-tools 30.0.0
    • Java binary at: /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 12.0.1, Build version 12A7300
    • CocoaPods version 1.9.3

[!] Android Studio (version 4.0)
    • Android Studio at /Users/nikitadold/Library/Application Support/JetBrains/Toolbox/apps/AndroidStudio/ch-0/193.6626763/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] IntelliJ IDEA Ultimate Edition (version 2020.2.2)
    • IntelliJ at /Users/nikitadold/Applications/JetBrains Toolbox/IntelliJ IDEA Ultimate.app
    • Flutter plugin installed
    • Dart plugin version 202.7319.5

@TahaTesser
请看这条评论

平台消息仅受主隔离支持。 [...]

这种行为是_expected_并且具有标签severe: new feature
这个问题应该被视为_功能请求_或_提案_而不是_错误_

@iapicca

在您的示例中,错误与此问题无关

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

@iapicca

在您的示例中,错误与此问题无关

E/flutter (23174): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Invalid argument(s): Illegal argument in isolate message : (object is a closure - Function '<anonymous closure>':.)

你是对的,我的不是一个很好的例子,请忽略它

此页面是否有帮助?
0 / 5 - 0 等级