Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 2f9a4ef

Browse files
authored
[Android] Expose channel buffer resize and overflow calls (#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 flutter/flutter#132048 Android implementation for flutter/flutter#132386 ## Tests Adds 2 tests.
1 parent 58f7d8e commit 2f9a4ef

File tree

3 files changed

+121
-17
lines changed

3 files changed

+121
-17
lines changed

shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler;
1313
import io.flutter.plugin.common.BinaryMessenger.BinaryReply;
1414
import java.nio.ByteBuffer;
15-
import java.nio.charset.Charset;
16-
import java.util.Locale;
15+
import java.util.Arrays;
1716

1817
/**
1918
* A named channel for communicating with the Flutter application using basic, asynchronous message
@@ -143,13 +142,56 @@ public void resizeChannelBuffer(int newSize) {
143142
resizeChannelBuffer(messenger, name, newSize);
144143
}
145144

146-
static void resizeChannelBuffer(
147-
@NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) {
148-
Charset charset = Charset.forName("UTF-8");
149-
String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize);
150-
final byte[] bytes = messageString.getBytes(charset);
145+
/**
146+
* Toggles whether the channel should show warning messages when discarding messages due to
147+
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
148+
* not be shown.
149+
*/
150+
public void allowChannelBufferOverflow(boolean allowed) {
151+
allowChannelBufferOverflow(messenger, name, allowed);
152+
}
153+
154+
private static ByteBuffer packetFromEncodedMessage(ByteBuffer message) {
155+
// Create a bytes array using the buffer content (messages.array() can not be used here).
156+
message.flip();
157+
final byte[] bytes = new byte[message.remaining()];
158+
message.get(bytes);
159+
160+
// The current Android Java/JNI platform message implementation assumes
161+
// that all buffers passed to native are direct buffers.
151162
ByteBuffer packet = ByteBuffer.allocateDirect(bytes.length);
152163
packet.put(bytes);
164+
165+
return packet;
166+
}
167+
168+
/**
169+
* Adjusts the number of messages that will get buffered when sending messages to channels that
170+
* aren't fully set up yet. For example, the engine isn't running yet or the channel's message
171+
* handler isn't set up on the Dart side yet.
172+
*/
173+
public static void resizeChannelBuffer(
174+
@NonNull BinaryMessenger messenger, @NonNull String channel, int newSize) {
175+
final StandardMethodCodec codec = StandardMethodCodec.INSTANCE;
176+
Object[] arguments = {channel, newSize};
177+
MethodCall methodCall = new MethodCall("resize", Arrays.asList(arguments));
178+
ByteBuffer message = codec.encodeMethodCall(methodCall);
179+
ByteBuffer packet = packetFromEncodedMessage(message);
180+
messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, packet);
181+
}
182+
183+
/**
184+
* Toggles whether the channel should show warning messages when discarding messages due to
185+
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
186+
* not be shown.
187+
*/
188+
public static void allowChannelBufferOverflow(
189+
@NonNull BinaryMessenger messenger, @NonNull String channel, boolean allowed) {
190+
final StandardMethodCodec codec = StandardMethodCodec.INSTANCE;
191+
Object[] arguments = {channel, allowed};
192+
MethodCall methodCall = new MethodCall("overflow", Arrays.asList(arguments));
193+
ByteBuffer message = codec.encodeMethodCall(methodCall);
194+
ByteBuffer packet = packetFromEncodedMessage(message);
153195
messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, packet);
154196
}
155197

shell/platform/android/io/flutter/plugin/common/MethodChannel.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ public void resizeChannelBuffer(int newSize) {
157157
BasicMessageChannel.resizeChannelBuffer(messenger, name, newSize);
158158
}
159159

160+
/**
161+
* Toggles whether the channel should show warning messages when discarding messages due to
162+
* overflow. When 'allowed' is true the channel is expected to overflow and warning messages will
163+
* not be shown.
164+
*/
165+
public void allowChannelBufferOverflow(boolean allowed) {
166+
BasicMessageChannel.allowChannelBufferOverflow(messenger, name, allowed);
167+
}
168+
160169
/** A handler of incoming method calls. */
161170
public interface MethodCallHandler {
162171
/**
Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.flutter.plugin.common;
22

33
import static org.mockito.ArgumentMatchers.anyInt;
4+
import static org.mockito.ArgumentMatchers.argThat;
45
import static org.mockito.ArgumentMatchers.eq;
56
import static org.mockito.Mockito.mock;
67
import static org.mockito.Mockito.times;
@@ -11,17 +12,16 @@
1112
import io.flutter.embedding.engine.FlutterJNI;
1213
import io.flutter.embedding.engine.dart.DartExecutor;
1314
import java.nio.ByteBuffer;
14-
import java.nio.charset.Charset;
15-
import java.util.Locale;
1615
import org.junit.Test;
1716
import org.junit.runner.RunWith;
17+
import org.mockito.ArgumentMatcher;
1818
import org.robolectric.annotation.Config;
1919

2020
@Config(manifest = Config.NONE)
2121
@RunWith(AndroidJUnit4.class)
2222
public class MethodChannelTest {
2323
@Test
24-
public void methodChannel_resizeChannelBuffer() {
24+
public void resizeChannelBufferMessageIsWellformed() {
2525
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
2626
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
2727
String channel = "flutter/test";
@@ -30,15 +30,68 @@ public void methodChannel_resizeChannelBuffer() {
3030
int newSize = 3;
3131
rawChannel.resizeChannelBuffer(newSize);
3232

33-
Charset charset = Charset.forName("UTF-8");
34-
String messageString = String.format(Locale.US, "resize\r%s\r%d", channel, newSize);
35-
final byte[] bytes = messageString.getBytes(charset);
36-
ByteBuffer packet = ByteBuffer.allocateDirect(bytes.length);
37-
packet.put(bytes);
33+
// Created from the following Dart code:
34+
// MethodCall methodCall = const MethodCall('resize', ['flutter/test', 3]);
35+
// const StandardMethodCodec().encodeMethodCall(methodCall).buffer.asUint8List();
36+
final byte[] expected = {
37+
7, 6, 114, 101, 115, 105, 122, 101, 12, 2, 7, 12, 102, 108, 117, 116, 116, 101, 114, 47, 116,
38+
101, 115, 116, 3, 3, 0, 0, 0
39+
};
3840

39-
// Verify that DartExecutor sent the correct message to FlutterJNI.
41+
// Verify that the correct message was sent to FlutterJNI.
42+
ArgumentMatcher<ByteBuffer> packetMatcher =
43+
new ByteBufferContentMatcher(ByteBuffer.wrap(expected));
4044
verify(mockFlutterJNI, times(1))
4145
.dispatchPlatformMessage(
42-
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL), eq(packet), anyInt(), anyInt());
46+
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL),
47+
argThat(packetMatcher),
48+
anyInt(),
49+
anyInt());
50+
}
51+
52+
@Test
53+
public void overflowChannelBufferMessageIsWellformed() {
54+
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
55+
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
56+
String channel = "flutter/test";
57+
MethodChannel rawChannel = new MethodChannel(dartExecutor, channel);
58+
59+
rawChannel.allowChannelBufferOverflow(true);
60+
61+
// Created from the following Dart code:
62+
// MethodCall methodCall = const MethodCall('overflow', ['flutter/test', true]);
63+
// const StandardMethodCodec().encodeMethodCall(methodCall).buffer.asUint8List();
64+
final byte[] expected = {
65+
7, 8, 111, 118, 101, 114, 102, 108, 111, 119, 12, 2, 7, 12, 102, 108, 117, 116, 116, 101, 114,
66+
47, 116, 101, 115, 116, 1
67+
};
68+
69+
// Verify that the correct message was sent to FlutterJNI.
70+
ArgumentMatcher<ByteBuffer> packetMatcher =
71+
new ByteBufferContentMatcher(ByteBuffer.wrap(expected));
72+
verify(mockFlutterJNI, times(1))
73+
.dispatchPlatformMessage(
74+
eq(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL),
75+
argThat(packetMatcher),
76+
anyInt(),
77+
anyInt());
78+
}
79+
}
80+
81+
// Custom ByteBuffer matcher which calls rewind on both buffers before calling equals.
82+
// ByteBuffer.equals might return true when comparing byte buffers with different content if
83+
// both have no remaining elements.
84+
class ByteBufferContentMatcher implements ArgumentMatcher<ByteBuffer> {
85+
private ByteBuffer expected;
86+
87+
public ByteBufferContentMatcher(ByteBuffer expected) {
88+
this.expected = expected;
89+
}
90+
91+
@Override
92+
public boolean matches(ByteBuffer received) {
93+
expected.rewind();
94+
received.rewind();
95+
return received.equals(expected);
4396
}
4497
}

0 commit comments

Comments
 (0)