[stable] [android] release background image readers on <= Android 14 (#171737)

CP of https://github.com/flutter/flutter/pull/171193. 

Template:

- Impacted Users (Approximately who will hit this issue, ex. all Flutter devs, Windows developers, all end-customers, apps using X framework feature).

All end users on Android 10-13 (inclusive) using an app with platform views.

- Impact Description (What is the impact? ex. visual jank on Samsung phones, app crash, cannot ship an iOS app. Does it impact development? ex. flutter doctor crashes when Android Studio is installed. Or shipping a production app? ex. the app crashes on launch).

Full app crash when backgrounding and then bringing the app back up.

- Workaround (Is there a workaround for this issue?)

No workaround.

- Risk (What is the risk level of this cherry-pick?)

Medium

- Test Coverage (Are you confident that your fix is well-tested by automated tests?)

No

- Validation Steps (What are the steps to validate that this fix works?)

Launch an app with platform views on these api levels and background them, and then bring the app back up.
This commit is contained in:
Gray Mackall 2025-07-08 10:33:53 -07:00 committed by GitHub
parent 331c569830
commit 72f2b18bb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 122 additions and 75 deletions

View File

@ -74,7 +74,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
private FlutterView flutterView;
// The texture registry maintaining the textures into which the embedded views will be rendered.
@Nullable private TextureRegistry textureRegistry;
@VisibleForTesting @Nullable TextureRegistry textureRegistry;
@Nullable private TextInputPlugin textInputPlugin;
@ -978,7 +978,7 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
TextureRegistry textureRegistry) {
if (enableSurfaceProducerRenderTarget && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
TextureRegistry.SurfaceLifecycle lifecycle =
Build.VERSION.SDK_INT == API_LEVELS.API_34
Build.VERSION.SDK_INT <= API_LEVELS.API_34
? TextureRegistry.SurfaceLifecycle.resetInBackground
: TextureRegistry.SurfaceLifecycle.manual;
final TextureRegistry.SurfaceProducer textureEntry =

View File

@ -107,7 +107,9 @@ public class PlatformViewsControllerTest {
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
@Config(
shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class},
minSdk = 35)
public void itRemovesPlatformViewBeforeDiposeIsCalled() {
PlatformViewsController platformViewsController = new PlatformViewsController();
FlutterJNI jni = new FlutterJNI();
@ -141,11 +143,55 @@ public class PlatformViewsControllerTest {
assertTrue(pView instanceof CountingPlatformView);
CountingPlatformView cpv = (CountingPlatformView) pView;
platformViewsController.configureForTextureLayerComposition(pView, request);
verify(platformViewsController.textureRegistry, times(1))
.createSurfaceProducer(TextureRegistry.SurfaceLifecycle.manual);
assertEquals(0, cpv.disposeCalls);
platformViewsController.disposePlatformView(viewId);
assertEquals(1, cpv.disposeCalls);
}
@Test
@Config(
shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class},
minSdk = 29,
maxSdk = 34)
public void itPassesSurfaceLifecyleResetInBackgroundLeqApi34() {
PlatformViewsController platformViewsController = new PlatformViewsController();
FlutterJNI jni = new FlutterJNI();
attach(jni, platformViewsController);
// Get the platform view registry.
PlatformViewRegistry registry = platformViewsController.getRegistry();
// Register a factory for our platform view.
registry.registerViewFactory(
CountingPlatformView.VIEW_TYPE_ID,
new PlatformViewFactory(StandardMessageCodec.INSTANCE) {
@Override
public PlatformView create(Context context, int viewId, Object args) {
return new CountingPlatformView(context);
}
});
// Create the platform view.
int viewId = 0;
final PlatformViewsChannel.PlatformViewCreationRequest request =
new PlatformViewsChannel.PlatformViewCreationRequest(
viewId,
CountingPlatformView.VIEW_TYPE_ID,
0,
0,
128,
128,
View.LAYOUT_DIRECTION_LTR,
null);
PlatformView pView = platformViewsController.createPlatformView(request, true);
assertTrue(pView instanceof CountingPlatformView);
CountingPlatformView cpv = (CountingPlatformView) pView;
platformViewsController.configureForTextureLayerComposition(pView, request);
verify(platformViewsController.textureRegistry, times(1))
.createSurfaceProducer(TextureRegistry.SurfaceLifecycle.resetInBackground);
}
@Test
@Config(shadows = {ShadowFlutterJNI.class, ShadowPlatformTaskQueue.class})
public void itNotifiesPlatformViewsOfEngineAttachmentAndDetachment() {
@ -1572,95 +1618,96 @@ public class PlatformViewsControllerTest {
final Context context = ApplicationProvider.getApplicationContext();
final TextureRegistry registry =
new TextureRegistry() {
public void TextureRegistry() {}
spy(
new TextureRegistry() {
public void TextureRegistry() {}
@NonNull
@Override
public SurfaceTextureEntry createSurfaceTexture() {
return registerSurfaceTexture(mock(SurfaceTexture.class));
}
@NonNull
@Override
public SurfaceTextureEntry registerSurfaceTexture(
@NonNull SurfaceTexture surfaceTexture) {
return new SurfaceTextureEntry() {
@NonNull
@Override
public SurfaceTexture surfaceTexture() {
return mock(SurfaceTexture.class);
public SurfaceTextureEntry createSurfaceTexture() {
return registerSurfaceTexture(mock(SurfaceTexture.class));
}
@NonNull
@Override
public long id() {
return 0;
public SurfaceTextureEntry registerSurfaceTexture(
@NonNull SurfaceTexture surfaceTexture) {
return new SurfaceTextureEntry() {
@NonNull
@Override
public SurfaceTexture surfaceTexture() {
return mock(SurfaceTexture.class);
}
@Override
public long id() {
return 0;
}
@Override
public void release() {}
};
}
@NonNull
@Override
public void release() {}
};
}
public ImageTextureEntry createImageTexture() {
return new ImageTextureEntry() {
@Override
public long id() {
return 0;
}
@NonNull
@Override
public ImageTextureEntry createImageTexture() {
return new ImageTextureEntry() {
@Override
public long id() {
return 0;
@Override
public void release() {}
@Override
public void pushImage(Image image) {}
};
}
@NonNull
@Override
public void release() {}
public SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle) {
return new SurfaceProducer() {
@Override
public void setCallback(SurfaceProducer.Callback cb) {}
@Override
public void pushImage(Image image) {}
};
}
@Override
public long id() {
return 0;
}
@NonNull
@Override
public SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle) {
return new SurfaceProducer() {
@Override
public void setCallback(SurfaceProducer.Callback cb) {}
@Override
public void release() {}
@Override
public long id() {
return 0;
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
@Override
public void setSize(int width, int height) {}
@Override
public Surface getSurface() {
return null;
}
@Override
public boolean handlesCropAndRotation() {
return false;
}
public void scheduleFrame() {}
};
}
@Override
public void release() {}
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
@Override
public void setSize(int width, int height) {}
@Override
public Surface getSurface() {
return null;
}
@Override
public boolean handlesCropAndRotation() {
return false;
}
public void scheduleFrame() {}
};
}
};
});
platformViewsController.attach(context, registry, executor);