Switch to FlutterSurfaceView if no Android view is in the frame (flutter/engine#19487)

This commit is contained in:
Emmanuel Garcia 2020-07-06 14:51:46 -07:00 committed by GitHub
parent 973753d800
commit ecc2f07d06
10 changed files with 412 additions and 71 deletions

View File

@ -377,12 +377,6 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
TRACE_EVENT0("flutter", "Rasterizer::DrawToSurface");
FML_DCHECK(surface_);
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
if (frame == nullptr) {
return RasterStatus::kFailed;
}
// There is no way for the compositor to know how long the layer tree
// construction took. Fortunately, the layer tree does. Grab that time
// for instrumentation.
@ -398,6 +392,16 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
embedder_root_canvas = external_view_embedder->GetRootCanvas();
}
// On Android, the external view embedder deletes surfaces in `BeginFrame`.
//
// Deleting a surface also clears the GL context. Therefore, acquire the
// frame after calling `BeginFrame` as this operation resets the GL context.
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
if (frame == nullptr) {
return RasterStatus::kFailed;
}
// If the external view embedder has specified an optional root surface, the
// root surface transformation is set by the embedder instead of
// having to apply it here.

View File

@ -85,18 +85,20 @@ bool AndroidExternalViewEmbedder::SubmitFrame(
std::unordered_map<int64_t, std::list<SkRect>> overlay_layers;
std::unordered_map<int64_t, sk_sp<SkPicture>> pictures;
SkCanvas* background_canvas = frame->SkiaCanvas();
auto current_frame_view_count = composition_order_.size();
// Restore the clip context after exiting this method since it's changed
// below.
SkAutoCanvasRestore save(background_canvas, /*doSave=*/true);
for (size_t i = 0; i < composition_order_.size(); i++) {
for (size_t i = 0; i < current_frame_view_count; i++) {
int64_t view_id = composition_order_[i];
sk_sp<SkPicture> picture =
picture_recorders_.at(view_id)->finishRecordingAsPicture();
FML_CHECK(picture);
pictures.insert({view_id, picture});
overlay_layers.insert({view_id, {}});
sk_sp<RTree> rtree = view_rtrees_.at(view_id);
@ -142,8 +144,14 @@ bool AndroidExternalViewEmbedder::SubmitFrame(
background_canvas->drawPicture(pictures.at(view_id));
}
// Submit the background canvas frame before switching the GL context to
// the surfaces above.
frame->Submit();
// the overlay surfaces.
//
// Skip a frame if the embedding is switching surfaces.
auto should_submit_current_frame =
previous_frame_view_count_ > 0 || current_frame_view_count == 0;
if (should_submit_current_frame) {
frame->Submit();
}
for (int64_t view_id : composition_order_) {
SkRect view_rect = GetViewRect(view_id);
@ -161,12 +169,15 @@ bool AndroidExternalViewEmbedder::SubmitFrame(
params.mutatorsStack() //
);
for (const SkRect& overlay_rect : overlay_layers.at(view_id)) {
CreateSurfaceIfNeeded(context, //
view_id, //
pictures.at(view_id), //
overlay_rect //
)
->Submit();
std::unique_ptr<SurfaceFrame> frame =
CreateSurfaceIfNeeded(context, //
view_id, //
pictures.at(view_id), //
overlay_rect //
);
if (should_submit_current_frame) {
frame->Submit();
}
}
}
return true;
@ -233,6 +244,8 @@ SkCanvas* AndroidExternalViewEmbedder::GetRootCanvas() {
}
void AndroidExternalViewEmbedder::Reset() {
previous_frame_view_count_ = composition_order_.size();
composition_order_.clear();
picture_recorders_.clear();
}
@ -244,6 +257,9 @@ void AndroidExternalViewEmbedder::BeginFrame(
double device_pixel_ratio,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
Reset();
// The surface size changed. Therefore, destroy existing surfaces as
// the existing surfaces in the pool can't be recycled.
if (frame_size_ != frame_size) {
surface_pool_->DestroyLayers(jni_facade_);
}

View File

@ -124,6 +124,9 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder {
// The r-tree that captures the operations for the picture recorders.
std::unordered_map<int64_t, sk_sp<RTree>> view_rtrees_;
// The number of platform views in the previous frame.
int64_t previous_frame_view_count_;
// Resets the state.
void Reset();

View File

@ -250,7 +250,7 @@ TEST(AndroidExternalViewEmbedder, PlatformViewRect__ChangedParams) {
ASSERT_EQ(SkRect::MakeXYWH(75, 90, 105, 120), embedder->GetViewRect(view_id));
}
TEST(AndroidExternalViewEmbedder, SubmitFrame__RecycleSurfaces) {
TEST(AndroidExternalViewEmbedder, SubmitFrame) {
auto jni_mock = std::make_shared<JNIMock>();
auto android_context =
std::make_shared<AndroidContext>(AndroidRenderingAPI::kSoftware);
@ -294,6 +294,27 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame__RecycleSurfaces) {
auto raster_thread_merger = GetThreadMergerFromPlatformThread();
// ------------------ First frame ------------------ //
{
auto did_submit_frame = false;
auto surface_frame = std::make_unique<SurfaceFrame>(
SkSurface::MakeNull(1000, 1000), false,
[&did_submit_frame](const SurfaceFrame& surface_frame,
SkCanvas* canvas) mutable {
if (canvas != nullptr) {
did_submit_frame = true;
}
return true;
});
embedder->SubmitFrame(gr_context.get(), std::move(surface_frame));
// Submits frame if no Android view in the current frame.
EXPECT_TRUE(did_submit_frame);
EXPECT_CALL(*jni_mock, FlutterViewEndFrame());
embedder->EndFrame(/*should_resubmit_frame=*/false, raster_thread_merger);
}
// ------------------ Second frame ------------------ //
{
EXPECT_CALL(*jni_mock, FlutterViewBeginFrame());
embedder->BeginFrame(frame_size, nullptr, 1.5, raster_thread_merger);
@ -338,18 +359,26 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame__RecycleSurfaces) {
EXPECT_CALL(*jni_mock,
FlutterViewDisplayOverlaySurface(0, 50, 50, 200, 200));
auto surface_frame =
std::make_unique<SurfaceFrame>(SkSurface::MakeNull(1000, 1000), false,
[](const SurfaceFrame& surface_frame,
SkCanvas* canvas) { return true; });
auto did_submit_frame = false;
auto surface_frame = std::make_unique<SurfaceFrame>(
SkSurface::MakeNull(1000, 1000), false,
[&did_submit_frame](const SurfaceFrame& surface_frame,
SkCanvas* canvas) mutable {
if (canvas != nullptr) {
did_submit_frame = true;
}
return true;
});
embedder->SubmitFrame(gr_context.get(), std::move(surface_frame));
// Doesn't submit frame if there aren't Android views in the previous frame.
EXPECT_FALSE(did_submit_frame);
EXPECT_CALL(*jni_mock, FlutterViewEndFrame());
embedder->EndFrame(/*should_resubmit_frame=*/false, raster_thread_merger);
}
// ------------------ Second frame ------------------ //
// ------------------ Third frame ------------------ //
{
EXPECT_CALL(*jni_mock, FlutterViewBeginFrame());
embedder->BeginFrame(frame_size, nullptr, 1.5, raster_thread_merger);
@ -392,11 +421,19 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame__RecycleSurfaces) {
EXPECT_CALL(*jni_mock,
FlutterViewDisplayOverlaySurface(0, 50, 50, 200, 200));
auto surface_frame =
std::make_unique<SurfaceFrame>(SkSurface::MakeNull(1000, 1000), false,
[](const SurfaceFrame& surface_frame,
SkCanvas* canvas) { return true; });
auto did_submit_frame = false;
auto surface_frame = std::make_unique<SurfaceFrame>(
SkSurface::MakeNull(1000, 1000), false,
[&did_submit_frame](const SurfaceFrame& surface_frame,
SkCanvas* canvas) mutable {
if (canvas != nullptr) {
did_submit_frame = true;
}
return true;
});
embedder->SubmitFrame(gr_context.get(), std::move(surface_frame));
// Submits frame if there are Android views in the previous frame.
EXPECT_TRUE(did_submit_frame);
EXPECT_CALL(*jni_mock, FlutterViewEndFrame());
embedder->EndFrame(/*should_resubmit_frame=*/false, raster_thread_merger);
@ -423,15 +460,83 @@ TEST(AndroidExternalViewEmbedder, DoesNotCallJNIPlatformThreadOnlyMethods) {
TEST(AndroidExternalViewEmbedder, DestroyOverlayLayersOnSizeChange) {
auto jni_mock = std::make_shared<JNIMock>();
auto embedder =
std::make_unique<AndroidExternalViewEmbedder>(nullptr, jni_mock, nullptr);
auto android_context =
std::make_shared<AndroidContext>(AndroidRenderingAPI::kSoftware);
auto window = fml::MakeRefCounted<AndroidNativeWindow>(nullptr);
auto gr_context = GrContext::MakeMock(nullptr);
auto frame_size = SkISize::Make(1000, 1000);
auto surface_factory =
[gr_context, window, frame_size](
std::shared_ptr<AndroidContext> android_context,
std::shared_ptr<PlatformViewAndroidJNI> jni_facade) {
auto surface_frame_1 = std::make_unique<SurfaceFrame>(
SkSurface::MakeNull(1000, 1000), false,
[](const SurfaceFrame& surface_frame, SkCanvas* canvas) {
return true;
});
auto surface_mock = std::make_unique<SurfaceMock>();
EXPECT_CALL(*surface_mock, AcquireFrame(frame_size))
.WillOnce(Return(ByMove(std::move(surface_frame_1))));
auto android_surface_mock = std::make_unique<AndroidSurfaceMock>();
EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true));
EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get()))
.WillOnce(Return(ByMove(std::move(surface_mock))));
EXPECT_CALL(*android_surface_mock, SetNativeWindow(window));
return android_surface_mock;
};
auto embedder = std::make_unique<AndroidExternalViewEmbedder>(
android_context, jni_mock, surface_factory);
auto raster_thread_merger = GetThreadMergerFromPlatformThread();
ASSERT_FALSE(raster_thread_merger->IsMerged());
embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0,
raster_thread_merger);
// ------------------ First frame ------------------ //
{
EXPECT_CALL(*jni_mock, FlutterViewBeginFrame());
embedder->BeginFrame(frame_size, nullptr, 1.5, raster_thread_merger);
// Add an Android view.
MutatorsStack stack1;
// TODO(egarciad): Investigate why Flow applies the device pixel ratio to
// the offsetPixels, but not the sizePoints.
auto view_params_1 = std::make_unique<EmbeddedViewParams>(
SkMatrix(), SkSize::Make(200, 200), stack1);
embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1));
// This simulates Flutter UI that intersects with the Android view.
embedder->CompositeEmbeddedView(0)->drawRect(
SkRect::MakeXYWH(50, 50, 200, 200), SkPaint());
// Create a new overlay surface.
EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface())
.WillOnce(Return(
ByMove(std::make_unique<PlatformViewAndroidJNI::OverlayMetadata>(
0, window))));
// The JNI call to display the Android view.
EXPECT_CALL(*jni_mock, FlutterViewOnDisplayPlatformView(0, 0, 0, 200, 200,
300, 300, stack1));
EXPECT_CALL(*jni_mock,
FlutterViewDisplayOverlaySurface(0, 50, 50, 200, 200));
auto surface_frame =
std::make_unique<SurfaceFrame>(SkSurface::MakeNull(1000, 1000), false,
[](const SurfaceFrame& surface_frame,
SkCanvas* canvas) { return true; });
embedder->SubmitFrame(gr_context.get(), std::move(surface_frame));
EXPECT_CALL(*jni_mock, FlutterViewEndFrame());
embedder->EndFrame(/*should_resubmit_frame=*/false, raster_thread_merger);
}
EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces());
EXPECT_CALL(*jni_mock, FlutterViewBeginFrame());
// Change the frame size.
embedder->BeginFrame(SkISize::Make(30, 40), nullptr, 1.0,
raster_thread_merger);
}

View File

@ -72,9 +72,11 @@ void SurfacePool::RecycleLayers() {
void SurfacePool::DestroyLayers(
std::shared_ptr<PlatformViewAndroidJNI> jni_facade) {
if (layers_.size() > 0) {
jni_facade->FlutterViewDestroyOverlaySurfaces();
}
layers_.clear();
available_layer_index_ = 0;
jni_facade->FlutterViewDestroyOverlaySurfaces();
}
std::vector<std::shared_ptr<OverlayLayer>> SurfacePool::GetUnusedLayers() {

View File

@ -164,5 +164,41 @@ TEST(SurfacePool, GetLayer__AllocateTwoLayers) {
ASSERT_EQ(1, layer_2->id);
}
TEST(SurfacePool, DestroyLayers) {
auto pool = std::make_unique<SurfacePool>();
auto jni_mock = std::make_shared<JNIMock>();
EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces()).Times(0);
pool->DestroyLayers(jni_mock);
auto gr_context = GrContext::MakeMock(nullptr);
auto android_context =
std::make_shared<AndroidContext>(AndroidRenderingAPI::kSoftware);
auto window = fml::MakeRefCounted<AndroidNativeWindow>(nullptr);
EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface())
.Times(1)
.WillOnce(Return(
ByMove(std::make_unique<PlatformViewAndroidJNI::OverlayMetadata>(
0, window))));
auto surface_factory =
[gr_context, window](std::shared_ptr<AndroidContext> android_context,
std::shared_ptr<PlatformViewAndroidJNI> jni_facade) {
auto android_surface_mock = std::make_unique<AndroidSurfaceMock>();
EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get()));
EXPECT_CALL(*android_surface_mock, SetNativeWindow(window));
EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true));
return android_surface_mock;
};
pool->GetLayer(gr_context.get(), android_context, jni_mock, surface_factory);
EXPECT_CALL(*jni_mock, FlutterViewDestroyOverlaySurfaces());
pool->DestroyLayers(jni_mock);
pool->RecycleLayers();
ASSERT_TRUE(pool->GetUnusedLayers().empty());
}
} // namespace testing
} // namespace flutter

View File

@ -62,6 +62,9 @@ public class FlutterImageView extends View implements RenderSurface {
*/
private int pendingImages = 0;
/** Whether the view is attached to the Flutter render. */
private boolean isAttachedToFlutterRenderer = false;
/**
* Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides
* the Flutter UI.
@ -70,6 +73,7 @@ public class FlutterImageView extends View implements RenderSurface {
super(context, null);
this.imageReader = createImageReader(width, height);
this.kind = kind;
init();
}
@VisibleForTesting
@ -77,6 +81,11 @@ public class FlutterImageView extends View implements RenderSurface {
super(context, null);
this.imageReader = imageReader;
this.kind = kind;
init();
}
private void init() {
setAlpha(0.0f);
}
@TargetApi(19)
@ -111,16 +120,21 @@ public class FlutterImageView extends View implements RenderSurface {
*/
@Override
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
this.flutterRenderer = flutterRenderer;
if (isAttachedToFlutterRenderer) {
return;
}
switch (kind) {
case background:
flutterRenderer.swapSurface(imageReader.getSurface());
break;
case overlay:
// Don't do anything as this is done by the handler of
// Do nothing since the attachment is done by the handler of
// `FlutterJNI#createOverlaySurface()` in the native side.
break;
}
setAlpha(1.0f);
this.flutterRenderer = flutterRenderer;
isAttachedToFlutterRenderer = true;
}
/**
@ -128,16 +142,26 @@ public class FlutterImageView extends View implements RenderSurface {
* Flutter UI to this {@code FlutterImageView}.
*/
public void detachFromRenderer() {
switch (kind) {
case background:
// TODO: Swap the surface back to the original one.
// https://github.com/flutter/flutter/issues/58291
break;
case overlay:
// TODO: Handle this in the native side.
// https://github.com/flutter/flutter/issues/59904
break;
if (!isAttachedToFlutterRenderer) {
return;
}
setAlpha(0.0f);
// Drop the lastest image as it shouldn't render this image if this view is
// attached to the renderer again.
acquireLatestImage();
// Clear drawings.
pendingImages = 0;
currentBitmap = null;
if (nextImage != null) {
nextImage.close();
nextImage = null;
}
if (currentImage != null) {
currentImage.close();
currentImage = null;
}
invalidate();
isAttachedToFlutterRenderer = false;
}
public void pause() {
@ -146,7 +170,10 @@ public class FlutterImageView extends View implements RenderSurface {
/** Acquires the next image to be drawn to the {@link android.graphics.Canvas}. */
@TargetApi(19)
public void acquireLatestImage() {
public boolean acquireLatestImage() {
if (!isAttachedToFlutterRenderer) {
return false;
}
// There's no guarantee that the image will be closed before the next call to
// `acquireLatestImage()`. For example, the device may not produce new frames if
// it's in sleep mode, so the calls to `invalidate()` will be queued up
@ -162,6 +189,30 @@ public class FlutterImageView extends View implements RenderSurface {
}
}
invalidate();
return nextImage != null;
}
/** Creates a new image reader with the provided size. */
public void resizeIfNeeded(int width, int height) {
if (flutterRenderer == null) {
return;
}
if (width == imageReader.getWidth() && height == imageReader.getHeight()) {
return;
}
// Close resources.
if (nextImage != null) {
nextImage.close();
nextImage = null;
}
if (currentImage != null) {
currentImage.close();
currentImage = null;
}
imageReader.close();
// Image readers cannot be resized once created.
imageReader = createImageReader(width, height);
pendingImages = 0;
}
@Override
@ -214,8 +265,13 @@ public class FlutterImageView extends View implements RenderSurface {
if (width == imageReader.getWidth() && height == imageReader.getHeight()) {
return;
}
if (kind == SurfaceKind.background && flutterRenderer != null) {
imageReader = createImageReader(width, height);
// `SurfaceKind.overlay` isn't resized. Instead, the `FlutterImageView` instance
// is destroyed. As a result, an instance with the new size is created by the surface
// pool in the native side.
if (kind == SurfaceKind.background && isAttachedToFlutterRenderer) {
resizeIfNeeded(width, height);
// Bind native window to the new surface, and create a new onscreen surface
// with the new size in the native side.
flutterRenderer.swapSurface(imageReader.getSurface());
}
}

View File

@ -81,6 +81,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
@Nullable private FlutterTextureView flutterTextureView;
@Nullable private FlutterImageView flutterImageView;
@Nullable private RenderSurface renderSurface;
@Nullable private RenderSurface previousRenderSurface;
private final Set<FlutterUiDisplayListener> flutterUiDisplayListeners = new HashSet<>();
private boolean isFlutterUiDisplayed;
@ -943,26 +944,63 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
flutterRenderer.stopRenderingToSurface();
flutterRenderer.setSemanticsEnabled(false);
renderSurface.detachFromRenderer();
flutterImageView = null;
previousRenderSurface = null;
flutterEngine = null;
}
public void convertToImageView() {
renderSurface.pause();
flutterImageView =
new FlutterImageView(
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
if (flutterImageView == null) {
flutterImageView =
new FlutterImageView(
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
addView(flutterImageView);
} else {
flutterImageView.resizeIfNeeded(getWidth(), getHeight());
}
previousRenderSurface = renderSurface;
renderSurface = flutterImageView;
if (flutterEngine != null) {
renderSurface.attachToRenderer(flutterEngine.getRenderer());
}
addView(flutterImageView);
}
public void acquireLatestImageViewFrame() {
if (flutterImageView != null) {
flutterImageView.acquireLatestImage();
/**
* 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.
*/
public void revertImageView() {
if (flutterImageView == null) {
Log.v(TAG, "Tried to revert the image view, but no image view is used.");
return;
}
if (previousRenderSurface == null) {
Log.v(TAG, "Tried to revert the image view, but no previous surface was used.");
return;
}
flutterImageView.detachFromRenderer();
renderSurface = previousRenderSurface;
previousRenderSurface = null;
if (flutterEngine != null) {
renderSurface.attachToRenderer(flutterEngine.getRenderer());
}
}
public void attachOverlaySurfaceToRender(FlutterImageView view) {
if (flutterEngine != null) {
view.attachToRenderer(flutterEngine.getRenderer());
}
}
public boolean acquireLatestImageViewFrame() {
if (flutterImageView != null) {
return flutterImageView.acquireLatestImage();
}
return false;
}
/** Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}. */

View File

@ -663,7 +663,6 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(viewWidth, ViewHeight);
View platformView = platformViews.get(viewId);
platformView.setLayoutParams(layoutParams);
platformView.setVisibility(View.VISIBLE);
platformView.bringToFront();
currentFrameUsedPlatformViewIds.add(viewId);
}
@ -691,42 +690,86 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
}
public void onEndFrame() {
// Hide overlay surfaces that aren't rendered in the current frame.
// Whether the current frame was rendered using ImageReaders.
//
// Since the image readers may not have images available at this point,
// this becomes true if all the required surfaces have images available.
//
// This is used to decide if the platform views can be rendered in the current frame.
// 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();
}
}
for (int i = 0; i < overlayLayerViews.size(); i++) {
int overlayId = overlayLayerViews.keyAt(i);
FlutterImageView overlayView = overlayLayerViews.valueAt(i);
if (currentFrameUsedOverlayLayerIds.contains(overlayId)) {
overlayView.acquireLatestImage();
((FlutterView) flutterView).attachOverlaySurfaceToRender(overlayView);
boolean didAcquireOverlaySurfaceImage = overlayView.acquireLatestImage();
isFrameRenderedUsingImageReaders &= didAcquireOverlaySurfaceImage;
} else {
// If the background surface isn't rendered by the image view, then the
// overlay surfaces can be detached from the rendered.
// This releases resources used by the ImageReader.
if (!flutterViewConvertedToImageView) {
overlayView.detachFromRenderer();
}
// Hide overlay surfaces that aren't rendered in the current frame.
overlayView.setVisibility(View.GONE);
}
}
// Hide platform views that aren't rendered in the current frame.
// The platform view is destroyed by the framework after the widget is disposed.
//
// The framework diposes the platform view, when its `State` object will never
// build again.
for (int i = 0; i < platformViews.size(); i++) {
int viewId = platformViews.keyAt(i);
if (!currentFrameUsedPlatformViewIds.contains(viewId)) {
platformViews.get(viewId).setVisibility(View.GONE);
mutatorViews.get(viewId).setVisibility(View.GONE);
View platformView = platformViews.get(viewId);
View mutatorView = mutatorViews.get(viewId);
// Show platform views only if the surfaces have images available in this frame,
// and if the platform view is rendered in this frame.
//
// Otherwise, hide the platform view, but don't remove it from the view hierarchy yet as
// they are removed when the framework diposes the platform view widget.
if (isFrameRenderedUsingImageReaders && currentFrameUsedPlatformViewIds.contains(viewId)) {
platformView.setVisibility(View.VISIBLE);
mutatorView.setVisibility(View.VISIBLE);
} else {
platformView.setVisibility(View.GONE);
mutatorView.setVisibility(View.GONE);
}
}
// If the background surface is still an image, then acquire the latest image.
if (flutterViewConvertedToImageView) {
((FlutterView) flutterView).acquireLatestImageViewFrame();
}
}
@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface() {
// Overlay surfaces have the same size as the background surface.
//
// This allows to reuse these surfaces in consecutive frames even
// if the drawings they contain have a different tight bound.
//
// The final view size is determined when its frame is set.
FlutterImageView imageView =
new FlutterImageView(
flutterView.getContext(),
flutterView.getWidth(),
flutterView.getHeight(),
FlutterImageView.SurfaceKind.overlay);
int id = nextOverlayLayerId++;
overlayLayerViews.put(id, imageView);
@ -734,6 +777,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
}
public void destroyOverlaySurfaces() {
for (int i = 0; i < overlayLayerViews.size(); i++) {
int overlayId = overlayLayerViews.keyAt(i);
FlutterImageView overlayView = overlayLayerViews.valueAt(i);
overlayView.detachFromRenderer();
((FlutterView) flutterView).removeView(overlayView);
}
overlayLayerViews.clear();
}
}

View File

@ -1,6 +1,8 @@
package io.flutter.embedding.android;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
@ -385,11 +387,38 @@ public class FlutterViewTest {
mockReader,
FlutterImageView.SurfaceKind.background));
imageView.acquireLatestImage();
final FlutterJNI jni = mock(FlutterJNI.class);
imageView.attachToRenderer(new FlutterRenderer(jni));
final Image mockImage = mock(Image.class);
when(mockReader.acquireLatestImage()).thenReturn(mockImage);
assertTrue(imageView.acquireLatestImage());
verify(mockReader, times(1)).acquireLatestImage();
verify(imageView, times(1)).invalidate();
}
@Test
public void flutterImageView_acquireLatestImageReturnsFalse() {
final ImageReader mockReader = mock(ImageReader.class);
when(mockReader.getMaxImages()).thenReturn(2);
final FlutterImageView imageView =
spy(
new FlutterImageView(
RuntimeEnvironment.application,
mockReader,
FlutterImageView.SurfaceKind.background));
assertFalse(imageView.acquireLatestImage());
final FlutterJNI jni = mock(FlutterJNI.class);
imageView.attachToRenderer(new FlutterRenderer(jni));
when(mockReader.acquireLatestImage()).thenReturn(null);
assertFalse(imageView.acquireLatestImage());
}
@Test
public void flutterImageView_acquiresMaxImagesAtMost() {
final ImageReader mockReader = mock(ImageReader.class);
@ -405,10 +434,13 @@ public class FlutterViewTest {
mockReader,
FlutterImageView.SurfaceKind.background));
final FlutterJNI jni = mock(FlutterJNI.class);
imageView.attachToRenderer(new FlutterRenderer(jni));
doNothing().when(imageView).invalidate();
imageView.acquireLatestImage();
imageView.acquireLatestImage();
imageView.acquireLatestImage();
assertTrue(imageView.acquireLatestImage());
assertTrue(imageView.acquireLatestImage());
assertTrue(imageView.acquireLatestImage());
verify(mockReader, times(2)).acquireLatestImage();
}