【问题标题】:Why does MethodChannel.Result.success() sometimes cause a timeout at Flutter?为什么 MethodChannel.Result.success() 有时会导致 Flutter 超时?
【发布时间】:2022-01-19 00:35:40
【问题描述】:

我有一个简单的 Flutter 插件,它通过 MethodChannel 与一些原生 Android 代码进行通信。以下是插件提供的方法的简单示例:

Future<void> doSomethingImportant(int address, SomeImportantData importantData) async {
    String jsonButStillImportant = jsonEncode(importantData.toJson());
    return methodChannel.invokeMethod('doSomethingImportant', {"address": address, "data": jsonButStillImportant});
  }

doSomethingImportant()(java) 方法完成后,通过调用将结果传递给 Flutter:

public void onSuccess(Result result, Object value) {
    new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
            public void run() {
                result.success(value);
                Log.d("Debug", "Why does this occasionally cause a timeout at Flutter?!?");
            }
    });
}

然后,在测试期间,该方法被这样调用:

void _someTestMethod() async {
  await doSomethingImportant(anAddress, theMostImportantDataInTheWorld).timeout(const Duration(seconds: 1)).catchError((e) {
    print("Something's wrong here: "+e.toString());
    return;
  });
}

这在大多数情况下都运行良好。然而,有时测试会失败,因为doSomethingImportant() 遇到超时,并且 logcat 会显示以下内容:

2021-12-15 19:56:50.919 D/Debug: Why does this occasionally cause a timeout at Flutter?!?" 
2021-12-15 19:56:51.697 I/flutter: Something's wrong here: TimeoutException after 0:00:01.000000: Future not completed

现在我想知道什么可能导致超时以及如何防止它。值得注意的是,超时消息总是在调用result.success() 后大约700-800 毫秒出现。

编辑 1: 问题是有时未来没有完成,即使result.success() 被调用。这只是偶尔发生,但如果发生,超时目的是优雅地退出调用函数。不幸的是,简单地删除超时不是一种选择,因为它会永远阻塞。


编辑 2 - 更多背景信息: 请注意,doSomethingImportant()_someTestMethod() 的上述代码 sn-ps 只是用于说明方法外观的示例。 (除了onSuccess(),这个是生产代码的精确副本)。

实际的插件控制蓝牙 Mesh 网络。 _someTestMethod() 发送和接收数据以验证网状网络和运行在各个节点上的固件是否正在执行它们应该执行的操作。 _someTestMethod() 中的逻辑如下所示:

  1. 将数据发送到网格中。
  2. 从网格中读取数据。
  3. 验证读取的数据是否符合预期结果。

之所以选择await/timeout() 方法,是因为每一步都依赖于上一步的成功完成。将这些调用嵌套在 .then() 回调中是一种替代方法,但我更喜欢从上到下的 await/timeout()-spaghetti-code 而不是嵌套的 .then() 回调。

问题是,Android 时不时会调用MethodChannel.Result.success(),但Flutter 并没有完成Future。如果发生这种情况,Future 将永远保持未完成状态,logcat 也不会提供任何信息。

同样,这种情况只是偶尔发生。大多数时候它工作得很好。

现在的问题是,这里出了什么问题?还有其他方法可以说服Future 完成吗?欢迎任何想法谢谢!

【问题讨论】:

    标签: flutter flutter-method-channel


    【解决方案1】:

    现在我想知道什么可能导致超时以及如何防止它。值得注意的是,超时消息总是在调用 result.success() 后大约 700-800 毫秒出现。

    您对如何修复(甚至调试)此超时的来源有任何想法吗?谢谢!

    您正在使用.timeout(const Duration(seconds: 1)),所以它超时了。如果您不希望它超时,请删除该代码。

    您能澄清一下您面临的问题是什么吗?您是否对自己的工作没有在 1 秒内完成感到失望,或者您只是没有意识到您添加了超时?


    修改后:

    使用await + timeout 不会优雅地退出函数,它实际上会引发错误。要“优雅地”处理 Future 可能不会立即完成的这种情况,而不是使用 await,而是使用 .then。这样,平台方法调用下方的任何代码都不会延迟。当然,您的平台方法中不会有结果。如果您立即需要该结果,那么 await + timeout + catchError.then 都不会帮助您。

    任意选择 1 秒(或任何其他时间)运行代码然后抛出错误肯定会带来问题,尤其是在用户使用各种设备的生产应用中。在慢速设备上,这可能会发生,你应该优雅地处理它。它也可能会损害调试,假设您进行平台方法调用然后在断点处暂停,代码将抛出错误,因为计时器仍在运行。多么糟糕的调试体验。在这种情况下,我强烈建议不要使用timeout

    帮助调试:

    • 在 Android Studio 中打开 android 项目窗口,然后:
      • 从此窗口(启动 Android 应用程序)运行调试器并附加调试器
      • 从 Flutter 窗口运行调试器,并将 android 项目窗口调试器附加到正在运行的应用程序。 应用
    • result.success(value); 行放置一个断点,并观察它是否被调用。
      • 也许new Handler(Looper.getMainLooper()).post(new Runnable() { 永远不会被调用,在这种情况下,您必须了解为什么永远不会发布该可运行文件。我不确定您是否真的运行了上面发布的示例,或者您是否只是在尝试调试更复杂的应用程序。
      • 也许正在发生另一个问题,并且可能会显示一个更有用的错误。 (您没有共享很多日志,请查看 logcat 或 pidcat。)。一个疯狂的猜测是主线程现在很忙,正在做一些你不应该让它在主线程上做的工作??,或者只是做 Flutter 工作(不太可能)。

    【讨论】:

    • 你能解释两条 logcat 消息之间的 778 毫秒吗?为什么doSomethingImportant 没有在那 778 毫秒内完成,导致超时触发?原生代码调用了result.success,但 Dart 的未来还没有完成?当然,778 毫秒的时间足以让该回复通过渠道。我提交它没有来,因为不寻常的“在回调中调用 result.xxx”模式破坏了某些东西。
    • 作者为什么不尝试删除超时并确认它是否“永远”返回,或者它只是在 1s 之后发生。
    • 问题已更新。谢谢你的帮助。删除超时将永远阻塞调用方法,这是超时应该防止的。
    • 我已经详细说明了答案。总结一下,不要用await+timeout,解决底层问题。调试 Android 端并尝试找出您的 Runnable 未发布的原因。如果您提供更多信息,我很乐意提供帮助。
    • @BenButterworth 非常感谢您的建议。这是一个非常奇怪的行为 - 可能是 MethodChannel.Result.success() 中的一个错误 - 虽然有点怀疑,但它发生得太频繁(每隔几十个调用一次)而不会被忽视。
    猜你喜欢
    • 2013-12-03
    • 2012-09-18
    • 2011-09-28
    • 1970-01-01
    • 2016-06-13
    • 2011-10-22
    • 1970-01-01
    • 2014-07-06
    • 2016-10-20
    相关资源
    最近更新 更多