Flutter Engine
The Flutter Engine
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
127 public Surface getSurface() {
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
uint32_t uint32_t * format
ColorSpace
Definition: image.h:16
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
CanvasImage Image
Definition: dart_ui.cc:55
PixelFormat
The Pixel formats supported by Impeller. The naming convention denotes the usage of the component,...
Definition: formats.h:99
int32_t height
int32_t width