From 198f96cd09e816be2e0dec72decf225a610ea485 Mon Sep 17 00:00:00 2001 From: ColdPaleLight <31977171+ColdPaleLight@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:11:01 +0800 Subject: [PATCH] Ensure that unregisterTexture is called when forget to call SurfaceTextureEntry.release (flutter/engine#28304) --- .../engine/renderer/FlutterRenderer.java | 33 ++++++++ .../engine/renderer/FlutterRendererTest.java | 79 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 3111ecb43f5..c9507489ce4 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -39,6 +39,7 @@ public class FlutterRenderer implements TextureRegistry { @NonNull private final AtomicLong nextTextureId = new AtomicLong(0L); @Nullable private Surface surface; private boolean isDisplayingFlutterUi = false; + private Handler handler = new Handler(); @NonNull private final FlutterUiDisplayListener flutterUiDisplayListener = @@ -168,6 +169,38 @@ public class FlutterRenderer implements TextureRegistry { unregisterTexture(id); released = true; } + + @Override + protected void finalize() throws Throwable { + try { + if (released) { + return; + } + + handler.post(new SurfaceTextureFinalizerRunnable(id, flutterJNI)); + } finally { + super.finalize(); + } + } + } + + static final class SurfaceTextureFinalizerRunnable implements Runnable { + private final long id; + private final FlutterJNI flutterJNI; + + SurfaceTextureFinalizerRunnable(long id, @NonNull FlutterJNI flutterJNI) { + this.id = id; + this.flutterJNI = flutterJNI; + } + + @Override + public void run() { + if (!flutterJNI.isAttached()) { + return; + } + Log.v(TAG, "Releasing a SurfaceTexture (" + id + ")."); + flutterJNI.unregisterTexture(id); + } } // ------ END TextureRegistry IMPLEMENTATION ---- diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 185ed43fa08..1ec89c41011 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -4,9 +4,14 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; +import android.os.Looper; import android.view.Surface; import io.flutter.embedding.engine.FlutterJNI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -116,4 +121,78 @@ public class FlutterRendererTest { // Verify behavior under test. verify(fakeFlutterJNI, times(0)).markTextureFrameAvailable(eq(entry.id())); } + + @Test + public void itUnregistersTextureWhenSurfaceTextureFinalized() { + // Setup the test. + FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class); + when(fakeFlutterJNI.isAttached()).thenReturn(true); + FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI); + + fakeFlutterJNI.detachFromNativeAndReleaseResources(); + + FlutterRenderer.SurfaceTextureRegistryEntry entry = + (FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture(); + long id = entry.id(); + + flutterRenderer.startRenderingToSurface(fakeSurface); + + // Execute the behavior under test. + runFinalization(entry); + + shadowOf(Looper.getMainLooper()).idle(); + + flutterRenderer.stopRenderingToSurface(); + + // Verify behavior under test. + verify(fakeFlutterJNI, times(1)).unregisterTexture(eq(id)); + } + + @Test + public void itStopsUnregisteringTextureWhenDetached() { + // Setup the test. + FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class); + when(fakeFlutterJNI.isAttached()).thenReturn(false); + FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI); + + fakeFlutterJNI.detachFromNativeAndReleaseResources(); + + FlutterRenderer.SurfaceTextureRegistryEntry entry = + (FlutterRenderer.SurfaceTextureRegistryEntry) flutterRenderer.createSurfaceTexture(); + long id = entry.id(); + + flutterRenderer.startRenderingToSurface(fakeSurface); + + flutterRenderer.stopRenderingToSurface(); + + // Execute the behavior under test. + runFinalization(entry); + + shadowOf(Looper.getMainLooper()).idle(); + + // Verify behavior under test. + verify(fakeFlutterJNI, times(0)).unregisterTexture(eq(id)); + } + + void runFinalization(FlutterRenderer.SurfaceTextureRegistryEntry entry) { + CountDownLatch latch = new CountDownLatch(1); + Thread fakeFinalizer = + new Thread( + new Runnable() { + public void run() { + try { + entry.finalize(); + latch.countDown(); + } catch (Throwable e) { + // do nothing + } + } + }); + fakeFinalizer.start(); + try { + latch.await(5L, TimeUnit.SECONDS); + } catch (Throwable e) { + // do nothing + } + } }