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

Commit 8b9f625

Browse files
author
Emmanuel Garcia
authored
Remove glitch when displaying platform views (#30724)
1 parent 358605c commit 8b9f625

File tree

7 files changed

+75
-25
lines changed

7 files changed

+75
-25
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class FlutterSurfaceView extends SurfaceView implements RenderSurface {
3737

3838
private final boolean renderTransparently;
3939
private boolean isSurfaceAvailableForRendering = false;
40+
private boolean isPaused = false;
4041
private boolean isAttachedToFlutterRenderer = false;
4142
@Nullable private FlutterRenderer flutterRenderer;
4243

@@ -200,6 +201,7 @@ public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
200201
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
201202
connectSurfaceToRenderer();
202203
}
204+
isPaused = false;
203205
}
204206

205207
/**
@@ -241,6 +243,7 @@ public void pause() {
241243
// Don't remove the `flutterUiDisplayListener` as `onFlutterUiDisplayed()` will make
242244
// the `FlutterSurfaceView` visible.
243245
flutterRenderer = null;
246+
isPaused = true;
244247
isAttachedToFlutterRenderer = false;
245248
} else {
246249
Log.w(TAG, "pause() invoked when no FlutterRenderer was attached.");
@@ -253,8 +256,13 @@ private void connectSurfaceToRenderer() {
253256
throw new IllegalStateException(
254257
"connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null.");
255258
}
256-
257-
flutterRenderer.startRenderingToSurface(getHolder().getSurface());
259+
// When connecting the surface to the renderer, it's possible that the surface is currently
260+
// paused. For instance, when a platform view is displayed, the current FlutterSurfaceView
261+
// is paused, and rendering continues in a FlutterImageView buffer while the platform view
262+
// is displayed.
263+
//
264+
// startRenderingToSurface stops rendering to an active surface if it isn't paused.
265+
flutterRenderer.startRenderingToSurface(getHolder().getSurface(), isPaused);
258266
}
259267

260268
// FlutterRenderer must be non-null.

shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public class FlutterTextureView extends TextureView implements RenderSurface {
3636

3737
private boolean isSurfaceAvailableForRendering = false;
3838
private boolean isAttachedToFlutterRenderer = false;
39+
private boolean isPaused = false;
3940
@Nullable private FlutterRenderer flutterRenderer;
4041
@Nullable private Surface renderSurface;
4142

@@ -187,6 +188,7 @@ public void detachFromRenderer() {
187188
public void pause() {
188189
if (flutterRenderer != null) {
189190
flutterRenderer = null;
191+
isPaused = true;
190192
isAttachedToFlutterRenderer = false;
191193
} else {
192194
Log.w(TAG, "pause() invoked when no FlutterRenderer was attached.");
@@ -217,7 +219,8 @@ private void connectSurfaceToRenderer() {
217219
}
218220

219221
renderSurface = new Surface(getSurfaceTexture());
220-
flutterRenderer.startRenderingToSurface(renderSurface);
222+
flutterRenderer.startRenderingToSurface(renderSurface, isPaused);
223+
isPaused = false;
221224
}
222225

223226
// FlutterRenderer must be non-null.

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public void onEngineWillDestroy() {
134134
*
135135
* <p>A new {@code FlutterEngine} will not display any UI until a {@link RenderSurface} is
136136
* registered. See {@link #getRenderer()} and {@link
137-
* FlutterRenderer#startRenderingToSurface(Surface)}.
137+
* FlutterRenderer#startRenderingToSurface(Surface, boolean)}.
138138
*
139139
* <p>A new {@code FlutterEngine} automatically attaches all plugins. See {@link #getPlugins()}.
140140
*

shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,24 @@ public void run() {
221221
* Notifies Flutter that the given {@code surface} was created and is available for Flutter
222222
* rendering.
223223
*
224+
* <p>If called more than once, the current native resources are released. This can be undesired
225+
* if the Engine expects to reuse this surface later. For example, this is true when platform
226+
* views are displayed in a frame, and then removed in the next frame.
227+
*
228+
* <p>To avoid releasing the current surface resources, set {@code keepCurrentSurface} to true.
229+
*
224230
* <p>See {@link android.view.SurfaceHolder.Callback} and {@link
225231
* android.view.TextureView.SurfaceTextureListener}
232+
*
233+
* @param surface The render surface.
234+
* @param keepCurrentSurface True if the current active surface should not be released.
226235
*/
227-
public void startRenderingToSurface(@NonNull Surface surface) {
228-
if (this.surface != null) {
236+
public void startRenderingToSurface(@NonNull Surface surface, boolean keepCurrentSurface) {
237+
// Don't stop rendering the surface if it's currently paused.
238+
// Stop rendering to the surface releases the associated native resources, which
239+
// causes a glitch when showing platform views.
240+
// For more, https://github.com/flutter/flutter/issues/95343
241+
if (this.surface != null && !keepCurrentSurface) {
229242
stopRenderingToSurface();
230243
}
231244

@@ -248,8 +261,8 @@ public void swapSurface(@NonNull Surface surface) {
248261

249262
/**
250263
* Notifies Flutter that a {@code surface} previously registered with {@link
251-
* #startRenderingToSurface(Surface)} has changed size to the given {@code width} and {@code
252-
* height}.
264+
* #startRenderingToSurface(Surface, boolean)} has changed size to the given {@code width} and
265+
* {@code height}.
253266
*
254267
* <p>See {@link android.view.SurfaceHolder.Callback} and {@link
255268
* android.view.TextureView.SurfaceTextureListener}
@@ -260,8 +273,8 @@ public void surfaceChanged(int width, int height) {
260273

261274
/**
262275
* Notifies Flutter that a {@code surface} previously registered with {@link
263-
* #startRenderingToSurface(Surface)} has been destroyed and needs to be released and cleaned up
264-
* on the Flutter side.
276+
* #startRenderingToSurface(Surface, boolean)} has been destroyed and needs to be released and
277+
* cleaned up on the Flutter side.
265278
*
266279
* <p>See {@link android.view.SurfaceHolder.Callback} and {@link
267280
* android.view.TextureView.SurfaceTextureListener}

shell/platform/android/io/flutter/embedding/engine/renderer/RenderSurface.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public interface RenderSurface {
3737
* FlutterRenderer} at the appropriate times:
3838
*
3939
* <ol>
40-
* <li>{@link FlutterRenderer#startRenderingToSurface(Surface)}
40+
* <li>{@link FlutterRenderer#startRenderingToSurface(Surface, boolean)}
4141
* <li>{@link FlutterRenderer#surfaceChanged(int, int)}}
4242
* <li>{@link FlutterRenderer#stopRenderingToSurface()}
4343
* </ol>

shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import static org.mockito.ArgumentMatchers.anyInt;
77
import static org.mockito.ArgumentMatchers.eq;
88
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.never;
910
import static org.mockito.Mockito.times;
1011
import static org.mockito.Mockito.verify;
1112
import static org.mockito.Mockito.when;
@@ -45,7 +46,7 @@ public void itForwardsSurfaceCreationNotificationToFlutterJNI() {
4546
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
4647

4748
// Execute the behavior under test.
48-
flutterRenderer.startRenderingToSurface(fakeSurface);
49+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
4950

5051
// Verify the behavior under test.
5152
verify(fakeFlutterJNI, times(1)).onSurfaceCreated(eq(fakeSurface));
@@ -57,7 +58,7 @@ public void itForwardsSurfaceChangeNotificationToFlutterJNI() {
5758
Surface fakeSurface = mock(Surface.class);
5859
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
5960

60-
flutterRenderer.startRenderingToSurface(fakeSurface);
61+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
6162

6263
// Execute the behavior under test.
6364
flutterRenderer.surfaceChanged(100, 50);
@@ -72,7 +73,7 @@ public void itForwardsSurfaceDestructionNotificationToFlutterJNI() {
7273
Surface fakeSurface = mock(Surface.class);
7374
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
7475

75-
flutterRenderer.startRenderingToSurface(fakeSurface);
76+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
7677

7778
// Execute the behavior under test.
7879
flutterRenderer.stopRenderingToSurface();
@@ -87,10 +88,10 @@ public void itStopsRenderingToOneSurfaceBeforeRenderingToANewSurface() {
8788
Surface fakeSurface2 = mock(Surface.class);
8889
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
8990

90-
flutterRenderer.startRenderingToSurface(fakeSurface);
91+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
9192

9293
// Execute behavior under test.
93-
flutterRenderer.startRenderingToSurface(fakeSurface2);
94+
flutterRenderer.startRenderingToSurface(fakeSurface2, /*keepCurrentSurface=*/ false);
9495

9596
// Verify behavior under test.
9697
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed(); // notification of 1st surface's removal.
@@ -101,7 +102,7 @@ public void itStopsRenderingToSurfaceWhenRequested() {
101102
// Setup the test.
102103
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
103104

104-
flutterRenderer.startRenderingToSurface(fakeSurface);
105+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
105106

106107
// Execute the behavior under test.
107108
flutterRenderer.stopRenderingToSurface();
@@ -110,6 +111,32 @@ public void itStopsRenderingToSurfaceWhenRequested() {
110111
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
111112
}
112113

114+
@Test
115+
public void iStopsRenderingToSurfaceWhenSurfaceAlreadySet() {
116+
// Setup the test.
117+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
118+
119+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
120+
121+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
122+
123+
// Verify behavior under test.
124+
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
125+
}
126+
127+
@Test
128+
public void itNeverStopsRenderingToSurfaceWhenRequested() {
129+
// Setup the test.
130+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
131+
132+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
133+
134+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ true);
135+
136+
// Verify behavior under test.
137+
verify(fakeFlutterJNI, never()).onSurfaceDestroyed();
138+
}
139+
113140
@Test
114141
public void itStopsSurfaceTextureCallbackWhenDetached() {
115142
// Setup the test.
@@ -120,7 +147,7 @@ public void itStopsSurfaceTextureCallbackWhenDetached() {
120147
FlutterRenderer.SurfaceTextureRegistryEntry entry =
121148
(FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture();
122149

123-
flutterRenderer.startRenderingToSurface(fakeSurface);
150+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
124151

125152
// Execute the behavior under test.
126153
flutterRenderer.stopRenderingToSurface();
@@ -143,7 +170,7 @@ public void itRegistersExistingSurfaceTexture() {
143170
(FlutterRenderer.SurfaceTextureRegistryEntry)
144171
flutterRenderer.registerSurfaceTexture(surfaceTexture);
145172

146-
flutterRenderer.startRenderingToSurface(fakeSurface);
173+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
147174

148175
// Verify behavior under test.
149176
assertEquals(surfaceTexture, entry.surfaceTexture());
@@ -164,7 +191,7 @@ public void itUnregistersTextureWhenSurfaceTextureFinalized() {
164191
(FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture();
165192
long id = entry.id();
166193

167-
flutterRenderer.startRenderingToSurface(fakeSurface);
194+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
168195

169196
// Execute the behavior under test.
170197
runFinalization(entry);
@@ -190,7 +217,7 @@ public void itStopsUnregisteringTextureWhenDetached() {
190217
(FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture();
191218
long id = entry.id();
192219

193-
flutterRenderer.startRenderingToSurface(fakeSurface);
220+
flutterRenderer.startRenderingToSurface(fakeSurface, /*keepCurrentSurface=*/ false);
194221

195222
flutterRenderer.stopRenderingToSurface();
196223

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package io.flutter.plugin.platform;
22

3+
import static android.os.Looper.getMainLooper;
34
import static io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewTouch;
45
import static org.junit.Assert.*;
56
import static org.mockito.ArgumentMatchers.*;
67
import static org.mockito.Mockito.*;
8+
import static org.robolectric.Shadows.shadowOf;
79

810
import android.content.Context;
911
import android.content.res.AssetManager;
@@ -486,10 +488,7 @@ public void onEndFrame__destroysOverlaySurfaceAfterFrameOnFlutterSurfaceView() {
486488
platformViewsController.onBeginFrame();
487489
platformViewsController.onEndFrame();
488490

489-
verify(overlayImageView, never()).detachFromRenderer();
490-
491-
// Simulate first frame from the framework.
492-
jni.onFirstFrame();
491+
shadowOf(getMainLooper()).idle();
493492
verify(overlayImageView, times(1)).detachFromRenderer();
494493
}
495494

0 commit comments

Comments
 (0)