44
55package io .flutter .embedding .android ;
66
7- import android .annotation .SuppressLint ;
87import android .annotation .TargetApi ;
98import android .content .Context ;
109import android .graphics .Bitmap ;
1514import android .media .Image ;
1615import android .media .Image .Plane ;
1716import android .media .ImageReader ;
17+ import android .util .AttributeSet ;
1818import android .view .Surface ;
1919import android .view .View ;
2020import androidx .annotation .NonNull ;
2121import androidx .annotation .Nullable ;
2222import androidx .annotation .VisibleForTesting ;
2323import io .flutter .embedding .engine .renderer .FlutterRenderer ;
2424import io .flutter .embedding .engine .renderer .RenderSurface ;
25+ import java .util .LinkedList ;
26+ import java .util .Queue ;
2527
2628/**
2729 * Paints a Flutter UI provided by an {@link android.media.ImageReader} onto a {@link
3537 * an {@link android.media.Image} and renders it to the {@link android.graphics.Canvas} in {@code
3638 * onDraw}.
3739 */
38- @ SuppressLint ("ViewConstructor" )
3940@ TargetApi (19 )
4041public class FlutterImageView extends View implements RenderSurface {
4142 @ NonNull private ImageReader imageReader ;
42- @ Nullable private Image nextImage ;
43+ @ Nullable private Queue < Image > imageQueue ;
4344 @ Nullable private Image currentImage ;
4445 @ Nullable private Bitmap currentBitmap ;
4546 @ Nullable private FlutterRenderer flutterRenderer ;
@@ -70,17 +71,24 @@ public enum SurfaceKind {
7071 * the Flutter UI.
7172 */
7273 public FlutterImageView (@ NonNull Context context , int width , int height , SurfaceKind kind ) {
73- super (context , null );
74- this .imageReader = createImageReader (width , height );
75- this .kind = kind ;
76- init ();
74+ this (context , createImageReader (width , height ), kind );
75+ }
76+
77+ public FlutterImageView (@ NonNull Context context ) {
78+ this (context , 1 , 1 , SurfaceKind .background );
79+ }
80+
81+ public FlutterImageView (@ NonNull Context context , @ NonNull AttributeSet attrs ) {
82+ this (context , 1 , 1 , SurfaceKind .background );
7783 }
7884
7985 @ VisibleForTesting
80- FlutterImageView (@ NonNull Context context , @ NonNull ImageReader imageReader , SurfaceKind kind ) {
86+ /*package*/ FlutterImageView (
87+ @ NonNull Context context , @ NonNull ImageReader imageReader , SurfaceKind kind ) {
8188 super (context , null );
8289 this .imageReader = imageReader ;
8390 this .kind = kind ;
91+ this .imageQueue = new LinkedList <>();
8492 init ();
8593 }
8694
@@ -150,12 +158,14 @@ public void detachFromRenderer() {
150158 // attached to the renderer again.
151159 acquireLatestImage ();
152160 // Clear drawings.
153- pendingImages = 0 ;
154161 currentBitmap = null ;
155- if (nextImage != null ) {
156- nextImage .close ();
157- nextImage = null ;
162+
163+ // Close the images in the queue and clear the queue.
164+ for (final Image image : imageQueue ) {
165+ image .close ();
158166 }
167+ imageQueue .clear ();
168+ // Close and clear the current image if any.
159169 if (currentImage != null ) {
160170 currentImage .close ();
161171 currentImage = null ;
@@ -168,7 +178,10 @@ public void pause() {
168178 // Not supported.
169179 }
170180
171- /** Acquires the next image to be drawn to the {@link android.graphics.Canvas}. */
181+ /**
182+ * Acquires the next image to be drawn to the {@link android.graphics.Canvas}. Returns true if
183+ * there's an image available in the queue.
184+ */
172185 @ TargetApi (19 )
173186 public boolean acquireLatestImage () {
174187 if (!isAttachedToFlutterRenderer ) {
@@ -182,14 +195,14 @@ public boolean acquireLatestImage() {
182195 // While the engine will also stop producing frames, there is a race condition.
183196 //
184197 // To avoid exceptions, check if a new image can be acquired.
185- if (pendingImages < imageReader .getMaxImages ()) {
186- nextImage = imageReader .acquireLatestImage ();
187- if (nextImage != null ) {
188- pendingImages ++ ;
198+ if (imageQueue . size () < imageReader .getMaxImages ()) {
199+ final Image image = imageReader .acquireLatestImage ();
200+ if (image != null ) {
201+ imageQueue . add ( image ) ;
189202 }
190203 }
191204 invalidate ();
192- return nextImage != null ;
205+ return ! imageQueue . isEmpty () ;
193206 }
194207
195208 /** Creates a new image reader with the provided size. */
@@ -200,15 +213,10 @@ public void resizeIfNeeded(int width, int height) {
200213 if (width == imageReader .getWidth () && height == imageReader .getHeight ()) {
201214 return ;
202215 }
203- // Close resources.
204- if (nextImage != null ) {
205- nextImage .close ();
206- nextImage = null ;
207- }
208- if (currentImage != null ) {
209- currentImage .close ();
210- currentImage = null ;
211- }
216+ imageQueue .clear ();
217+ currentImage = null ;
218+ // Close all the resources associated with the image reader,
219+ // including the images.
212220 imageReader .close ();
213221 // Image readers cannot be resized once created.
214222 imageReader = createImageReader (width , height );
@@ -218,16 +226,14 @@ public void resizeIfNeeded(int width, int height) {
218226 @ Override
219227 protected void onDraw (Canvas canvas ) {
220228 super .onDraw (canvas );
221- if (nextImage != null ) {
229+
230+ if (!imageQueue .isEmpty ()) {
222231 if (currentImage != null ) {
223232 currentImage .close ();
224- pendingImages --;
225233 }
226- currentImage = nextImage ;
227- nextImage = null ;
234+ currentImage = imageQueue .poll ();
228235 updateCurrentBitmap ();
229236 }
230-
231237 if (currentBitmap != null ) {
232238 canvas .drawBitmap (currentBitmap , 0 , 0 , null );
233239 }
@@ -238,6 +244,7 @@ private void updateCurrentBitmap() {
238244 if (android .os .Build .VERSION .SDK_INT >= 29 ) {
239245 final HardwareBuffer buffer = currentImage .getHardwareBuffer ();
240246 currentBitmap = Bitmap .wrapHardwareBuffer (buffer , ColorSpace .get (ColorSpace .Named .SRGB ));
247+ buffer .close ();
241248 } else {
242249 final Plane [] imagePlanes = currentImage .getPlanes ();
243250 if (imagePlanes .length != 1 ) {
@@ -255,7 +262,6 @@ private void updateCurrentBitmap() {
255262 Bitmap .createBitmap (
256263 desiredWidth , desiredHeight , android .graphics .Bitmap .Config .ARGB_8888 );
257264 }
258-
259265 currentBitmap .copyPixelsFromBuffer (imagePlane .getBuffer ());
260266 }
261267 }
0 commit comments