Flutter MethodChannel 原生通信导致的Reply already submitted问题

前言

最近在做公司的Flutter项目,在封装扫码插件的时候,Bugly显示Reply already submitted问题,就此记录一下。

2020-10-19 13:54:52.823 31754-31754/com.xxx.qr_code_plugin_example E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.xxx.qr_code_plugin_example, PID: 31754
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=65517, result=-1, data=Intent { (has extras) }} to activity {com.xxx.qr_code_plugin_example/com.xxx.qr_code_plugin_example.MainActivity}: java.lang.IllegalStateException: Reply already submitted
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4938)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4979)
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7560)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
     Caused by: java.lang.IllegalStateException: Reply already submitted
        at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:139)
        at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:235)
        at com.xxx.qr_code_plugin.QrCodePlugin.onActivityResult(QrCodePlugin.kt:199)
        at io.flutter.embedding.engine.FlutterEnginePluginRegistry$FlutterEngineActivityPluginBinding.onActivityResult(FlutterEnginePluginRegistry.java:691)
        at io.flutter.embedding.engine.FlutterEnginePluginRegistry.onActivityResult(FlutterEnginePluginRegistry.java:378)
        at io.flutter.embedding.android.FlutterActivityAndFragmentDelegate.onActivityResult(FlutterActivityAndFragmentDelegate.java:619)
        at io.flutter.embedding.android.FlutterActivity.onActivityResult(FlutterActivity.java:584)
        at android.app.Activity.dispatchActivityResult(Activity.java:8250)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4931)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4979) 
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2044) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:224) 
        at android.app.ActivityThread.main(ActivityThread.java:7560) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) 

问题复现

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        mMethodResult = result
        mCallMethod = call 
        // 调用两次success方法
        result.success("xxx")
        result.success("xxx")
    }

写法很多,网上有说是因为switch case语句没有设置default导致的该问题,但是设置了default确实可以解决,但是为什么会导致这个问题,原因不明。

解决办法

fun ignoreIllegalState(fn: () -> Unit) {
        try {
            fn()
        }catch (e:IllegalStateException){
            // ignore
        }
    }

Result#success()调用流程

MethodChannel

private final class IncomingMethodCallHandler implements BinaryMessageHandler {
    private final MethodCallHandler handler;

    IncomingMethodCallHandler(MethodCallHandler handler) {
      this.handler = handler;
    }

    @Override
    @UiThread
    public void onMessage(ByteBuffer message, final BinaryReply reply) {
      final MethodCall call = codec.decodeMethodCall(message);
      try {
        handler.onMethodCall(
            call,
            new Result() {
              @Override
              public void success(Object result) {
                // 执行 success() 方法
                reply.reply(codec.encodeSuccessEnvelope(result));
              }

              @Override
              public void error(String errorCode, String errorMessage, Object errorDetails) {
                reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
              }

              @Override
              public void notImplemented() {
                reply.reply(null);
              }
            });
      } catch (RuntimeException e) {
        Log.e(TAG + name, "Failed to handle method call", e);
        reply.reply(codec.encodeErrorEnvelope("error", e.getMessage(), null));
      }
    }
  }

由此看到,IncomingMethodCallHandler 实现了BinaryMessageHandler 接口,也就实现了接口方法onMessage(), 看下具体是谁调用了onMessage()方法。

DartMessenger#handleMessageFromDart()

@Override
public void handleMessageFromDart(
    @NonNull final String channel, @Nullable byte[] message, final int replyId) {
  Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
  BinaryMessenger.BinaryMessageHandler handler = messageHandlers.get(channel);
  if (handler != null) {
    try {
      Log.v(TAG, "Deferring to registered handler to process message.");
      final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
      // 这里参数直接初始化Reply()
      handler.onMessage(buffer, new Reply(flutterJNI, replyId));
    } catch (Exception ex) {
      Log.e(TAG, "Uncaught exception in binary message listener", ex);
      flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
    }
  } else {
    Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message."
    flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
  }
}

最终调用的就是Reply的reply方法。

private static class Reply implements BinaryMessenger.BinaryReply {
  @NonNull private final FlutterJNI flutterJNI;
  private final int replyId;
  // 1 
  private final AtomicBoolean done = new AtomicBoolean(false);
  Reply(@NonNull FlutterJNI flutterJNI, int replyId) {
    this.flutterJNI = flutterJNI;
    this.replyId = replyId;
  }
  @Override
  public void reply(@Nullable ByteBuffer reply) {
    // 2
    if (done.getAndSet(true)) {
      throw new IllegalStateException("Reply already submitted");
    }
    if (reply == null) {
      flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
    } else {
      flutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
    }
  }
}

1处代码使用了CAS原子性变量,为了记录此次一次的Method Call 流程是否已完成。2处代码就是抛出异常Reply already submitted的原因。若流程第一次调用,则会走FlutterJNI,进行真正的通信过程,通过字节流进行数据的相互传递。

所以解决问题办法,就是从代码逻辑判断是什么情况会导致同一个Result对象会调用多次的success()。

你可能感兴趣的:(Flutter MethodChannel 原生通信导致的Reply already submitted问题)