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

Commit ea63438

Browse files
Workaround HardwareRenderer breakage in Android 14 (#52370)
- Destroy ImageReaders on memory trim. - Unset the VirtualDisplay's surface on memory trim. - On resume, recreate ImageReaders. - On resume, do a dumb little dance and then set the VirtualDisplay's surface Fixes: flutter/flutter#146499 Fixes: flutter/flutter#144219 Internal bug: b/335646931 Android Fix: https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/base/+/27015418 Android 15 will include the fix. Unclear if Android 14 will be patched.
1 parent c519e9d commit ea63438

File tree

5 files changed

+114
-6
lines changed

5 files changed

+114
-6
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: 21 additions & 5 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;
@@ -418,17 +419,18 @@ final class ImageReaderSurfaceProducer
418419
// Flip when debugging to see verbose logs.
419420
private static final boolean VERBOSE_LOGS = false;
420421

421-
// If we cleanup the ImageReaders on memory pressure it breaks VirtualDisplay
422-
// backed platform views. Disable for now as this is only necessary to work
423-
// around a Samsung-specific Android 14 bug.
424-
private static final boolean CLEANUP_ON_MEMORY_PRESSURE = false;
422+
// We must always cleanup on memory pressure on Android 14 due to a bug in Android.
423+
// It is safe to do on all versions so we unconditionally have this set to true.
424+
private static final boolean CLEANUP_ON_MEMORY_PRESSURE = true;
425425

426426
private final long id;
427427

428428
private boolean released;
429429
// Will be true in tests and on Android API < 33.
430430
private boolean ignoringFence = false;
431431

432+
private boolean trimOnMemoryPressure = CLEANUP_ON_MEMORY_PRESSURE;
433+
432434
// The requested width and height are updated by setSize.
433435
private int requestedWidth = 1;
434436
private int requestedHeight = 1;
@@ -442,6 +444,7 @@ final class ImageReaderSurfaceProducer
442444
private long lastDequeueTime = 0;
443445
private long lastQueueTime = 0;
444446
private long lastScheduleTime = 0;
447+
private int numTrims = 0;
445448

446449
private Object lock = new Object();
447450
// REQUIRED: The following fields must only be accessed when lock is held.
@@ -659,9 +662,15 @@ PerImage dequeueImage() {
659662

660663
@Override
661664
public void onTrimMemory(int level) {
662-
if (!CLEANUP_ON_MEMORY_PRESSURE) {
665+
if (!trimOnMemoryPressure) {
666+
return;
667+
}
668+
if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
663669
return;
664670
}
671+
synchronized (lock) {
672+
numTrims++;
673+
}
665674
cleanup();
666675
createNewReader = true;
667676
}
@@ -878,6 +887,13 @@ public int numImageReaders() {
878887
}
879888
}
880889

890+
@VisibleForTesting
891+
public int numTrims() {
892+
synchronized (lock) {
893+
return numTrims;
894+
}
895+
}
896+
881897
@VisibleForTesting
882898
public int numImages() {
883899
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: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,13 +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.
651651
// This should do nothing.
652652
texture.onTrimMemory(0);
653653
shadowOf(Looper.getMainLooper()).idle();
654654

655655
assertEquals(1, texture.numImageReaders());
656656
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+
664+
assertEquals(0, texture.numImageReaders());
665+
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());
657685
}
658686

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

0 commit comments

Comments
 (0)