Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterImageView.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.embedding.android;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.annotation.SuppressLint;
10import android.annotation.TargetApi;
11import android.content.Context;
12import android.graphics.Bitmap;
13import android.graphics.Canvas;
14import android.graphics.ColorSpace;
15import android.graphics.PixelFormat;
16import android.hardware.HardwareBuffer;
17import android.media.Image;
18import android.media.Image.Plane;
19import android.media.ImageReader;
20import android.util.AttributeSet;
21import android.view.Surface;
22import android.view.View;
23import androidx.annotation.NonNull;
24import androidx.annotation.Nullable;
25import androidx.annotation.VisibleForTesting;
26import io.flutter.Log;
27import io.flutter.embedding.engine.renderer.FlutterRenderer;
28import io.flutter.embedding.engine.renderer.RenderSurface;
29import java.nio.ByteBuffer;
30import java.util.Locale;
31
32/**
33 * Paints a Flutter UI provided by an {@link android.media.ImageReader} onto a {@link
34 * android.graphics.Canvas}.
35 *
36 * <p>A {@code FlutterImageView} is intended for situations where a developer needs to render a
37 * Flutter UI, but also needs to render an interactive {@link
38 * io.flutter.plugin.platform.PlatformView}.
39 *
40 * <p>This {@code View} takes an {@link android.media.ImageReader} that provides the Flutter UI in
41 * an {@link android.media.Image} and renders it to the {@link android.graphics.Canvas} in {@code
42 * onDraw}.
43 */
44public class FlutterImageView extends View implements RenderSurface {
45 private static final String TAG = "FlutterImageView";
46
47 @NonNull private ImageReader imageReader;
48 @Nullable private Image currentImage;
49 @Nullable private Bitmap currentBitmap;
50 @Nullable private FlutterRenderer flutterRenderer;
51
52 public ImageReader getImageReader() {
53 return imageReader;
54 }
55
56 public enum SurfaceKind {
57 /** Displays the background canvas. */
59
60 /** Displays the overlay surface canvas. */
62 }
63
64 /** The kind of surface. */
65 private SurfaceKind kind;
66
67 /** Whether the view is attached to the Flutter render. */
68 private boolean isAttachedToFlutterRenderer = false;
69
70 /**
71 * Constructs a {@code FlutterImageView} with an {@link android.media.ImageReader} that provides
72 * the Flutter UI.
73 */
74 public FlutterImageView(@NonNull Context context, int width, int height, SurfaceKind kind) {
75 this(context, createImageReader(width, height), kind);
76 }
77
78 public FlutterImageView(@NonNull Context context) {
79 this(context, 1, 1, SurfaceKind.background);
80 }
81
82 public FlutterImageView(@NonNull Context context, @NonNull AttributeSet attrs) {
83 this(context, 1, 1, SurfaceKind.background);
84 }
85
86 @VisibleForTesting
87 /*package*/ FlutterImageView(
88 @NonNull Context context, @NonNull ImageReader imageReader, SurfaceKind kind) {
89 super(context, null);
90 this.imageReader = imageReader;
91 this.kind = kind;
92 init();
93 }
94
95 private void init() {
96 setAlpha(0.0f);
97 }
98
99 private static void logW(String format, Object... args) {
100 Log.w(TAG, String.format(Locale.US, format, args));
101 }
102
103 @SuppressLint("WrongConstant") // RGBA_8888 is a valid constant.
104 @NonNull
105 private static ImageReader createImageReader(int width, int height) {
106 if (width <= 0) {
107 logW("ImageReader width must be greater than 0, but given width=%d, set width=1", width);
108 width = 1;
109 }
110 if (height <= 0) {
111 logW("ImageReader height must be greater than 0, but given height=%d, set height=1", height);
112 height = 1;
113 }
114 if (android.os.Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
115 return ImageReader.newInstance(
116 width,
117 height,
118 PixelFormat.RGBA_8888,
119 3,
120 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT);
121 } else {
122 return ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 3);
123 }
124 }
125
126 @NonNull
128 return imageReader.getSurface();
129 }
130
131 @Nullable
132 @Override
134 return flutterRenderer;
135 }
136
137 /**
138 * Invoked by the owner of this {@code FlutterImageView} when it wants to begin rendering a
139 * Flutter UI to this {@code FlutterImageView}.
140 */
141 @Override
142 public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
143 switch (kind) {
144 case background:
145 flutterRenderer.swapSurface(imageReader.getSurface());
146 break;
147 case overlay:
148 // Do nothing since the attachment is done by the handler of
149 // `FlutterJNI#createOverlaySurface()` in the native side.
150 break;
151 }
152 setAlpha(1.0f);
153 this.flutterRenderer = flutterRenderer;
154 isAttachedToFlutterRenderer = true;
155 }
156
157 /**
158 * Invoked by the owner of this {@code FlutterImageView} when it no longer wants to render a
159 * Flutter UI to this {@code FlutterImageView}.
160 */
161 public void detachFromRenderer() {
162 if (!isAttachedToFlutterRenderer) {
163 return;
164 }
165 setAlpha(0.0f);
166 // Drop the latest image as it shouldn't render this image if this view is
167 // attached to the renderer again.
169 // Clear drawings.
170 currentBitmap = null;
171
172 // Close and clear the current image if any.
173 closeCurrentImage();
174 invalidate();
175 isAttachedToFlutterRenderer = false;
176 }
177
178 public void pause() {
179 // Not supported.
180 }
181
182 public void resume() {
183 // Not supported.
184 }
185
186 /**
187 * Acquires the next image to be drawn to the {@link android.graphics.Canvas}. Returns true if
188 * there's an image available in the queue.
189 */
190 public boolean acquireLatestImage() {
191 if (!isAttachedToFlutterRenderer) {
192 return false;
193 }
194 // 1. `acquireLatestImage()` may return null if no new image is available.
195 // 2. There's no guarantee that `onDraw()` is called after `invalidate()`.
196 // For example, the device may not produce new frames if it's in sleep mode
197 // or some special Android devices so the calls to `invalidate()` queued up
198 // until the device produces a new frame.
199 // 3. While the engine will also stop producing frames, there is a race condition.
200 final Image newImage = imageReader.acquireLatestImage();
201 if (newImage != null) {
202 // Only close current image after acquiring valid new image
203 closeCurrentImage();
204 currentImage = newImage;
205 invalidate();
206 }
207 return newImage != null;
208 }
209
210 /** Creates a new image reader with the provided size. */
211 public void resizeIfNeeded(int width, int height) {
212 if (flutterRenderer == null) {
213 return;
214 }
215 if (width == imageReader.getWidth() && height == imageReader.getHeight()) {
216 return;
217 }
218
219 // Close resources.
220 closeCurrentImage();
221 // Close the current image reader, then create a new one with the new size.
222 // Image readers cannot be resized once created.
224 imageReader = createImageReader(width, height);
225 }
226
227 /**
228 * Closes the image reader associated with the current {@code FlutterImageView}.
229 *
230 * <p>Once the image reader is closed, calling {@code acquireLatestImage} will result in an {@code
231 * IllegalStateException}.
232 */
233 public void closeImageReader() {
234 imageReader.close();
235 }
236
237 @Override
238 protected void onDraw(Canvas canvas) {
239 super.onDraw(canvas);
240 if (currentImage != null) {
241 updateCurrentBitmap();
242 }
243 if (currentBitmap != null) {
244 canvas.drawBitmap(currentBitmap, 0, 0, null);
245 }
246 }
247
248 private void closeCurrentImage() {
249 // Close and clear the current image if any.
250 if (currentImage != null) {
251 currentImage.close();
252 currentImage = null;
253 }
254 }
255
256 @TargetApi(API_LEVELS.API_29)
257 private void updateCurrentBitmap() {
258 if (android.os.Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
259 final HardwareBuffer buffer = currentImage.getHardwareBuffer();
260 currentBitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
261 buffer.close();
262 } else {
263 final Plane[] imagePlanes = currentImage.getPlanes();
264 if (imagePlanes.length != 1) {
265 return;
266 }
267
268 final Plane imagePlane = imagePlanes[0];
269 final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
270 final int desiredHeight = currentImage.getHeight();
271
272 if (currentBitmap == null
273 || currentBitmap.getWidth() != desiredWidth
274 || currentBitmap.getHeight() != desiredHeight) {
275 currentBitmap =
276 Bitmap.createBitmap(
277 desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
278 }
279 ByteBuffer buffer = imagePlane.getBuffer();
280 buffer.rewind();
281 currentBitmap.copyPixelsFromBuffer(buffer);
282 }
283 }
284
285 @Override
286 protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
287 if (width == imageReader.getWidth() && height == imageReader.getHeight()) {
288 return;
289 }
290 // `SurfaceKind.overlay` isn't resized. Instead, the `FlutterImageView` instance
291 // is destroyed. As a result, an instance with the new size is created by the surface
292 // pool in the native side.
293 if (kind == SurfaceKind.background && isAttachedToFlutterRenderer) {
295 // Bind native window to the new surface, and create a new onscreen surface
296 // with the new size in the native side.
297 flutterRenderer.swapSurface(imageReader.getSurface());
298 }
299 }
300}
static void w(@NonNull String tag, @NonNull String message)
Definition Log.java:76
FlutterImageView(@NonNull Context context, @NonNull AttributeSet attrs)
void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
FlutterImageView(@NonNull Context context, int width, int height, SurfaceKind kind)
void attachToRenderer(@NonNull FlutterRenderer flutterRenderer)
FlutterImageView( @NonNull Context context, @NonNull ImageReader imageReader, SurfaceKind kind)
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
static const uint8_t buffer[]
uint32_t uint32_t * format
int32_t height
int32_t width