mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Switch to FlutterSurfaceView if no Android view is in the frame (flutter/engine#19487)
This commit is contained in:
parent
973753d800
commit
ecc2f07d06
@ -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.
|
||||
|
||||
@ -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_);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}. */
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user