-
Notifications
You must be signed in to change notification settings - Fork 6k
Wait before switching surfaces #20100
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ | |
| import io.flutter.view.AccessibilityBridge; | ||
| import java.util.HashSet; | ||
| import java.util.Set; | ||
| import java.util.concurrent.Callable; | ||
|
|
||
| /** | ||
| * Displays a Flutter UI on an Android device. | ||
|
|
@@ -949,13 +950,18 @@ public void detachFromFlutterEngine() { | |
| flutterEngine = null; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| @NonNull | ||
| public FlutterImageView createImageView() { | ||
| return new FlutterImageView( | ||
| getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background); | ||
| } | ||
|
|
||
| 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()); | ||
|
|
@@ -970,9 +976,12 @@ 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. | ||
| * rendering to a {@code 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 Callable<Void> onDone) { | ||
| if (flutterImageView == null) { | ||
| Log.v(TAG, "Tried to revert the image view, but no image view is used."); | ||
| return; | ||
|
|
@@ -981,11 +990,46 @@ 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; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ack.
No. Only |
||
| if (flutterEngine != null) { | ||
| renderSurface.attachToRenderer(flutterEngine.getRenderer()); | ||
| if (flutterEngine == null) { | ||
| flutterImageView.detachFromRenderer(); | ||
| callSafely(onDone); | ||
| return; | ||
| } | ||
| final FlutterRenderer render = flutterEngine.getRenderer(); | ||
|
||
| if (render == null) { | ||
| flutterImageView.detachFromRenderer(); | ||
| callSafely(onDone); | ||
| return; | ||
| } | ||
| // Start rendering on the previous surface. | ||
| // This surface is typically `FlutterSurfaceView` or `FlutterTextureView`. | ||
| renderSurface.attachToRenderer(render); | ||
|
|
||
| // Install a Flutter UI listener to wait until the first frame is rendered | ||
| // in the new surface to call the `onDone` callback. | ||
| render.addIsDisplayingFlutterUiListener( | ||
| new FlutterUiDisplayListener() { | ||
| @Override | ||
| public void onFlutterUiDisplayed() { | ||
| render.removeIsDisplayingFlutterUiListener(this); | ||
| callSafely(onDone); | ||
| flutterImageView.detachFromRenderer(); | ||
| } | ||
|
|
||
| @Override | ||
| public void onFlutterUiNoLongerDisplayed() { | ||
| // no-op | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| private static void callSafely(@NonNull Callable<Void> onDone) { | ||
| try { | ||
| onDone.call(); | ||
| } catch (Exception exception) { | ||
| Log.e(TAG, "onDone callback threw exception: " + exception.getMessage()); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
|
@@ -120,9 +120,14 @@ public static native void nativeInit( | |
| */ | ||
| public static native void nativePrefetchDefaultFontManager(); | ||
|
|
||
| // TODO(mattcarroll): add javadocs | ||
| private native boolean nativeGetIsSoftwareRenderingEnabled(); | ||
|
|
||
| @VisibleForTesting | ||
|
||
| @UiThread | ||
| public native boolean nativeGetIsSoftwareRenderingEnabled(); | ||
| // TODO(mattcarroll): add javadocs | ||
| public boolean getIsSoftwareRenderingEnabled() { | ||
| return nativeGetIsSoftwareRenderingEnabled(); | ||
| } | ||
|
|
||
| @Nullable | ||
| // TODO(mattcarroll): add javadocs | ||
|
|
@@ -212,7 +217,12 @@ public boolean isAttached() { | |
| public void attachToNative(boolean isBackgroundView) { | ||
| ensureRunningOnMainThread(); | ||
| ensureNotAttachedToNative(); | ||
| nativePlatformViewId = nativeAttach(this, isBackgroundView); | ||
| nativePlatformViewId = performNativeAttach(this, isBackgroundView); | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be package private?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, it can't because |
||
| public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView) { | ||
| return nativeAttach(flutterJNI, isBackgroundView); | ||
| } | ||
|
|
||
| private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); | ||
|
|
@@ -279,7 +289,7 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene | |
| @SuppressWarnings("unused") | ||
| @VisibleForTesting | ||
| @UiThread | ||
| void onFirstFrame() { | ||
| public void onFirstFrame() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be package private?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| ensureRunningOnMainThread(); | ||
|
|
||
| for (FlutterUiDisplayListener listener : flutterUiDisplayListeners) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -713,7 +713,7 @@ public void onDisplayPlatformView( | |
| int width, | ||
| int height, | ||
| int viewWidth, | ||
| int ViewHeight, | ||
| int viewHeight, | ||
| FlutterMutatorsStack mutatorsStack) { | ||
| initializeRootImageViewIfNeeded(); | ||
| initializePlatformViewIfNeeded(viewId); | ||
|
|
@@ -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(); | ||
|
|
@@ -753,6 +753,21 @@ 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 null; | ||
|
||
| }); | ||
| return; | ||
| } | ||
| // Whether the current frame was rendered using ImageReaders. | ||
| // | ||
| // Since the image readers may not have images available at this point, | ||
|
|
@@ -762,22 +777,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); | ||
|
|
@@ -818,6 +823,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. | ||
|
|
@@ -826,17 +839,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() { | ||
|
|
||
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.androidvsio.flutter.plugin.platform