Initial work toward converting the FlutterView to use a FlutterImageView on demand (flutter/engine#19279)

This commit is contained in:
Jason Simmons 2020-06-24 16:26:03 -07:00 committed by GitHub
parent dea0224390
commit 5b8a1cb82e
3 changed files with 115 additions and 52 deletions

View File

@ -17,6 +17,8 @@ import android.media.ImageReader;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;
/**
* Paints a Flutter UI provided by an {@link android.media.ImageReader} onto a {@link
@ -32,10 +34,12 @@ import androidx.annotation.Nullable;
*/
@SuppressLint("ViewConstructor")
@TargetApi(19)
public class FlutterImageView extends View {
public class FlutterImageView extends View implements RenderSurface {
private final ImageReader imageReader;
@Nullable private Image nextImage;
@Nullable private Image currentImage;
@Nullable private Bitmap currentBitmap;
@Nullable private FlutterRenderer flutterRenderer;
/**
* Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides
@ -46,6 +50,37 @@ public class FlutterImageView extends View {
this.imageReader = imageReader;
}
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/**
* Invoked by the owner of this {@code FlutterImageView} when it wants to begin rendering a
* Flutter UI to this {@code FlutterImageView}.
*/
@Override
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
if (this.flutterRenderer != null) {
this.flutterRenderer.stopRenderingToSurface();
}
this.flutterRenderer = flutterRenderer;
flutterRenderer.startRenderingToSurface(imageReader.getSurface());
}
/**
* Invoked by the owner of this {@code FlutterImageView} when it no longer wants to render a
* Flutter UI to this {@code FlutterImageView}.
*/
public void detachFromRenderer() {
if (flutterRenderer != null) {
flutterRenderer.stopRenderingToSurface();
flutterRenderer = null;
}
}
/** Acquires the next image to be drawn to the {@link android.graphics.Canvas}. */
@TargetApi(19)
public void acquireLatestImage() {
@ -56,51 +91,44 @@ public class FlutterImageView extends View {
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (nextImage == null) {
return;
if (nextImage != null) {
if (currentImage != null) {
currentImage.close();
}
currentImage = nextImage;
nextImage = null;
updateCurrentBitmap();
}
if (currentImage != null) {
currentImage.close();
if (currentBitmap != null) {
canvas.drawBitmap(currentBitmap, 0, 0, null);
}
currentImage = nextImage;
nextImage = null;
if (android.os.Build.VERSION.SDK_INT >= 29) {
drawImageBuffer(canvas);
return;
}
drawImagePlane(canvas);
}
@TargetApi(29)
private void drawImageBuffer(@NonNull Canvas canvas) {
final HardwareBuffer buffer = currentImage.getHardwareBuffer();
private void updateCurrentBitmap() {
if (android.os.Build.VERSION.SDK_INT >= 29) {
final HardwareBuffer buffer = currentImage.getHardwareBuffer();
currentBitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
} else {
final Plane[] imagePlanes = currentImage.getPlanes();
if (imagePlanes.length != 1) {
return;
}
final Bitmap bitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
canvas.drawBitmap(bitmap, 0, 0, null);
}
final Plane imagePlane = imagePlanes[0];
final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
final int desiredHeight = currentImage.getHeight();
private void drawImagePlane(@NonNull Canvas canvas) {
if (currentImage == null) {
return;
if (currentBitmap == null
|| currentBitmap.getWidth() != desiredWidth
|| currentBitmap.getHeight() != desiredHeight) {
currentBitmap =
Bitmap.createBitmap(
desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
}
currentBitmap.copyPixelsFromBuffer(imagePlane.getBuffer());
}
final Plane[] imagePlanes = currentImage.getPlanes();
if (imagePlanes.length != 1) {
return;
}
final Plane imagePlane = imagePlanes[0];
final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
final int desiredHeight = currentImage.getHeight();
final Bitmap bitmap =
android.graphics.Bitmap.createBitmap(
desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(imagePlane.getBuffer());
canvas.drawBitmap(bitmap, 0, 0, null);
}
}

View File

@ -10,6 +10,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.media.ImageReader;
import android.os.Build;
import android.text.format.DateFormat;
import android.util.AttributeSet;
@ -303,6 +304,7 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
super(context, attrs);
this.flutterImageView = flutterImageView;
this.renderSurface = flutterImageView;
init();
}
@ -940,6 +942,32 @@ public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseC
flutterEngine = null;
}
public void convertToImageView() {
renderSurface.detachFromRenderer();
ImageReader imageReader = PlatformViewsController.createImageReader(getWidth(), getHeight());
flutterImageView = new FlutterImageView(getContext(), imageReader);
renderSurface = flutterImageView;
if (flutterEngine != null) {
renderSurface.attachToRenderer(flutterEngine.getRenderer());
}
removeAllViews();
addView(flutterImageView);
// TODO(jsimmons): this is a temporary hack that schedules a redraw of the FlutterImageView
// at a time when the engine has presumably posted a frame. Remove this when
// PlatformViewsController.onEndFrame callbacks have been implemented.
postDelayed(
new Runnable() {
public void run() {
flutterImageView.acquireLatestImage();
flutterImageView.invalidate();
}
},
1000);
}
/** Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}. */
@VisibleForTesting
public boolean isAttachedToFlutterEngine() {

View File

@ -22,6 +22,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.android.FlutterImageView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterOverlaySurface;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel;
@ -79,9 +80,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
// Map of unique IDs to views that render overlay layers.
private final LongSparseArray<FlutterImageView> overlayLayerViews;
// Next available unique ID for use in overlayLayerViews;
// Next available unique ID for use in overlayLayerViews.
private long nextOverlayLayerId = 0;
// Tracks whether the flutterView has been converted to use a FlutterImageView.
private boolean flutterViewConvertedToImageView = false;
private final PlatformViewsChannel.PlatformViewsHandler channelHandler =
new PlatformViewsChannel.PlatformViewsHandler() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@ -552,7 +556,10 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
}
public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) {
// TODO: Implement this method. https://github.com/flutter/flutter/issues/58288
if (!flutterViewConvertedToImageView) {
((FlutterView) flutterView).convertToImageView();
flutterViewConvertedToImageView = true;
}
}
public void onBeginFrame() {
@ -564,22 +571,22 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
}
@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface() {
ImageReader imageReader;
public static ImageReader createImageReader(int width, int height) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
imageReader =
ImageReader.newInstance(
flutterView.getWidth(),
flutterView.getHeight(),
PixelFormat.RGBA_8888,
2,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
return ImageReader.newInstance(
width,
height,
PixelFormat.RGBA_8888,
2,
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
} else {
imageReader =
ImageReader.newInstance(
flutterView.getWidth(), flutterView.getHeight(), PixelFormat.RGBA_8888, 2);
return ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 2);
}
}
@TargetApi(19)
public FlutterOverlaySurface createOverlaySurface() {
ImageReader imageReader = createImageReader(flutterView.getWidth(), flutterView.getHeight());
FlutterImageView imageView = new FlutterImageView(flutterView.getContext(), imageReader);
long id = nextOverlayLayerId++;
overlayLayerViews.put(id, imageView);