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

Wait before switching surfaces #20100

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -949,13 +949,22 @@ public void detachFromFlutterEngine() {
flutterEngine = null;
}

@VisibleForTesting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the tests in the same package (can this be package private)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

different packages unfortunately.io.flutter.embedding.android vs io.flutter.plugin.platform

@NonNull
public FlutterImageView createImageView() {
return new FlutterImageView(
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
}

/**
* Converts the current render surface to a {@link FlutterImageView} if it's not one already.
* Otherwise, it resizes the {@link FlutterImageView} based on the current view size.
*/
public void convertToImageView() {
renderSurface.pause();

if (flutterImageView == null) {
flutterImageView =
new FlutterImageView(
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
flutterImageView = createImageView();
addView(flutterImageView);
} else {
flutterImageView.resizeIfNeeded(getWidth(), getHeight());
Expand All @@ -969,10 +978,13 @@ public void convertToImageView() {
}

/**
* If the surface is rendered by a {@code FlutterImageView}. Then, calling this method will stop
* rendering to a {@code FlutterImageView}, and use the previous surface instead.
* If the surface is rendered by a {@link FlutterImageView}, then calling this method will stop
* rendering to a {@link FlutterImageView}, and render on the previous surface instead.
*
* @param onDone a callback called when Flutter UI is rendered on the previous surface. Use this
* callback to perform cleanups. For example, destroy overlay surfaces.
*/
public void revertImageView() {
public void revertImageView(@NonNull Runnable onDone) {
if (flutterImageView == null) {
Log.v(TAG, "Tried to revert the image view, but no image view is used.");
return;
Expand All @@ -981,12 +993,39 @@ public void revertImageView() {
Log.v(TAG, "Tried to revert the image view, but no previous surface was used.");
return;
}
flutterImageView.detachFromRenderer();
renderSurface = previousRenderSurface;
previousRenderSurface = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that there's a single instance variable for the previous surface seems weird... can you render an image view over an image view? should we keep a stack of surfaces?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack.

can you render an image view over an image view?

No.

Only FlutterSurfaceView/FlutterTextureView <-> FlutterImage, so a stack isn't needed as there could be 2 surfaces max.

if (flutterEngine != null) {
renderSurface.attachToRenderer(flutterEngine.getRenderer());
if (flutterEngine == null) {
flutterImageView.detachFromRenderer();
onDone.run();
return;
}
final FlutterRenderer renderer = flutterEngine.getRenderer();
if (renderer == null) {
flutterImageView.detachFromRenderer();
onDone.run();
return;
}
// Start rendering on the previous surface.
// This surface is typically `FlutterSurfaceView` or `FlutterTextureView`.
renderSurface.attachToRenderer(renderer);

// Install a Flutter UI listener to wait until the first frame is rendered
// in the new surface to call the `onDone` callback.
renderer.addIsDisplayingFlutterUiListener(
new FlutterUiDisplayListener() {
@Override
public void onFlutterUiDisplayed() {
renderer.removeIsDisplayingFlutterUiListener(this);
onDone.run();
flutterImageView.detachFromRenderer();
}

@Override
public void onFlutterUiNoLongerDisplayed() {
// no-op
}
});
}

public void attachOverlaySurfaceToRender(FlutterImageView view) {
Expand Down
19 changes: 14 additions & 5 deletions shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
*
* <p>To invoke a native method that is not associated with a platform view, invoke it statically:
*
* <p>{@code bool enabled = FlutterJNI.nativeGetIsSoftwareRenderingEnabled(); }
* <p>{@code bool enabled = FlutterJNI.getIsSoftwareRenderingEnabled(); }
*/
@Keep
public class FlutterJNI {
Expand Down Expand Up @@ -120,9 +120,13 @@ public static native void nativeInit(
*/
public static native void nativePrefetchDefaultFontManager();

// TODO(mattcarroll): add javadocs
private native boolean nativeGetIsSoftwareRenderingEnabled();

@UiThread
public native boolean nativeGetIsSoftwareRenderingEnabled();
// TODO(mattcarroll): add javadocs
public boolean getIsSoftwareRenderingEnabled() {
return nativeGetIsSoftwareRenderingEnabled();
}

@Nullable
// TODO(mattcarroll): add javadocs
Expand Down Expand Up @@ -212,7 +216,12 @@ public boolean isAttached() {
public void attachToNative(boolean isBackgroundView) {
ensureRunningOnMainThread();
ensureNotAttachedToNative();
nativePlatformViewId = nativeAttach(this, isBackgroundView);
nativePlatformViewId = performNativeAttach(this, isBackgroundView);
}

@VisibleForTesting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be package private?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it can't because FlutterJNI is a dependency of the PlatformViewsController, which for historic reasons is a different package.

public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView) {
return nativeAttach(flutterJNI, isBackgroundView);
}

private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView);
Expand Down Expand Up @@ -279,7 +288,7 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene
@SuppressWarnings("unused")
@VisibleForTesting
@UiThread
void onFirstFrame() {
public void onFirstFrame() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be package private?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

ensureRunningOnMainThread();

for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ private void unregisterTexture(long textureId) {

// TODO(mattcarroll): describe the native behavior that this invokes
public boolean isSoftwareRenderingEnabled() {
return flutterJNI.nativeGetIsSoftwareRenderingEnabled();
return flutterJNI.getIsSoftwareRenderingEnabled();
}

// TODO(mattcarroll): describe the native behavior that this invokes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ public void onDisplayPlatformView(
int width,
int height,
int viewWidth,
int ViewHeight,
int viewHeight,
FlutterMutatorsStack mutatorsStack) {
initializeRootImageViewIfNeeded();
initializePlatformViewIfNeeded(viewId);
Expand All @@ -723,7 +723,7 @@ public void onDisplayPlatformView(
mutatorView.setVisibility(View.VISIBLE);
mutatorView.bringToFront();

FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, viewHeight);
View platformView = platformViews.get(viewId);
platformView.setLayoutParams(layoutParams);
platformView.bringToFront();
Expand Down Expand Up @@ -753,6 +753,20 @@ public void onBeginFrame() {
}

public void onEndFrame() {
final FlutterView view = (FlutterView) flutterView;
// If there are no platform views in the current frame,
// then revert the image view surface and use the previous surface.
//
// Otherwise, acquire the latest image.
if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) {
flutterViewConvertedToImageView = false;
view.revertImageView(
() -> {
// Destroy overlay surfaces once the surface reversion is completed.
finishFrame(false);
});
return;
}
// Whether the current frame was rendered using ImageReaders.
//
// Since the image readers may not have images available at this point,
Expand All @@ -762,22 +776,12 @@ public void onEndFrame() {
// If one of the surfaces doesn't have an image, the frame may be incomplete and must be
// dropped.
// For example, a toolbar widget painted by Flutter may not be rendered.
boolean isFrameRenderedUsingImageReaders = false;

if (flutterViewConvertedToImageView) {
FlutterView view = (FlutterView) flutterView;
// If there are no platform views in the current frame,
// then revert the image view surface and use the previous surface.
//
// Otherwise, acquire the latest image.
if (currentFrameUsedPlatformViewIds.isEmpty()) {
view.revertImageView();
flutterViewConvertedToImageView = false;
} else {
isFrameRenderedUsingImageReaders = view.acquireLatestImageViewFrame();
}
}
boolean isFrameRenderedUsingImageReaders =
flutterViewConvertedToImageView && view.acquireLatestImageViewFrame();
finishFrame(isFrameRenderedUsingImageReaders);
}

private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
for (int i = 0; i < overlayLayerViews.size(); i++) {
int overlayId = overlayLayerViews.keyAt(i);
FlutterImageView overlayView = overlayLayerViews.valueAt(i);
Expand Down Expand Up @@ -818,6 +822,14 @@ public void onEndFrame() {
}
}

@VisibleForTesting
@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface(@NonNull FlutterImageView imageView) {
final int id = nextOverlayLayerId++;
overlayLayerViews.put(id, imageView);
return new FlutterOverlaySurface(id, imageView.getSurface());
}

@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface() {
// Overlay surfaces have the same size as the background surface.
Expand All @@ -826,17 +838,12 @@ public FlutterOverlaySurface createOverlaySurface() {
// if the drawings they contain have a different tight bound.
//
// The final view size is determined when its frame is set.
FlutterImageView imageView =
return createOverlaySurface(
new FlutterImageView(
flutterView.getContext(),
flutterView.getWidth(),
flutterView.getHeight(),
FlutterImageView.SurfaceKind.overlay);

int id = nextOverlayLayerId++;
overlayLayerViews.put(id, imageView);

return new FlutterOverlaySurface(id, imageView.getSurface());
FlutterImageView.SurfaceKind.overlay));
}

public void destroyOverlaySurfaces() {
Expand Down
2 changes: 1 addition & 1 deletion shell/platform/android/io/flutter/view/FlutterView.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public FlutterView(Context context, AttributeSet attrs, FlutterNativeView native

dartExecutor = mNativeView.getDartExecutor();
flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().nativeGetIsSoftwareRenderingEnabled();
mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().getIsSoftwareRenderingEnabled();
mMetrics = new ViewportMetrics();
mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
setFocusable(true);
Expand Down
Loading