[Android] Expose channel buffer resize and overflow calls (flutter/engine#44434)

## Description

This PR updates the Android engine in order to provide a more efficient implementation for `BasicMessageChannel.resizeChannelBuffer` (helper to call the `resize` control command).
It also adds a new helper called`BasicMessageChannel.allowChannelBufferOverflow` to call the `overflow` control command.

## Related Issue

Fixes https://github.com/flutter/flutter/issues/132048
Android implementation for https://github.com/flutter/flutter/issues/132386

## Tests

Adds 2 tests.
This commit is contained in:
Bruno Leroux 2023-08-18 08:16:22 +02:00 committed by GitHub
parent 216984de1a
commit 4f50825d26
3 changed files with 121 additions and 17 deletions

View File

@ -12,8 +12,7 @@ import io.flutter.Log;
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Arrays;
/**
* A named channel for communicating with the Flutter application using basic, asynchronous message
@ -143,13 +142,56 @@ public final class BasicMessageChannel<T> {
resizeChannelBuffer(messenger, name, newSize);
}
static void resizeChannelBuffer(
@NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) {
Charset charset = Charset.forName("UTF-8");
String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize);
final byte[] bytes = messageString.getBytes(charset);
/**
* Toggles whether the channel should show warning messages when discarding messages due to
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
* not be shown.
*/
public void allowChannelBufferOverflow(boolean allowed) {
allowChannelBufferOverflow(messenger, name, allowed);
}
private static ByteBuffer packetFromEncodedMessage(ByteBuffer message) {
// Create a bytes array using the buffer content (messages.array() can not be used here).
message.flip();
final byte[] bytes = new byte[message.remaining()];
message.get(bytes);
// The current Android Java/JNI platform message implementation assumes
// that all buffers passed to native are direct buffers.
ByteBuffer packet = ByteBuffer.allocateDirect(bytes.length);
packet.put(bytes);
return packet;
}
/**
* Adjusts the number of messages that will get buffered when sending messages to channels that
* aren't fully set up yet. For example, the engine isn't running yet or the channel's message
* handler isn't set up on the Dart side yet.
*/
public static void resizeChannelBuffer(
@NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) {
final StandardMethodCodec codec = StandardMethodCodec.INSTANCE;
Object[] arguments = {channel, newSize};
MethodCall methodCall = new MethodCall("resize", Arrays.asList(arguments));
ByteBuffer message = codec.encodeMethodCall(methodCall);
ByteBuffer packet = packetFromEncodedMessage(message);
messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, packet);
}
/**
* Toggles whether the channel should show warning messages when discarding messages due to
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
* not be shown.
*/
public static void allowChannelBufferOverflow(
@NonNull BinaryMessenger messenger, @NonNull String channel, boolean allowed) {
final StandardMethodCodec codec = StandardMethodCodec.INSTANCE;
Object[] arguments = {channel, allowed};
MethodCall methodCall = new MethodCall("overflow", Arrays.asList(arguments));
ByteBuffer message = codec.encodeMethodCall(methodCall);
ByteBuffer packet = packetFromEncodedMessage(message);
messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, packet);
}

View File

@ -157,6 +157,15 @@ public class MethodChannel {
BasicMessageChannel.resizeChannelBuffer(messenger, name, newSize);
}
/**
* Toggles whether the channel should show warning messages when discarding messages due to
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
* not be shown.
*/
public void allowChannelBufferOverflow(boolean allowed) {
BasicMessageChannel.allowChannelBufferOverflow(messenger, name, allowed);
}
/** A handler of incoming method calls. */
public interface MethodCallHandler {
/**

View File

@ -1,6 +1,7 @@
package io.flutter.plugin.common;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@ -11,17 +12,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.embedding.engine.dart.DartExecutor;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.robolectric.annotation.Config;
@Config(manifest = Config.NONE)
@RunWith(AndroidJUnit4.class)
public class MethodChannelTest {
@Test
public void methodChannel_resizeChannelBuffer() {
public void resizeChannelBufferMessageIsWellformed() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
String channel = "flutter/test";
@ -30,15 +30,68 @@ public class MethodChannelTest {
int newSize = 3;
rawChannel.resizeChannelBuffer(newSize);
Charset charset = Charset.forName("UTF-8");
String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize);
final byte[] bytes = messageString.getBytes(charset);
ByteBuffer packet = ByteBuffer.allocateDirect(bytes.length);
packet.put(bytes);
// Created from the following Dart code:
// MethodCall methodCall = const MethodCall('resize', ['flutter/test', 3]);
// const StandardMethodCodec().encodeMethodCall(methodCall).buffer.asUint8List();
final byte[] expected = {
7, 6, 114, 101, 115, 105, 122, 101, 12, 2, 7, 12, 102, 108, 117, 116, 116, 101, 114, 47, 116,
101, 115, 116, 3, 3, 0, 0, 0
};
// Verify that DartExecutor sent the correct message to FlutterJNI.
// Verify that the correct message was sent to FlutterJNI.
ArgumentMatcher<ByteBuffer> packetMatcher =
new ByteBufferContentMatcher(ByteBuffer.wrap(expected));
verify(mockFlutterJNI, times(1))
.dispatchPlatformMessage(
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL), eq(packet), anyInt(), anyInt());
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL),
argThat(packetMatcher),
anyInt(),
anyInt());
}
@Test
public void overflowChannelBufferMessageIsWellformed() {
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
String channel = "flutter/test";
MethodChannel rawChannel = new MethodChannel(dartExecutor, channel);
rawChannel.allowChannelBufferOverflow(true);
// Created from the following Dart code:
// MethodCall methodCall = const MethodCall('overflow', ['flutter/test', true]);
// const StandardMethodCodec().encodeMethodCall(methodCall).buffer.asUint8List();
final byte[] expected = {
7, 8, 111, 118, 101, 114, 102, 108, 111, 119, 12, 2, 7, 12, 102, 108, 117, 116, 116, 101, 114,
47, 116, 101, 115, 116, 1
};
// Verify that the correct message was sent to FlutterJNI.
ArgumentMatcher<ByteBuffer> packetMatcher =
new ByteBufferContentMatcher(ByteBuffer.wrap(expected));
verify(mockFlutterJNI, times(1))
.dispatchPlatformMessage(
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL),
argThat(packetMatcher),
anyInt(),
anyInt());
}
}
// Custom ByteBuffer matcher which calls rewind on both buffers before calling equals.
// ByteBuffer.equals might return true when comparing byte buffers with different content if
// both have no remaining elements.
class ByteBufferContentMatcher implements ArgumentMatcher<ByteBuffer> {
private ByteBuffer expected;
public ByteBufferContentMatcher(ByteBuffer expected) {
this.expected = expected;
}
@Override
public boolean matches(ByteBuffer received) {
expected.rewind();
received.rewind();
return received.equals(expected);
}
}