[android] only release background image readers on Android 14. (#165942)

Fixes https://github.com/flutter/flutter/issues/163561
Fixes https://github.com/flutter/flutter/issues/156488
Fixes https://github.com/flutter/flutter/issues/156489
Fixes https://github.com/flutter/flutter/issues/163520

Forked from https://github.com/flutter/flutter/pull/163692


Only release background image readers on Android 14. I believe reader
disposal is the ultimate cause of
https://github.com/flutter/flutter/issues/162147 , where older android
devices don't seem to handle backgrounding the same way we expect on
newer devices. The result of this is a crash in HWUI, which is
unexpected.

Since we only ever did the background release to work around an ANdroid
14 bug, and because it breaks other functionality like background
playback - we should remove it for all targets besides 14.
This commit is contained in:
Jonah Williams
2025-03-27 16:31:52 -07:00
committed by GitHub
parent a59795e812
commit d6ec7d4dc0
19 changed files with 141 additions and 76 deletions

View File

@@ -16,10 +16,12 @@ namespace flutter {
ImageExternalTexture::ImageExternalTexture(
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade)
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageLifecycle lifecycle)
: Texture(id),
image_texture_entry_(image_texture_entry),
jni_facade_(jni_facade) {}
jni_facade_(jni_facade),
texture_lifecycle_(lifecycle) {}
ImageExternalTexture::~ImageExternalTexture() = default;
@@ -66,8 +68,19 @@ void ImageExternalTexture::OnGrContextCreated() {
// Implementing flutter::ContextListener.
void ImageExternalTexture::OnGrContextDestroyed() {
if (state_ == AttachmentState::kAttached) {
dl_image_.reset();
image_lru_.Clear();
switch (texture_lifecycle_) {
case ImageLifecycle::kReset: {
dl_image_.reset();
image_lru_.Clear();
} break;
case ImageLifecycle::kKeepAlive:
// Intentionally do nothing.
///
// If we reset the image, we are not able to re-acquire it, but the
// producer of the image will not know to reproduce it, resulting in a
// blank image. See https://github.com/flutter/flutter/issues/163561.
break;
}
Detach();
}
state_ = AttachmentState::kDetached;

View File

@@ -35,10 +35,14 @@ namespace flutter {
///
class ImageExternalTexture : public flutter::Texture {
public:
/// Whether the last image should be reset when the context is destroyed.
enum class ImageLifecycle { kReset, kKeepAlive };
explicit ImageExternalTexture(
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade);
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageLifecycle lifecycle);
// |flutter::Texture|
virtual ~ImageExternalTexture();
@@ -100,6 +104,7 @@ class ImageExternalTexture : public flutter::Texture {
// |flutter::ContextListener|
void OnGrContextDestroyed() override;
const ImageLifecycle texture_lifecycle_;
FML_DISALLOW_COPY_AND_ASSIGN(ImageExternalTexture);
};

View File

@@ -24,8 +24,9 @@ namespace flutter {
ImageExternalTextureGL::ImageExternalTextureGL(
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade)
: ImageExternalTexture(id, image_texture_entry, jni_facade) {}
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle)
: ImageExternalTexture(id, image_texture_entry, jni_facade, lifecycle) {}
void ImageExternalTextureGL::Attach(PaintContext& context) {
if (state_ == AttachmentState::kUninitialized) {

View File

@@ -20,7 +20,8 @@ class ImageExternalTextureGL : public ImageExternalTexture {
ImageExternalTextureGL(
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_textury_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade);
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle);
protected:
// |ImageExternalTexture|

View File

@@ -13,8 +13,9 @@ ImageExternalTextureGLImpeller::ImageExternalTextureGLImpeller(
const std::shared_ptr<impeller::ContextGLES>& context,
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_textury_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade)
: ImageExternalTextureGL(id, image_textury_entry, jni_facade),
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle)
: ImageExternalTextureGL(id, image_textury_entry, jni_facade, lifecycle),
impeller_context_(context) {}
void ImageExternalTextureGLImpeller::Detach() {}

View File

@@ -22,7 +22,8 @@ class ImageExternalTextureGLImpeller : public ImageExternalTextureGL {
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>&
hardware_buffer_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade);
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle);
private:
// |ImageExternalTexture|

View File

@@ -13,8 +13,9 @@ ImageExternalTextureGLSkia::ImageExternalTextureGLSkia(
const std::shared_ptr<AndroidContextGLSkia>& context,
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade)
: ImageExternalTextureGL(id, image_texture_entry, jni_facade) {}
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle)
: ImageExternalTextureGL(id, image_texture_entry, jni_facade, lifecycle) {}
void ImageExternalTextureGLSkia::Attach(PaintContext& context) {
if (state_ == AttachmentState::kUninitialized) {

View File

@@ -19,7 +19,8 @@ class ImageExternalTextureGLSkia : public ImageExternalTextureGL {
const std::shared_ptr<AndroidContextGLSkia>& context,
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_textury_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade);
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle);
private:
// |ImageExternalTexture|

View File

@@ -20,8 +20,9 @@ ImageExternalTextureVKImpeller::ImageExternalTextureVKImpeller(
const std::shared_ptr<impeller::ContextVK>& impeller_context,
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade)
: ImageExternalTexture(id, image_texture_entry, jni_facade),
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle)
: ImageExternalTexture(id, image_texture_entry, jni_facade, lifecycle),
impeller_context_(impeller_context) {}
ImageExternalTextureVKImpeller::~ImageExternalTextureVKImpeller() {}

View File

@@ -23,7 +23,8 @@ class ImageExternalTextureVKImpeller : public ImageExternalTexture {
int64_t id,
const fml::jni::ScopedJavaGlobalRef<jobject>&
hardware_buffer_texture_entry,
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade);
const std::shared_ptr<PlatformViewAndroidJNI>& jni_facade,
ImageExternalTexture::ImageLifecycle lifecycle);
~ImageExternalTextureVKImpeller() override;

View File

@@ -938,19 +938,23 @@ public class FlutterJNI {
*/
@UiThread
public void registerImageTexture(
long textureId, @NonNull TextureRegistry.ImageConsumer imageTexture) {
long textureId,
@NonNull TextureRegistry.ImageConsumer imageTexture,
boolean resetOnBackground) {
ensureRunningOnMainThread();
ensureAttachedToNative();
nativeRegisterImageTexture(
nativeShellHolderId,
textureId,
new WeakReference<TextureRegistry.ImageConsumer>(imageTexture));
new WeakReference<TextureRegistry.ImageConsumer>(imageTexture),
resetOnBackground);
}
private native void nativeRegisterImageTexture(
long nativeShellHolderId,
long textureId,
@NonNull WeakReference<TextureRegistry.ImageConsumer> imageTexture);
@NonNull WeakReference<TextureRegistry.ImageConsumer> imageTexture,
boolean resetOnBackground);
/**
* Call this method to inform Flutter that a texture previously registered with {@link

View File

@@ -186,37 +186,16 @@ public class FlutterRenderer implements TextureRegistry {
*/
@NonNull
@Override
public SurfaceProducer createSurfaceProducer() {
// Prior to Impeller, Flutter on Android *only* ran on OpenGLES (via Skia). That
// meant that
// plugins (i.e. end-users) either explicitly created a SurfaceTexture (via
// createX/registerX) or an ImageTexture (via createX/registerX).
//
// In an Impeller world, which for the first time uses (if available) a Vulkan
// rendering
// backend, it is no longer possible (at least not trivially) to render an
// OpenGLES-provided
// texture (SurfaceTexture) in a Vulkan context.
//
// This function picks the "best" rendering surface based on the Android
// runtime, and
// provides a consumer-agnostic SurfaceProducer (which in turn vends a Surface),
// and has
// plugins (i.e. end-users) use the Surface instead, letting us "hide" the
// consumer-side
// of the implementation.
//
// tl;dr: If ImageTexture is available, we use it, otherwise we use a
// SurfaceTexture.
// Coincidentally, if ImageTexture is available, we are also on an Android
// version that is
// running Vulkan, so we don't have to worry about it not being supported.
public SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle) {
final SurfaceProducer entry;
if (!debugForceSurfaceProducerGlTextures && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
final long id = nextTextureId.getAndIncrement();
final ImageReaderSurfaceProducer producer = new ImageReaderSurfaceProducer(id);
registerImageTexture(id, producer);
addOnTrimMemoryListener(producer);
boolean reset = lifecycle == SurfaceLifecycle.resetInBackground;
registerImageTexture(id, producer, reset);
if (reset) {
addOnTrimMemoryListener(producer);
}
imageReaderProducers.add(producer);
Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id);
entry = producer;
@@ -282,7 +261,7 @@ public class FlutterRenderer implements TextureRegistry {
final ImageTextureRegistryEntry entry =
new ImageTextureRegistryEntry(nextTextureId.getAndIncrement());
Log.v(TAG, "New ImageTextureEntry ID: " + entry.id());
registerImageTexture(entry.id(), entry);
registerImageTexture(entry.id(), entry, /*resetOnBackground=*/ false);
return entry;
}
@@ -1279,25 +1258,23 @@ public class FlutterRenderer implements TextureRegistry {
displayFeaturesState);
}
// TODO(mattcarroll): describe the native behavior that this invokes
// TODO(mattcarroll): determine if this is nullable or nonnull
public Bitmap getBitmap() {
return flutterJNI.getBitmap();
}
// TODO(mattcarroll): describe the native behavior that this invokes
public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) {
flutterJNI.dispatchPointerDataPacket(buffer, position);
}
// TODO(mattcarroll): describe the native behavior that this invokes
private void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) {
flutterJNI.registerTexture(textureId, textureWrapper);
}
private void registerImageTexture(
long textureId, @NonNull TextureRegistry.ImageConsumer imageTexture) {
flutterJNI.registerImageTexture(textureId, imageTexture);
long textureId,
@NonNull TextureRegistry.ImageConsumer imageTexture,
boolean resetOnBackground) {
flutterJNI.registerImageTexture(textureId, imageTexture, resetOnBackground);
}
@VisibleForTesting
@@ -1305,27 +1282,22 @@ public class FlutterRenderer implements TextureRegistry {
flutterJNI.scheduleFrame();
}
// TODO(mattcarroll): describe the native behavior that this invokes
private void unregisterTexture(long textureId) {
flutterJNI.unregisterTexture(textureId);
}
// TODO(mattcarroll): describe the native behavior that this invokes
public boolean isSoftwareRenderingEnabled() {
return flutterJNI.getIsSoftwareRenderingEnabled();
}
// TODO(mattcarroll): describe the native behavior that this invokes
public void setAccessibilityFeatures(int flags) {
flutterJNI.setAccessibilityFeatures(flags);
}
// TODO(mattcarroll): describe the native behavior that this invokes
public void setSemanticsEnabled(boolean enabled) {
flutterJNI.setSemanticsEnabled(enabled);
}
// TODO(mattcarroll): describe the native behavior that this invokes
public void dispatchSemanticsAction(
int nodeId, int action, @Nullable ByteBuffer args, int argsPosition) {
flutterJNI.dispatchSemanticsAction(nodeId, action, args, argsPosition);

View File

@@ -977,7 +977,12 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
private static PlatformViewRenderTarget makePlatformViewRenderTarget(
TextureRegistry textureRegistry) {
if (enableSurfaceProducerRenderTarget && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
final TextureRegistry.SurfaceProducer textureEntry = textureRegistry.createSurfaceProducer();
TextureRegistry.SurfaceLifecycle lifecycle =
Build.VERSION.SDK_INT == API_LEVELS.API_34
? TextureRegistry.SurfaceLifecycle.resetInBackground
: TextureRegistry.SurfaceLifecycle.manual;
final TextureRegistry.SurfaceProducer textureEntry =
textureRegistry.createSurfaceProducer(lifecycle);
Log.i(TAG, "PlatformView is using SurfaceProducer backend");
return new SurfaceProducerPlatformViewRenderTarget(textureEntry);
}

View File

@@ -18,12 +18,57 @@ import androidx.annotation.Nullable;
*/
public interface TextureRegistry {
/**
* Creates and registers a SurfaceProducer texture managed by the Flutter engine.
* Creates and registers a {@link SurfaceProducer}, or a Flutter-managed {@link Surface}.
*
* <p>Uses the {@link SurfaceLifecycle#manual} lifecycle implicitly.
*
* @return A SurfaceProducer.
*/
@NonNull
SurfaceProducer createSurfaceProducer();
default SurfaceProducer createSurfaceProducer() {
return createSurfaceProducer(SurfaceLifecycle.manual);
}
/**
* How a {@link SurfaceProducer} created by {@link #createSurfaceProducer()} manages the lifecycle
* of the created surface.
*/
enum SurfaceLifecycle {
/**
* The surface and latest image should be kept, even if the app enters the background.
*
* <p>The application, or calling code, can choose to (manually) reset the surface at the
* appropriate time (such as to lower memory pressure, or cleanup an unused surface), but by
* default the surface will never be reset, and as a result, new images do not have to be drawn
* to the surface.
*
* <p>This is an appropriate lifecycle for external textures, as it is not guaranteed that new
* images will be drawn to the surface, and whether the image should be kept when the app is
* backgrounded.
*/
manual,
/**
* The surface will be reset if the app enters the background.
*
* <p>While the application can choose to manually reset the surface, Flutter may automatically
* reset the surface when the app enters the background. If the surface is reset, and no new
* images are drawn to the surface, the texture will appear blank.
*
* <p>This is an appropriate lifecycle for platform views, as the platform implementation will
* request a new surface, and draw to, as appropriate when resuming from the background, and
* producing a new image when coming back to the foreground.
*/
resetInBackground
}
/**
* Creates and a {@link SurfaceProducer}, or a Flutter-managed {@link Surface}.
*
* @param lifecycle Whether to automatically reset the last image and release the surface.
* @return A SurfaceProducer.
*/
@NonNull
SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle);
/**
* Creates and registers a SurfaceTexture managed by the Flutter engine.

View File

@@ -348,26 +348,27 @@ void PlatformViewAndroid::RegisterExternalTexture(
void PlatformViewAndroid::RegisterImageTexture(
int64_t texture_id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry) {
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
ImageExternalTexture::ImageLifecycle lifecycle) {
switch (android_context_->RenderingApi()) {
case AndroidRenderingAPI::kImpellerOpenGLES:
// Impeller GLES.
RegisterTexture(std::make_shared<ImageExternalTextureGLImpeller>(
std::static_pointer_cast<impeller::ContextGLES>(
android_context_->GetImpellerContext()),
texture_id, image_texture_entry, jni_facade_));
texture_id, image_texture_entry, jni_facade_, lifecycle));
break;
case AndroidRenderingAPI::kSkiaOpenGLES:
// Legacy GL.
RegisterTexture(std::make_shared<ImageExternalTextureGLSkia>(
std::static_pointer_cast<AndroidContextGLSkia>(android_context_),
texture_id, image_texture_entry, jni_facade_));
texture_id, image_texture_entry, jni_facade_, lifecycle));
break;
case AndroidRenderingAPI::kImpellerVulkan:
RegisterTexture(std::make_shared<ImageExternalTextureVKImpeller>(
std::static_pointer_cast<impeller::ContextVK>(
android_context_->GetImpellerContext()),
texture_id, image_texture_entry, jni_facade_));
texture_id, image_texture_entry, jni_facade_, lifecycle));
break;
case AndroidRenderingAPI::kSoftware:
FML_LOG(INFO) << "Software rendering does not support external textures.";

View File

@@ -20,6 +20,7 @@
#include "flutter/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate.h"
#include "flutter/shell/platform/android/surface/android_native_window.h"
#include "flutter/shell/platform/android/surface/android_surface.h"
#include "shell/platform/android/image_external_texture.h"
namespace flutter {
@@ -93,7 +94,8 @@ class PlatformViewAndroid final : public PlatformView {
void RegisterImageTexture(
int64_t texture_id,
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry);
const fml::jni::ScopedJavaGlobalRef<jobject>& image_texture_entry,
ImageExternalTexture::ImageLifecycle lifecycle);
// |PlatformView|
void LoadDartDeferredLibrary(

View File

@@ -530,10 +530,16 @@ static void RegisterImageTexture(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jlong texture_id,
jobject image_texture_entry) {
jobject image_texture_entry,
jboolean reset_on_background) {
ImageExternalTexture::ImageLifecycle lifecycle =
reset_on_background ? ImageExternalTexture::ImageLifecycle::kReset
: ImageExternalTexture::ImageLifecycle::kKeepAlive;
ANDROID_SHELL_HOLDER->GetPlatformView()->RegisterImageTexture(
static_cast<int64_t>(texture_id), //
fml::jni::ScopedJavaGlobalRef<jobject>(env, image_texture_entry) //
static_cast<int64_t>(texture_id), //
fml::jni::ScopedJavaGlobalRef<jobject>(env, image_texture_entry), //
lifecycle //
);
}
@@ -811,7 +817,7 @@ bool RegisterApi(JNIEnv* env) {
{
.name = "nativeRegisterImageTexture",
.signature = "(JJLjava/lang/ref/"
"WeakReference;)V",
"WeakReference;Z)V",
.fnPtr = reinterpret_cast<void*>(&RegisterImageTexture),
},
{

View File

@@ -829,7 +829,8 @@ public class FlutterRendererTest {
@SuppressWarnings({"deprecation", "removal"})
public void ImageReaderSurfaceProducerIsCleanedUpOnTrimMemory() {
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
TextureRegistry.SurfaceProducer producer =
flutterRenderer.createSurfaceProducer(TextureRegistry.SurfaceLifecycle.resetInBackground);
// Create and set a mock callback.
TextureRegistry.SurfaceProducer.Callback callback =
@@ -851,7 +852,8 @@ public class FlutterRendererTest {
public void ImageReaderSurfaceProducerSignalsCleanupBeforeDestroying() throws Exception {
// Regression test for https://github.com/flutter/flutter/issues/160933.
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
TextureRegistry.SurfaceProducer producer =
flutterRenderer.createSurfaceProducer(TextureRegistry.SurfaceLifecycle.resetInBackground);
// Ensure the callbacks were actually called.
// Note this needs to be an object in order to be accessed in the callback.
@@ -902,7 +904,8 @@ public class FlutterRendererTest {
public void ImageReaderSurfaceProducerUnsubscribesWhenReleased() {
// Regression test for https://github.com/flutter/flutter/issues/156434.
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
TextureRegistry.SurfaceProducer producer =
flutterRenderer.createSurfaceProducer(TextureRegistry.SurfaceLifecycle.resetInBackground);
// Create and set a mock callback.
TextureRegistry.SurfaceProducer.Callback callback =
@@ -924,7 +927,8 @@ public class FlutterRendererTest {
@SuppressWarnings({"deprecation", "removal"})
public void ImageReaderSurfaceProducerIsCreatedOnLifecycleResume() throws Exception {
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
TextureRegistry.SurfaceProducer producer =
flutterRenderer.createSurfaceProducer(TextureRegistry.SurfaceLifecycle.resetInBackground);
// Create a callback.
CountDownLatch latch = new CountDownLatch(1);

View File

@@ -1621,7 +1621,7 @@ public class PlatformViewsControllerTest {
@NonNull
@Override
public SurfaceProducer createSurfaceProducer() {
public SurfaceProducer createSurfaceProducer(SurfaceLifecycle lifecycle) {
return new SurfaceProducer() {
@Override
public void setCallback(SurfaceProducer.Callback cb) {}