Skip to content

Commit 7119498

Browse files
Don't re-initialize the default RenderSurface when returning from hybrid composition mode (flutter#47358)
When we enter hybrid composition mode we 'pause' the default RenderSurface (implemented by SurfaceView or TextureView) and swap to an ImageReader based RenderSurface. When we return from hybrid composition mode we recreate and re-initialize the real RenderSurface as if it was being used for the first time. This broke Platform Views in an internal app b/306122497 because we would incorrectly tell the texture to attach when it was never detached. This CL changes the protocol so that when we return from hybrid composition mode we only swap the RenderSurface and do not re-create it. This avoids doing a bunch of unnecessary work and fixes the logic error of re-attaching textures that were never detached. *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* *List which issues are fixed by this PR. You must list at least one issue.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent bc15dff commit 7119498

9 files changed

Lines changed: 157 additions & 81 deletions

File tree

shell/common/shell.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,6 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
814814
const bool should_post_raster_task =
815815
!task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread();
816816

817-
fml::AutoResetWaitableEvent latch;
818817
auto raster_task = fml::MakeCopyable(
819818
[&waiting_for_first_frame = waiting_for_first_frame_, //
820819
rasterizer = rasterizer_->GetWeakPtr(), //
@@ -841,8 +840,8 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
841840
// weak pointer. However, we are preventing the platform view from being
842841
// collected by using a latch.
843842
auto* platform_view = platform_view_.get();
844-
845843
FML_DCHECK(platform_view);
844+
fml::AutoResetWaitableEvent latch;
846845

847846
auto io_task = [io_manager = io_manager_->GetWeakPtr(), platform_view,
848847
ui_task_runner = task_runners_.GetUITaskRunner(), ui_task,

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ public void pause() {
186186
// Not supported.
187187
}
188188

189+
public void resume() {
190+
// Not supported.
191+
}
192+
189193
/**
190194
* Acquires the next image to be drawn to the {@link android.graphics.Canvas}. Returns true if
191195
* there's an image available in the queue.

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

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@ public class FlutterSurfaceView extends SurfaceView implements RenderSurface {
3838
private final boolean renderTransparently;
3939
private boolean isSurfaceAvailableForRendering = false;
4040
private boolean isPaused = false;
41-
private boolean isAttachedToFlutterRenderer = false;
4241
@Nullable private FlutterRenderer flutterRenderer;
4342

43+
private boolean shouldNotify() {
44+
return flutterRenderer != null && !isPaused;
45+
}
46+
4447
// Connects the {@code Surface} beneath this {@code SurfaceView} with Flutter's native code.
4548
// Callbacks are received by this Object and then those messages are forwarded to our
4649
// FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
@@ -51,7 +54,7 @@ public void surfaceCreated(@NonNull SurfaceHolder holder) {
5154
Log.v(TAG, "SurfaceHolder.Callback.startRenderingToSurface()");
5255
isSurfaceAvailableForRendering = true;
5356

54-
if (isAttachedToFlutterRenderer) {
57+
if (shouldNotify()) {
5558
connectSurfaceToRenderer();
5659
}
5760
}
@@ -60,7 +63,7 @@ public void surfaceCreated(@NonNull SurfaceHolder holder) {
6063
public void surfaceChanged(
6164
@NonNull SurfaceHolder holder, int format, int width, int height) {
6265
Log.v(TAG, "SurfaceHolder.Callback.surfaceChanged()");
63-
if (isAttachedToFlutterRenderer) {
66+
if (shouldNotify()) {
6467
changeSurfaceSize(width, height);
6568
}
6669
}
@@ -70,7 +73,7 @@ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
7073
Log.v(TAG, "SurfaceHolder.Callback.stopRenderingToSurface()");
7174
isSurfaceAvailableForRendering = false;
7275

73-
if (isAttachedToFlutterRenderer) {
76+
if (shouldNotify()) {
7477
disconnectSurfaceFromRenderer();
7578
}
7679
}
@@ -183,25 +186,15 @@ public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
183186
if (this.flutterRenderer != null) {
184187
Log.v(
185188
TAG,
186-
"Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
189+
"Already connected to a FlutterRenderer. Detaching from old one and attaching to new"
190+
+ " one.");
187191
this.flutterRenderer.stopRenderingToSurface();
188192
this.flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
189193
}
190194

191195
this.flutterRenderer = flutterRenderer;
192-
isAttachedToFlutterRenderer = true;
193196

194-
this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
195-
196-
// If we're already attached to an Android window then we're now attached to both a renderer
197-
// and the Android window. We can begin rendering now.
198-
if (isSurfaceAvailableForRendering) {
199-
Log.v(
200-
TAG,
201-
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
202-
connectSurfaceToRenderer();
203-
}
204-
isPaused = false;
197+
resume();
205198
}
206199

207200
/**
@@ -222,13 +215,13 @@ public void detachFromRenderer() {
222215
disconnectSurfaceFromRenderer();
223216
}
224217

218+
pause();
219+
225220
// Make the SurfaceView invisible to avoid showing a black rectangle.
226221
setAlpha(0.0f);
227-
228222
flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
229-
230223
flutterRenderer = null;
231-
isAttachedToFlutterRenderer = false;
224+
232225
} else {
233226
Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
234227
}
@@ -239,22 +232,37 @@ public void detachFromRenderer() {
239232
* UI to this {@code FlutterSurfaceView}.
240233
*/
241234
public void pause() {
242-
if (flutterRenderer != null) {
243-
// Don't remove the `flutterUiDisplayListener` as `onFlutterUiDisplayed()` will make
244-
// the `FlutterSurfaceView` visible.
245-
flutterRenderer = null;
246-
isPaused = true;
247-
isAttachedToFlutterRenderer = false;
248-
} else {
235+
if (flutterRenderer == null) {
249236
Log.w(TAG, "pause() invoked when no FlutterRenderer was attached.");
237+
return;
238+
}
239+
isPaused = true;
240+
}
241+
242+
public void resume() {
243+
if (flutterRenderer == null) {
244+
Log.w(TAG, "resume() invoked when no FlutterRenderer was attached.");
245+
return;
246+
}
247+
this.flutterRenderer.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
248+
249+
// If we're already attached to an Android window then we're now attached to both a renderer
250+
// and the Android window. We can begin rendering now.
251+
if (isSurfaceAvailableForRendering) {
252+
Log.v(
253+
TAG,
254+
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
255+
connectSurfaceToRenderer();
250256
}
257+
isPaused = false;
251258
}
252259

253260
// FlutterRenderer and getSurfaceTexture() must both be non-null.
254261
private void connectSurfaceToRenderer() {
255262
if (flutterRenderer == null || getHolder() == null) {
256263
throw new IllegalStateException(
257-
"connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null.");
264+
"connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder()"
265+
+ " are non-null.");
258266
}
259267
// When connecting the surface to the renderer, it's possible that the surface is currently
260268
// paused. For instance, when a platform view is displayed, the current FlutterSurfaceView
@@ -285,9 +293,10 @@ private void changeSurfaceSize(int width, int height) {
285293
private void disconnectSurfaceFromRenderer() {
286294
if (flutterRenderer == null) {
287295
throw new IllegalStateException(
288-
"disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
296+
"disconnectSurfaceFromRenderer() should only be called when flutterRenderer is"
297+
+ " non-null.");
289298
}
290299

291300
flutterRenderer.stopRenderingToSurface();
292301
}
293-
}
302+
}

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

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,14 @@ public class FlutterTextureView extends TextureView implements RenderSurface {
3535
private static final String TAG = "FlutterTextureView";
3636

3737
private boolean isSurfaceAvailableForRendering = false;
38-
private boolean isAttachedToFlutterRenderer = false;
3938
private boolean isPaused = false;
4039
@Nullable private FlutterRenderer flutterRenderer;
4140
@Nullable private Surface renderSurface;
4241

42+
private boolean shouldNotify() {
43+
return flutterRenderer != null && !isPaused;
44+
}
45+
4346
// Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native
4447
// code.
4548
// Callbacks are received by this Object and then those messages are forwarded to our
@@ -55,7 +58,7 @@ public void onSurfaceTextureAvailable(
5558
// If we're already attached to a FlutterRenderer then we're now attached to both a
5659
// renderer
5760
// and the Android window, so we can begin rendering now.
58-
if (isAttachedToFlutterRenderer) {
61+
if (shouldNotify()) {
5962
connectSurfaceToRenderer();
6063
}
6164
}
@@ -64,7 +67,7 @@ public void onSurfaceTextureAvailable(
6467
public void onSurfaceTextureSizeChanged(
6568
@NonNull SurfaceTexture surface, int width, int height) {
6669
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureSizeChanged()");
67-
if (isAttachedToFlutterRenderer) {
70+
if (shouldNotify()) {
6871
changeSurfaceSize(width, height);
6972
}
7073
}
@@ -82,7 +85,7 @@ public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
8285
// If we're attached to a FlutterRenderer then we need to notify it that our
8386
// SurfaceTexture
8487
// has been destroyed.
85-
if (isAttachedToFlutterRenderer) {
88+
if (shouldNotify()) {
8689
disconnectSurfaceFromRenderer();
8790
}
8891

@@ -139,21 +142,14 @@ public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
139142
if (this.flutterRenderer != null) {
140143
Log.v(
141144
TAG,
142-
"Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
145+
"Already connected to a FlutterRenderer. Detaching from old one and attaching to new"
146+
+ " one.");
143147
this.flutterRenderer.stopRenderingToSurface();
144148
}
145149

146150
this.flutterRenderer = flutterRenderer;
147-
isAttachedToFlutterRenderer = true;
148151

149-
// If we're already attached to an Android window then we're now attached to both a renderer
150-
// and the Android window. We can begin rendering now.
151-
if (isSurfaceAvailableForRendering) {
152-
Log.v(
153-
TAG,
154-
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
155-
connectSurfaceToRenderer();
156-
}
152+
resume();
157153
}
158154

159155
/**
@@ -174,8 +170,9 @@ public void detachFromRenderer() {
174170
disconnectSurfaceFromRenderer();
175171
}
176172

173+
pause();
174+
177175
flutterRenderer = null;
178-
isAttachedToFlutterRenderer = false;
179176
} else {
180177
Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
181178
}
@@ -186,13 +183,28 @@ public void detachFromRenderer() {
186183
* UI to this {@code FlutterTextureView}.
187184
*/
188185
public void pause() {
189-
if (flutterRenderer != null) {
190-
flutterRenderer = null;
191-
isPaused = true;
192-
isAttachedToFlutterRenderer = false;
193-
} else {
186+
if (flutterRenderer == null) {
194187
Log.w(TAG, "pause() invoked when no FlutterRenderer was attached.");
188+
return;
195189
}
190+
isPaused = true;
191+
}
192+
193+
public void resume() {
194+
if (flutterRenderer == null) {
195+
Log.w(TAG, "resume() invoked when no FlutterRenderer was attached.");
196+
return;
197+
}
198+
199+
// If we're already attached to an Android window then we're now attached to both a renderer
200+
// and the Android window. We can begin rendering now.
201+
if (isSurfaceAvailableForRendering) {
202+
Log.v(
203+
TAG,
204+
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
205+
connectSurfaceToRenderer();
206+
}
207+
isPaused = false;
196208
}
197209

198210
/**
@@ -209,7 +221,8 @@ public void setRenderSurface(Surface renderSurface) {
209221
private void connectSurfaceToRenderer() {
210222
if (flutterRenderer == null || getSurfaceTexture() == null) {
211223
throw new IllegalStateException(
212-
"connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null.");
224+
"connectSurfaceToRenderer() should only be called when flutterRenderer and"
225+
+ " getSurfaceTexture() are non-null.");
213226
}
214227

215228
// Definitively release the surface to avoid leaked closeables, just in case
@@ -220,7 +233,6 @@ private void connectSurfaceToRenderer() {
220233

221234
renderSurface = new Surface(getSurfaceTexture());
222235
flutterRenderer.startRenderingToSurface(renderSurface, isPaused);
223-
isPaused = false;
224236
}
225237

226238
// FlutterRenderer must be non-null.
@@ -243,7 +255,8 @@ private void changeSurfaceSize(int width, int height) {
243255
private void disconnectSurfaceFromRenderer() {
244256
if (flutterRenderer == null) {
245257
throw new IllegalStateException(
246-
"disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
258+
"disconnectSurfaceFromRenderer() should only be called when flutterRenderer is"
259+
+ " non-null.");
247260
}
248261

249262
flutterRenderer.stopRenderingToSurface();
@@ -252,4 +265,4 @@ private void disconnectSurfaceFromRenderer() {
252265
renderSurface = null;
253266
}
254267
}
255-
}
268+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,9 +1369,9 @@ public void revertImageView(@NonNull Runnable onDone) {
13691369
onDone.run();
13701370
return;
13711371
}
1372-
// Start rendering on the previous surface.
1372+
// Resume rendering to the previous surface.
13731373
// This surface is typically `FlutterSurfaceView` or `FlutterTextureView`.
1374-
renderSurface.attachToRenderer(renderer);
1374+
renderSurface.resume();
13751375

13761376
// Install a Flutter UI listener to wait until the first frame is rendered
13771377
// in the new surface to call the `onDone` callback.

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -467,20 +467,26 @@ protected void finalize() throws Throwable {
467467
* android.view.TextureView.SurfaceTextureListener}
468468
*
469469
* @param surface The render surface.
470-
* @param keepCurrentSurface True if the current active surface should not be released.
470+
* @param onlySwap True if the current active surface should not be detached.
471471
*/
472-
public void startRenderingToSurface(@NonNull Surface surface, boolean keepCurrentSurface) {
473-
// Don't stop rendering the surface if it's currently paused.
474-
// Stop rendering to the surface releases the associated native resources, which
475-
// causes a glitch when showing platform views.
476-
// For more, https://github.com/flutter/flutter/issues/95343
477-
if (this.surface != null && !keepCurrentSurface) {
472+
public void startRenderingToSurface(@NonNull Surface surface, boolean onlySwap) {
473+
if (!onlySwap) {
474+
// Stop rendering to the surface releases the associated native resources, which
475+
// causes a glitch when toggling between rendering to an image view (hybrid composition) and
476+
// rendering directly to a Surface or Texture view. For more,
477+
// https://github.com/flutter/flutter/issues/95343
478478
stopRenderingToSurface();
479479
}
480480

481481
this.surface = surface;
482482

483-
flutterJNI.onSurfaceCreated(surface);
483+
if (onlySwap) {
484+
// In the swap case we are just swapping the surface that we render to.
485+
flutterJNI.onSurfaceWindowChanged(surface);
486+
} else {
487+
// In the non-swap case we are creating a new surface to render to.
488+
flutterJNI.onSurfaceCreated(surface);
489+
}
484490
}
485491

486492
/**

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,11 @@ public interface RenderSurface {
6060
* #attachToRenderer(FlutterRenderer)}.
6161
*/
6262
void pause();
63+
64+
/**
65+
* Instructs this {@code RenderSurface} to resume forwarding {@code Surface} notifications to the
66+
* {@code FlutterRenderer} that was previously connected with {@link
67+
* #attachToRenderer(FlutterRenderer)}.
68+
*/
69+
void resume();
6370
}

shell/platform/android/platform_view_android.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ void PlatformViewAndroid::NotifySurfaceWindowChanged(
197197
});
198198
latch.Wait();
199199
}
200+
201+
PlatformView::ScheduleFrame();
200202
}
201203

202204
void PlatformViewAndroid::NotifyDestroyed() {

0 commit comments

Comments
 (0)