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

Commit e2b0519

Browse files
committed
[android] Fixes 'drawRenderNode called on a context with no surface' crash
When a memory pressure warning is received and the level equal {@code ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system release the underlying surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a crash occurs, and we found that this crash appeared on Android8.1 and above system. Here our workaround is to recreate the surface before using it. Fixes flutter/flutter#103870
1 parent 11fe7e0 commit e2b0519

File tree

5 files changed

+142
-24
lines changed

5 files changed

+142
-24
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package io.flutter.embedding.android;
66

7+
import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
78
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
89
import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
910

@@ -844,6 +845,7 @@ void onTrimMemory(int level) {
844845
flutterEngine.getDartExecutor().notifyLowMemoryWarning();
845846
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
846847
}
848+
flutterEngine.getRenderer().onTrimMemory(level);
847849
}
848850
}
849851

@@ -860,6 +862,7 @@ void onLowMemory() {
860862
ensureAlive();
861863
flutterEngine.getDartExecutor().notifyLowMemoryWarning();
862864
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
865+
flutterEngine.getRenderer().onTrimMemory(TRIM_MEMORY_COMPLETE);
863866
}
864867

865868
/**

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

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.nio.ByteBuffer;
2020
import java.util.ArrayList;
2121
import java.util.List;
22+
import java.util.Set;
23+
import java.util.concurrent.CopyOnWriteArraySet;
2224
import java.util.concurrent.atomic.AtomicLong;
2325

2426
/**
@@ -44,6 +46,10 @@ public class FlutterRenderer implements TextureRegistry {
4446
private boolean isDisplayingFlutterUi = false;
4547
private Handler handler = new Handler();
4648

49+
@NonNull
50+
private final Set<TextureRegistry.OnLowMemoryListener> onLowMemoryListeners =
51+
new CopyOnWriteArraySet<>();
52+
4753
@NonNull
4854
private final FlutterUiDisplayListener flutterUiDisplayListener =
4955
new FlutterUiDisplayListener() {
@@ -91,6 +97,19 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene
9197
flutterJNI.removeIsDisplayingFlutterUiListener(listener);
9298
}
9399

100+
/** Adds a listener that is invoked when a memory pressure warning was forward. */
101+
public void addOnLowMemoryListener(@NonNull OnLowMemoryListener listener) {
102+
onLowMemoryListeners.add(listener);
103+
}
104+
105+
/**
106+
* Removes a {@link OnLowMemoryListener} that was added with {@link
107+
* #addOnLowMemoryListener(OnLowMemoryListener)}.
108+
*/
109+
public void removeOnLowMemoryListener(@NonNull OnLowMemoryListener listener) {
110+
onLowMemoryListeners.remove(listener);
111+
}
112+
94113
// ------ START TextureRegistry IMPLEMENTATION -----
95114
/**
96115
* Creates and returns a new {@link SurfaceTexture} managed by the Flutter engine that is also
@@ -114,20 +133,30 @@ public SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfac
114133
new SurfaceTextureRegistryEntry(nextTextureId.getAndIncrement(), surfaceTexture);
115134
Log.v(TAG, "New SurfaceTexture ID: " + entry.id());
116135
registerTexture(entry.id(), entry.textureWrapper());
136+
addOnLowMemoryListener(entry);
117137
return entry;
118138
}
119139

120-
final class SurfaceTextureRegistryEntry implements TextureRegistry.SurfaceTextureEntry {
140+
@Override
141+
public void onTrimMemory(int level) {
142+
for (TextureRegistry.OnLowMemoryListener listener : onLowMemoryListeners) {
143+
listener.onLowMemory(level);
144+
}
145+
}
146+
147+
final class SurfaceTextureRegistryEntry
148+
implements TextureRegistry.SurfaceTextureEntry, TextureRegistry.OnLowMemoryListener {
121149
private final long id;
122150
@NonNull private final SurfaceTextureWrapper textureWrapper;
123151
private boolean released;
124-
@Nullable private OnFrameConsumedListener listener;
152+
@Nullable private OnLowMemoryListener lowMemoryListener;
153+
@Nullable private OnFrameConsumedListener frameConsumedListener;
125154
private final Runnable onFrameConsumed =
126155
new Runnable() {
127156
@Override
128157
public void run() {
129-
if (listener != null) {
130-
listener.onFrameConsumed();
158+
if (frameConsumedListener != null) {
159+
frameConsumedListener.onFrameConsumed();
131160
}
132161
}
133162
};
@@ -151,6 +180,13 @@ public void run() {
151180
}
152181
}
153182

183+
@Override
184+
public void onLowMemory(int level) {
185+
if (lowMemoryListener != null) {
186+
lowMemoryListener.onLowMemory(level);
187+
}
188+
}
189+
154190
private SurfaceTexture.OnFrameAvailableListener onFrameListener =
155191
new SurfaceTexture.OnFrameAvailableListener() {
156192
@Override
@@ -166,6 +202,10 @@ public void onFrameAvailable(@NonNull SurfaceTexture texture) {
166202
}
167203
};
168204

205+
private void removeListener() {
206+
removeOnLowMemoryListener(this);
207+
}
208+
169209
@NonNull
170210
public SurfaceTextureWrapper textureWrapper() {
171211
return textureWrapper;
@@ -190,6 +230,7 @@ public void release() {
190230
Log.v(TAG, "Releasing a SurfaceTexture (" + id + ").");
191231
textureWrapper.release();
192232
unregisterTexture(id);
233+
removeListener();
193234
released = true;
194235
}
195236

@@ -200,34 +241,31 @@ protected void finalize() throws Throwable {
200241
return;
201242
}
202243

203-
handler.post(new SurfaceTextureFinalizerRunnable(id, flutterJNI));
244+
handler.post(
245+
new Runnable() {
246+
@Override
247+
public void run() {
248+
if (!flutterJNI.isAttached()) {
249+
return;
250+
}
251+
Log.v(TAG, "Releasing a SurfaceTexture (" + id + ").");
252+
unregisterTexture(id);
253+
removeListener();
254+
}
255+
});
204256
} finally {
205257
super.finalize();
206258
}
207259
}
208260

209261
@Override
210262
public void setOnFrameConsumedListener(@Nullable OnFrameConsumedListener listener) {
211-
this.listener = listener;
212-
}
213-
}
214-
215-
static final class SurfaceTextureFinalizerRunnable implements Runnable {
216-
private final long id;
217-
private final FlutterJNI flutterJNI;
218-
219-
SurfaceTextureFinalizerRunnable(long id, @NonNull FlutterJNI flutterJNI) {
220-
this.id = id;
221-
this.flutterJNI = flutterJNI;
263+
frameConsumedListener = listener;
222264
}
223265

224266
@Override
225-
public void run() {
226-
if (!flutterJNI.isAttached()) {
227-
return;
228-
}
229-
Log.v(TAG, "Releasing a SurfaceTexture (" + id + ").");
230-
flutterJNI.unregisterTexture(id);
267+
public void setOnLowMemoryListener(@Nullable OnLowMemoryListener listener) {
268+
lowMemoryListener = listener;
231269
}
232270
}
233271
// ------ END TextureRegistry IMPLEMENTATION ----

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
package io.flutter.plugin.platform;
66

7+
import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
8+
79
import android.annotation.SuppressLint;
810
import android.annotation.TargetApi;
911
import android.content.Context;
@@ -56,7 +58,7 @@ class PlatformViewWrapper extends FrameLayout {
5658
@Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener;
5759
private final AtomicLong pendingFramesCount = new AtomicLong(0L);
5860

59-
private final TextureRegistry.OnFrameConsumedListener listener =
61+
private final TextureRegistry.OnFrameConsumedListener frameConsumedListener =
6062
new TextureRegistry.OnFrameConsumedListener() {
6163
@Override
6264
public void onFrameConsumed() {
@@ -66,12 +68,39 @@ public void onFrameConsumed() {
6668
}
6769
};
6870

71+
private boolean shouldRecreateSurfaceForLowMemory = false;
72+
private final TextureRegistry.OnLowMemoryListener lowMemoryListener =
73+
new TextureRegistry.OnLowMemoryListener() {
74+
@Override
75+
public void onLowMemory(int level) {
76+
// When a memory pressure warning is received and the level equal {@code
77+
// ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system release the underlying
78+
// surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a crash
79+
// occurs, and we found that this crash appeared on Android8.1 and above system.
80+
//
81+
// Here our workaround is to recreate the surface before using it.
82+
if (level == TRIM_MEMORY_COMPLETE && Build.VERSION.SDK_INT >= 27) {
83+
shouldRecreateSurfaceForLowMemory = true;
84+
}
85+
}
86+
};
87+
6988
private void onFrameProduced() {
7089
if (Build.VERSION.SDK_INT == 29) {
7190
pendingFramesCount.incrementAndGet();
7291
}
7392
}
7493

94+
private void recreateSurfaceIfNeeded() {
95+
if (shouldRecreateSurfaceForLowMemory) {
96+
if (surface != null) {
97+
surface.release();
98+
}
99+
surface = createSurface(tx);
100+
shouldRecreateSurfaceForLowMemory = false;
101+
}
102+
}
103+
75104
private boolean shouldDrawToSurfaceNow() {
76105
if (Build.VERSION.SDK_INT == 29) {
77106
return pendingFramesCount.get() <= 0L;
@@ -87,7 +116,8 @@ public PlatformViewWrapper(@NonNull Context context) {
87116
public PlatformViewWrapper(
88117
@NonNull Context context, @NonNull TextureRegistry.SurfaceTextureEntry textureEntry) {
89118
this(context);
90-
textureEntry.setOnFrameConsumedListener(listener);
119+
textureEntry.setOnFrameConsumedListener(frameConsumedListener);
120+
textureEntry.setOnLowMemoryListener(lowMemoryListener);
91121
setTexture(textureEntry.surfaceTexture());
92122
}
93123

@@ -249,6 +279,10 @@ public void draw(Canvas canvas) {
249279
// If there are still frames that are not consumed, we will draw them next time.
250280
invalidate();
251281
} else {
282+
// We try to recreate the surface before using it to avoid the crash:
283+
// https://github.com/flutter/flutter/issues/103870
284+
recreateSurfaceIfNeeded();
285+
252286
// Override the canvas that this subtree of views will use to draw.
253287
final Canvas surfaceCanvas = surface.lockHardwareCanvas();
254288
try {

shell/platform/android/io/flutter/view/TextureRegistry.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ public interface TextureRegistry {
3131
@NonNull
3232
SurfaceTextureEntry registerSurfaceTexture(@NonNull SurfaceTexture surfaceTexture);
3333

34+
/**
35+
* Callback invoked when memory is low.
36+
*
37+
* <p>Invoke this from {@link android.app.Activity#onTrimMemory(int)}.
38+
*/
39+
default void onTrimMemory(int level) {}
40+
3441
/** A registry entry for a managed SurfaceTexture. */
3542
interface SurfaceTextureEntry {
3643
/** @return The managed SurfaceTexture. */
@@ -45,6 +52,9 @@ interface SurfaceTextureEntry {
4552

4653
/** Set a listener that will be notified when the most recent image has been consumed. */
4754
default void setOnFrameConsumedListener(@Nullable OnFrameConsumedListener listener) {}
55+
56+
/** Set a listener that will be notified when a memory pressure warning was forward. */
57+
default void setOnLowMemoryListener(@Nullable OnLowMemoryListener listener) {}
4858
}
4959

5060
/** Listener invoked when the most recent image has been consumed. */
@@ -55,4 +65,10 @@ interface OnFrameConsumedListener {
5565
*/
5666
void onFrameConsumed();
5767
}
68+
69+
/** Listener invoked when a memory pressure warning was forward. */
70+
interface OnLowMemoryListener {
71+
/** This method will be invoked when a memory pressure warning was forward. */
72+
void onLowMemory(int level);
73+
}
5874
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.flutter.embedding.engine.renderer;
22

3+
import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
34
import static org.junit.Assert.assertArrayEquals;
45
import static org.junit.Assert.assertEquals;
56
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -339,4 +340,30 @@ public void onFrameConsumed() {
339340
// Verify behavior under test.
340341
assertEquals(1, invocationCount.get());
341342
}
343+
344+
@Test
345+
public void itNotifySurfaceTextureEntryWhenMemoryPressureWarning() {
346+
// Setup the test.
347+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
348+
349+
AtomicInteger invocationCount = new AtomicInteger(0);
350+
final TextureRegistry.OnLowMemoryListener listener =
351+
new TextureRegistry.OnLowMemoryListener() {
352+
@Override
353+
public void onLowMemory(int level) {
354+
invocationCount.incrementAndGet();
355+
}
356+
};
357+
358+
FlutterRenderer.SurfaceTextureRegistryEntry entry =
359+
(FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture();
360+
entry.setOnLowMemoryListener(listener);
361+
flutterRenderer.addOnLowMemoryListener(entry);
362+
363+
// Execute the behavior under test.
364+
flutterRenderer.onTrimMemory(TRIM_MEMORY_COMPLETE);
365+
366+
// Verify behavior under test.
367+
assertEquals(1, invocationCount.get());
368+
}
342369
}

0 commit comments

Comments
 (0)