Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 3793869

Browse files
authored
I52370 3.22.cp attempt2 (#52491)
- **Replace LinkedLists that are used as a queue in android FlutterRenderer with ArrayDeques (#51494)** - **Workaround HardwareRenderer breakage in Android 14 (#52370)** Fixes flutter/flutter#147644 Includes #51494 to avoid merge conflicts and have a cleaner merge.
1 parent b4bfd45 commit 3793869

File tree

5 files changed

+121
-4
lines changed

5 files changed

+121
-4
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,7 @@ void onPostResume() {
583583
ensureAlive();
584584
if (flutterEngine != null) {
585585
updateSystemUiOverlays();
586+
flutterEngine.getPlatformViewsController().onResume();
586587
} else {
587588
Log.w(TAG, "onPostResume() invoked before FlutterFragment was attached to an Activity.");
588589
}
@@ -1020,6 +1021,7 @@ void onTrimMemory(int level) {
10201021
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
10211022
}
10221023
flutterEngine.getRenderer().onTrimMemory(level);
1024+
flutterEngine.getPlatformViewsController().onTrimMemory(level);
10231025
}
10241026
}
10251027

shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import static io.flutter.Build.API_LEVELS;
88

99
import android.annotation.TargetApi;
10+
import android.content.ComponentCallbacks2;
1011
import android.graphics.Bitmap;
1112
import android.graphics.ImageFormat;
1213
import android.graphics.Rect;
@@ -29,11 +30,11 @@
2930
import java.io.IOException;
3031
import java.lang.ref.WeakReference;
3132
import java.nio.ByteBuffer;
33+
import java.util.ArrayDeque;
3234
import java.util.ArrayList;
3335
import java.util.HashMap;
3436
import java.util.HashSet;
3537
import java.util.Iterator;
36-
import java.util.LinkedList;
3738
import java.util.List;
3839
import java.util.Set;
3940
import java.util.concurrent.atomic.AtomicLong;
@@ -415,12 +416,18 @@ final class ImageReaderSurfaceProducer
415416
// Flip when debugging to see verbose logs.
416417
private static final boolean VERBOSE_LOGS = false;
417418

419+
// We must always cleanup on memory pressure on Android 14 due to a bug in Android.
420+
// It is safe to do on all versions so we unconditionally have this set to true.
421+
private static final boolean CLEANUP_ON_MEMORY_PRESSURE = true;
422+
418423
private final long id;
419424

420425
private boolean released;
421426
// Will be true in tests and on Android API < 33.
422427
private boolean ignoringFence = false;
423428

429+
private boolean trimOnMemoryPressure = CLEANUP_ON_MEMORY_PRESSURE;
430+
424431
// The requested width and height are updated by setSize.
425432
private int requestedWidth = 1;
426433
private int requestedHeight = 1;
@@ -434,10 +441,11 @@ final class ImageReaderSurfaceProducer
434441
private long lastDequeueTime = 0;
435442
private long lastQueueTime = 0;
436443
private long lastScheduleTime = 0;
444+
private int numTrims = 0;
437445

438446
private Object lock = new Object();
439447
// REQUIRED: The following fields must only be accessed when lock is held.
440-
private final LinkedList<PerImageReader> imageReaderQueue = new LinkedList<PerImageReader>();
448+
private final ArrayDeque<PerImageReader> imageReaderQueue = new ArrayDeque<PerImageReader>();
441449
private final HashMap<ImageReader, PerImageReader> perImageReaders =
442450
new HashMap<ImageReader, PerImageReader>();
443451
private PerImage lastDequeuedImage = null;
@@ -457,7 +465,7 @@ public PerImage(Image image, long queuedTime) {
457465
/** Internal class: state held per ImageReader. */
458466
private class PerImageReader {
459467
public final ImageReader reader;
460-
private final LinkedList<PerImage> imageQueue = new LinkedList<PerImage>();
468+
private final ArrayDeque<PerImage> imageQueue = new ArrayDeque<PerImage>();
461469
private boolean closed = false;
462470

463471
private final ImageReader.OnImageAvailableListener onImageAvailableListener =
@@ -651,6 +659,15 @@ PerImage dequeueImage() {
651659

652660
@Override
653661
public void onTrimMemory(int level) {
662+
if (!trimOnMemoryPressure) {
663+
return;
664+
}
665+
if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
666+
return;
667+
}
668+
synchronized (lock) {
669+
numTrims++;
670+
}
654671
cleanup();
655672
createNewReader = true;
656673
}
@@ -867,6 +884,13 @@ public int numImageReaders() {
867884
}
868885
}
869886

887+
@VisibleForTesting
888+
public int numTrims() {
889+
synchronized (lock) {
890+
return numTrims;
891+
}
892+
}
893+
870894
@VisibleForTesting
871895
public int numImages() {
872896
int r = 0;

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static io.flutter.Build.API_LEVELS;
1010

1111
import android.annotation.TargetApi;
12+
import android.content.ComponentCallbacks2;
1213
import android.content.Context;
1314
import android.content.MutableContextWrapper;
1415
import android.os.Build;
@@ -1053,6 +1054,24 @@ private void diposeAllViews() {
10531054
}
10541055
}
10551056

1057+
// Invoked when the Android system is requesting we reduce memory usage.
1058+
public void onTrimMemory(int level) {
1059+
if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
1060+
return;
1061+
}
1062+
for (VirtualDisplayController vdc : vdControllers.values()) {
1063+
vdc.clearSurface();
1064+
}
1065+
}
1066+
1067+
// Called after the application has been resumed.
1068+
// This is where we undo whatever may have been done in onTrimMemory.
1069+
public void onResume() {
1070+
for (VirtualDisplayController vdc : vdControllers.values()) {
1071+
vdc.resetSurface();
1072+
}
1073+
}
1074+
10561075
/**
10571076
* Disposes a single
10581077
*

shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,49 @@ public void dispatchTouchEvent(MotionEvent event) {
284284
presentation.dispatchTouchEvent(event);
285285
}
286286

287+
public void clearSurface() {
288+
virtualDisplay.setSurface(null);
289+
}
290+
291+
public void resetSurface() {
292+
final int width = getRenderTargetWidth();
293+
final int height = getRenderTargetHeight();
294+
final boolean isFocused = getView().isFocused();
295+
final SingleViewPresentation.PresentationState presentationState = presentation.detachState();
296+
297+
// We detach the surface to prevent it being destroyed when releasing the vd.
298+
virtualDisplay.setSurface(null);
299+
virtualDisplay.release();
300+
final DisplayManager displayManager =
301+
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
302+
int flags = 0;
303+
virtualDisplay =
304+
displayManager.createVirtualDisplay(
305+
"flutter-vd#" + viewId,
306+
width,
307+
height,
308+
densityDpi,
309+
renderTarget.getSurface(),
310+
flags,
311+
callback,
312+
null /* handler */);
313+
// Create a new SingleViewPresentation and show() it before we cancel() the existing
314+
// presentation. Calling show() and cancel() in this order fixes
315+
// https://github.com/flutter/flutter/issues/26345 and maintains seamless transition
316+
// of the contents of the presentation.
317+
SingleViewPresentation newPresentation =
318+
new SingleViewPresentation(
319+
context,
320+
virtualDisplay.getDisplay(),
321+
accessibilityEventsDelegate,
322+
presentationState,
323+
focusChangeListener,
324+
isFocused);
325+
newPresentation.show();
326+
presentation.cancel();
327+
presentation = newPresentation;
328+
}
329+
287330
static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener {
288331
static void schedule(View view, Runnable runnable) {
289332
OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable);

shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,12 +647,41 @@ public void ImageReaderSurfaceProducerTrimMemoryCallback() {
647647
assertEquals(1, texture.numImageReaders());
648648
assertEquals(1, texture.numImages());
649649

650-
// Invoke the onTrimMemory callback.
650+
// Invoke the onTrimMemory callback with level 0.
651+
// This should do nothing.
651652
texture.onTrimMemory(0);
652653
shadowOf(Looper.getMainLooper()).idle();
653654

655+
assertEquals(1, texture.numImageReaders());
656+
assertEquals(1, texture.numImages());
657+
assertEquals(0, texture.numTrims());
658+
659+
// Invoke the onTrimMemory callback with level 40.
660+
// This should result in a trim.
661+
texture.onTrimMemory(40);
662+
shadowOf(Looper.getMainLooper()).idle();
663+
654664
assertEquals(0, texture.numImageReaders());
655665
assertEquals(0, texture.numImages());
666+
assertEquals(1, texture.numTrims());
667+
668+
// Request the surface, this should result in a new image reader.
669+
surface = texture.getSurface();
670+
assertEquals(1, texture.numImageReaders());
671+
assertEquals(0, texture.numImages());
672+
assertEquals(1, texture.numTrims());
673+
674+
// Render an image.
675+
canvas = surface.lockHardwareCanvas();
676+
canvas.drawARGB(255, 255, 0, 0);
677+
surface.unlockCanvasAndPost(canvas);
678+
679+
// Let callbacks run, this will produce a single frame.
680+
shadowOf(Looper.getMainLooper()).idle();
681+
682+
assertEquals(1, texture.numImageReaders());
683+
assertEquals(1, texture.numImages());
684+
assertEquals(1, texture.numTrims());
656685
}
657686

658687
// A 0x0 ImageReader is a runtime error.

0 commit comments

Comments
 (0)