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

Commit 7f5d044

Browse files
author
Emmanuel Garcia
authored
Wait before switching surfaces (#20100)
1 parent 280bbfc commit 7f5d044

File tree

6 files changed

+291
-50
lines changed

6 files changed

+291
-50
lines changed

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

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -953,13 +953,22 @@ public void detachFromFlutterEngine() {
953953
flutterEngine = null;
954954
}
955955

956+
@VisibleForTesting
957+
@NonNull
958+
public FlutterImageView createImageView() {
959+
return new FlutterImageView(
960+
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
961+
}
962+
963+
/**
964+
* Converts the current render surface to a {@link FlutterImageView} if it's not one already.
965+
* Otherwise, it resizes the {@link FlutterImageView} based on the current view size.
966+
*/
956967
public void convertToImageView() {
957968
renderSurface.pause();
958969

959970
if (flutterImageView == null) {
960-
flutterImageView =
961-
new FlutterImageView(
962-
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
971+
flutterImageView = createImageView();
963972
addView(flutterImageView);
964973
} else {
965974
flutterImageView.resizeIfNeeded(getWidth(), getHeight());
@@ -973,10 +982,13 @@ public void convertToImageView() {
973982
}
974983

975984
/**
976-
* If the surface is rendered by a {@code FlutterImageView}. Then, calling this method will stop
977-
* rendering to a {@code FlutterImageView}, and use the previous surface instead.
985+
* If the surface is rendered by a {@link FlutterImageView}, then calling this method will stop
986+
* rendering to a {@link FlutterImageView}, and render on the previous surface instead.
987+
*
988+
* @param onDone a callback called when Flutter UI is rendered on the previous surface. Use this
989+
* callback to perform cleanups. For example, destroy overlay surfaces.
978990
*/
979-
public void revertImageView() {
991+
public void revertImageView(@NonNull Runnable onDone) {
980992
if (flutterImageView == null) {
981993
Log.v(TAG, "Tried to revert the image view, but no image view is used.");
982994
return;
@@ -985,12 +997,39 @@ public void revertImageView() {
985997
Log.v(TAG, "Tried to revert the image view, but no previous surface was used.");
986998
return;
987999
}
988-
flutterImageView.detachFromRenderer();
9891000
renderSurface = previousRenderSurface;
9901001
previousRenderSurface = null;
991-
if (flutterEngine != null) {
992-
renderSurface.attachToRenderer(flutterEngine.getRenderer());
1002+
if (flutterEngine == null) {
1003+
flutterImageView.detachFromRenderer();
1004+
onDone.run();
1005+
return;
1006+
}
1007+
final FlutterRenderer renderer = flutterEngine.getRenderer();
1008+
if (renderer == null) {
1009+
flutterImageView.detachFromRenderer();
1010+
onDone.run();
1011+
return;
9931012
}
1013+
// Start rendering on the previous surface.
1014+
// This surface is typically `FlutterSurfaceView` or `FlutterTextureView`.
1015+
renderSurface.attachToRenderer(renderer);
1016+
1017+
// Install a Flutter UI listener to wait until the first frame is rendered
1018+
// in the new surface to call the `onDone` callback.
1019+
renderer.addIsDisplayingFlutterUiListener(
1020+
new FlutterUiDisplayListener() {
1021+
@Override
1022+
public void onFlutterUiDisplayed() {
1023+
renderer.removeIsDisplayingFlutterUiListener(this);
1024+
onDone.run();
1025+
flutterImageView.detachFromRenderer();
1026+
}
1027+
1028+
@Override
1029+
public void onFlutterUiNoLongerDisplayed() {
1030+
// no-op
1031+
}
1032+
});
9941033
}
9951034

9961035
public void attachOverlaySurfaceToRender(FlutterImageView view) {

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@
9090
*
9191
* <p>To invoke a native method that is not associated with a platform view, invoke it statically:
9292
*
93-
* <p>{@code bool enabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled(); }
93+
* <p>{@code bool enabled = FlutterJNI.getIsSoftwareRenderingEnabled(); }
9494
*/
9595
@Keep
9696
public class FlutterJNI {
@@ -120,9 +120,13 @@ public static native void nativeInit(
120120
*/
121121
public static native void nativePrefetchDefaultFontManager();
122122

123-
// TODO(mattcarroll): add javadocs
123+
private native boolean nativeGetIsSoftwareRenderingEnabled();
124+
124125
@UiThread
125-
public native boolean nativeGetIsSoftwareRenderingEnabled();
126+
// TODO(mattcarroll): add javadocs
127+
public boolean getIsSoftwareRenderingEnabled() {
128+
return nativeGetIsSoftwareRenderingEnabled();
129+
}
126130

127131
@Nullable
128132
// TODO(mattcarroll): add javadocs
@@ -212,7 +216,12 @@ public boolean isAttached() {
212216
public void attachToNative(boolean isBackgroundView) {
213217
ensureRunningOnMainThread();
214218
ensureNotAttachedToNative();
215-
nativePlatformViewId = nativeAttach(this, isBackgroundView);
219+
nativePlatformViewId = performNativeAttach(this, isBackgroundView);
220+
}
221+
222+
@VisibleForTesting
223+
public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView) {
224+
return nativeAttach(flutterJNI, isBackgroundView);
216225
}
217226

218227
private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView);
@@ -279,7 +288,7 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene
279288
@SuppressWarnings("unused")
280289
@VisibleForTesting
281290
@UiThread
282-
void onFirstFrame() {
291+
public void onFirstFrame() {
283292
ensureRunningOnMainThread();
284293

285294
for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ private void unregisterTexture(long textureId) {
314314

315315
// TODO(mattcarroll): describe the native behavior that this invokes
316316
public boolean isSoftwareRenderingEnabled() {
317-
return flutterJNI.nativeGetIsSoftwareRenderingEnabled();
317+
return flutterJNI.getIsSoftwareRenderingEnabled();
318318
}
319319

320320
// TODO(mattcarroll): describe the native behavior that this invokes

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ public void onDisplayPlatformView(
713713
int width,
714714
int height,
715715
int viewWidth,
716-
int ViewHeight,
716+
int viewHeight,
717717
FlutterMutatorsStack mutatorsStack) {
718718
initializeRootImageViewIfNeeded();
719719
initializePlatformViewIfNeeded(viewId);
@@ -723,7 +723,7 @@ public void onDisplayPlatformView(
723723
mutatorView.setVisibility(View.VISIBLE);
724724
mutatorView.bringToFront();
725725

726-
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
726+
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, viewHeight);
727727
View platformView = platformViews.get(viewId);
728728
platformView.setLayoutParams(layoutParams);
729729
platformView.bringToFront();
@@ -753,6 +753,20 @@ public void onBeginFrame() {
753753
}
754754

755755
public void onEndFrame() {
756+
final FlutterView view = (FlutterView) flutterView;
757+
// If there are no platform views in the current frame,
758+
// then revert the image view surface and use the previous surface.
759+
//
760+
// Otherwise, acquire the latest image.
761+
if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) {
762+
flutterViewConvertedToImageView = false;
763+
view.revertImageView(
764+
() -> {
765+
// Destroy overlay surfaces once the surface reversion is completed.
766+
finishFrame(false);
767+
});
768+
return;
769+
}
756770
// Whether the current frame was rendered using ImageReaders.
757771
//
758772
// Since the image readers may not have images available at this point,
@@ -762,22 +776,12 @@ public void onEndFrame() {
762776
// If one of the surfaces doesn't have an image, the frame may be incomplete and must be
763777
// dropped.
764778
// For example, a toolbar widget painted by Flutter may not be rendered.
765-
boolean isFrameRenderedUsingImageReaders = false;
766-
767-
if (flutterViewConvertedToImageView) {
768-
FlutterView view = (FlutterView) flutterView;
769-
// If there are no platform views in the current frame,
770-
// then revert the image view surface and use the previous surface.
771-
//
772-
// Otherwise, acquire the latest image.
773-
if (currentFrameUsedPlatformViewIds.isEmpty()) {
774-
view.revertImageView();
775-
flutterViewConvertedToImageView = false;
776-
} else {
777-
isFrameRenderedUsingImageReaders = view.acquireLatestImageViewFrame();
778-
}
779-
}
779+
boolean isFrameRenderedUsingImageReaders =
780+
flutterViewConvertedToImageView && view.acquireLatestImageViewFrame();
781+
finishFrame(isFrameRenderedUsingImageReaders);
782+
}
780783

784+
private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
781785
for (int i = 0; i < overlayLayerViews.size(); i++) {
782786
int overlayId = overlayLayerViews.keyAt(i);
783787
FlutterImageView overlayView = overlayLayerViews.valueAt(i);
@@ -818,6 +822,14 @@ public void onEndFrame() {
818822
}
819823
}
820824

825+
@VisibleForTesting
826+
@TargetApi(19)
827+
public FlutterOverlaySurface createOverlaySurface(@NonNull FlutterImageView imageView) {
828+
final int id = nextOverlayLayerId++;
829+
overlayLayerViews.put(id, imageView);
830+
return new FlutterOverlaySurface(id, imageView.getSurface());
831+
}
832+
821833
@TargetApi(19)
822834
public FlutterOverlaySurface createOverlaySurface() {
823835
// Overlay surfaces have the same size as the background surface.
@@ -826,17 +838,12 @@ public FlutterOverlaySurface createOverlaySurface() {
826838
// if the drawings they contain have a different tight bound.
827839
//
828840
// The final view size is determined when its frame is set.
829-
FlutterImageView imageView =
841+
return createOverlaySurface(
830842
new FlutterImageView(
831843
flutterView.getContext(),
832844
flutterView.getWidth(),
833845
flutterView.getHeight(),
834-
FlutterImageView.SurfaceKind.overlay);
835-
836-
int id = nextOverlayLayerId++;
837-
overlayLayerViews.put(id, imageView);
838-
839-
return new FlutterOverlaySurface(id, imageView.getSurface());
846+
FlutterImageView.SurfaceKind.overlay));
840847
}
841848

842849
public void destroyOverlaySurfaces() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public FlutterView(Context context, AttributeSet attrs, FlutterNativeView native
169169

170170
dartExecutor = mNativeView.getDartExecutor();
171171
flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
172-
mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().nativeGetIsSoftwareRenderingEnabled();
172+
mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().getIsSoftwareRenderingEnabled();
173173
mMetrics = new ViewportMetrics();
174174
mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
175175
setFocusable(true);

0 commit comments

Comments
 (0)