Flutter MethodChannel 原生通信导致的Rep
2020-10-19 本文已影响0人
巴黎没有摩天轮Li
前言
最近在做公司的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()。