diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index cfe8bbc7221..a7f96245153 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -17,3 +17,4 @@ * Fixes instance manager hot restart behavior and fixes Java casting issue. * Implements image capture. * Fixes cast of CameraInfo to fix integration test failure. +* Updates internal Java InstanceManager to only stop finalization callbacks when stopped. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java index 08ecc847aa1..d0176a1812c 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraAndroidCameraxPlugin.java @@ -31,7 +31,7 @@ public CameraAndroidCameraxPlugin() {} void setUp(BinaryMessenger binaryMessenger, Context context, TextureRegistry textureRegistry) { // Set up instance manager. instanceManager = - InstanceManager.open( + InstanceManager.create( identifier -> { new GeneratedCameraXLibrary.JavaObjectFlutterApi(binaryMessenger) .dispose(identifier, reply -> {}); @@ -66,7 +66,7 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (instanceManager != null) { - instanceManager.close(); + instanceManager.stopFinalizationListener(); } } diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java index 4e7fa3e9e16..43004a6aab7 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/InstanceManager.java @@ -7,6 +7,7 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; @@ -30,9 +31,6 @@ */ @SuppressWarnings("unchecked") public class InstanceManager { - /// Constant returned from #addHostCreatedInstance() if the manager is closed. - public static final int INSTANCE_CLOSED = -1; - // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously from Dart. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, @@ -40,7 +38,6 @@ public class InstanceManager { private static final long MIN_HOST_CREATED_IDENTIFIER = 65536; private static final long CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL = 30000; private static final String TAG = "InstanceManager"; - private static final String CLOSED_WARNING = "Method was called while the manager was closed."; /** Interface for listening when a weak reference of an instance is removed from the manager. */ public interface FinalizationListener { @@ -59,17 +56,18 @@ public interface FinalizationListener { private final FinalizationListener finalizationListener; private long nextIdentifier = MIN_HOST_CREATED_IDENTIFIER; - private boolean isClosed = false; + private boolean hasFinalizationListenerStopped = false; /** * Instantiate a new manager. * - *

When the manager is no longer needed, {@link #close()} must be called. + *

When the manager is no longer needed, {@link #stopFinalizationListener()} must be called. * * @param finalizationListener the listener for garbage collected weak references. * @return a new `InstanceManager`. */ - public static InstanceManager open(FinalizationListener finalizationListener) { + @NonNull + public static InstanceManager create(FinalizationListener finalizationListener) { return new InstanceManager(finalizationListener); } @@ -85,15 +83,12 @@ private InstanceManager(FinalizationListener finalizationListener) { * * @param identifier the identifier paired to an instance. * @param the expected return type. - * @return the removed instance if the manager contains the given identifier, otherwise null if - * the manager doesn't contain the value or the manager is closed. + * @return the removed instance if the manager contains the given identifier, otherwise `null` if + * the manager doesn't contain the value. */ @Nullable public T remove(long identifier) { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return null; - } + logWarningIfFinalizationListenerHasStopped(); return (T) strongInstances.remove(identifier); } @@ -111,14 +106,12 @@ public T remove(long identifier) { * * @param instance an instance that may be stored in the manager. * @return the identifier associated with `instance` if the manager contains the value, otherwise - * null if the manager doesn't contain the value or the manager is closed. + * `null` if the manager doesn't contain the value. */ @Nullable public Long getIdentifierForStrongReference(Object instance) { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return null; - } + logWarningIfFinalizationListenerHasStopped(); + final Long identifier = identifiers.get(instance); if (identifier != null) { strongInstances.put(identifier, instance); @@ -133,17 +126,12 @@ public Long getIdentifierForStrongReference(Object instance) { * allows two objects that are equivalent (e.g. the `equals` method returns true and their * hashcodes are equal) to both be added. * - *

If the manager is closed, the addition is ignored and a warning is logged. - * * @param instance the instance to be stored. * @param identifier the identifier to be paired with instance. This value must be >= 0 and * unique. */ public void addDartCreatedInstance(Object instance, long identifier) { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return; - } + logWarningIfFinalizationListenerHasStopped(); addInstance(instance, identifier); } @@ -151,14 +139,12 @@ public void addDartCreatedInstance(Object instance, long identifier) { * Adds a new instance that was instantiated from the host platform. * * @param instance the instance to be stored. This must be unique to all other added instances. - * @return the unique identifier (>= 0) stored with instance. If the manager is closed, returns - * -1. + * @return the unique identifier (>= 0) stored with instance. */ public long addHostCreatedInstance(Object instance) { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return INSTANCE_CLOSED; - } else if (containsInstance(instance)) { + logWarningIfFinalizationListenerHasStopped(); + + if (containsInstance(instance)) { throw new IllegalArgumentException( String.format("Instance of `%s` has already been added.", instance.getClass())); } @@ -173,14 +159,12 @@ public long addHostCreatedInstance(Object instance) { * @param identifier the identifier associated with an instance. * @param the expected return type. * @return the instance associated with `identifier` if the manager contains the value, otherwise - * null if the manager doesn't contain the value or the manager is closed. + * `null` if the manager doesn't contain the value. */ @Nullable public T getInstance(long identifier) { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return null; - } + logWarningIfFinalizationListenerHasStopped(); + final WeakReference instance = (WeakReference) weakInstances.get(identifier); if (instance != null) { return instance.get(); @@ -192,26 +176,23 @@ public T getInstance(long identifier) { * Returns whether this manager contains the given `instance`. * * @param instance the instance whose presence in this manager is to be tested. - * @return whether this manager contains the given `instance`. If the manager is closed, returns - * `false`. + * @return whether this manager contains the given `instance`. */ public boolean containsInstance(Object instance) { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return false; - } + logWarningIfFinalizationListenerHasStopped(); return identifiers.containsKey(instance); } /** - * Closes the manager and releases resources. + * Stop the periodic run of the {@link FinalizationListener} for instances that have been garbage + * collected. * - *

Methods called after this one will be ignored and log a warning. + *

The InstanceManager can continue to be used, but the {@link FinalizationListener} will no + * longer be called and methods will log a warning. */ - public void close() { + public void stopFinalizationListener() { handler.removeCallbacks(this::releaseAllFinalizedInstances); - isClosed = true; - clear(); + hasFinalizationListenerStopped = true; } /** @@ -227,15 +208,20 @@ public void clear() { } /** - * Whether the manager has released resources and is no longer usable. + * Whether the {@link FinalizationListener} is still being called for instances that are garbage + * collected. * - *

See {@link #close()}. + *

See {@link #stopFinalizationListener()}. */ - public boolean isClosed() { - return isClosed; + public boolean hasFinalizationListenerStopped() { + return hasFinalizationListenerStopped; } private void releaseAllFinalizedInstances() { + if (hasFinalizationListenerStopped()) { + return; + } + WeakReference reference; while ((reference = (WeakReference) referenceQueue.poll()) != null) { final Long identifier = weakReferencesToIdentifiers.remove(reference); @@ -263,4 +249,10 @@ private void addInstance(Object instance, long identifier) { weakReferencesToIdentifiers.put(weakReference, identifier); strongInstances.put(identifier, instance); } + + private void logWarningIfFinalizationListenerHasStopped() { + if (hasFinalizationListenerStopped()) { + Log.w(TAG, "The manager was used after calls to the FinalizationListener have been stopped."); + } + } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java index 663d0e2f26d..0cd85848e71 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraInfoTest.java @@ -32,12 +32,12 @@ public class CameraInfoTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); } @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSelectorTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSelectorTest.java index 2b27e08b579..81ae31f7475 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSelectorTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraSelectorTest.java @@ -36,12 +36,12 @@ public class CameraSelectorTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); } @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java index e2135b3945b..af4fb2ce8e6 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/CameraTest.java @@ -30,12 +30,12 @@ public class CameraTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); } @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java index c65bc3d3922..d1881b39a43 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java @@ -48,14 +48,14 @@ public class ImageCaptureTest { @Before public void setUp() throws Exception { - testInstanceManager = spy(InstanceManager.open(identifier -> {})); + testInstanceManager = spy(InstanceManager.create(identifier -> {})); context = mock(Context.class); mockedStaticFile = mockStatic(File.class); } @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); mockedStaticFile.close(); } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java index 34d8add9fc9..7dd5e62acab 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/InstanceManagerTest.java @@ -15,7 +15,7 @@ public class InstanceManagerTest { @Test public void addDartCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); @@ -24,12 +24,12 @@ public void addDartCreatedInstance() { assertEquals((Long) 0L, instanceManager.getIdentifierForStrongReference(object)); assertTrue(instanceManager.containsInstance(object)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test public void addHostCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object object = new Object(); long identifier = instanceManager.addHostCreatedInstance(object); @@ -38,12 +38,12 @@ public void addHostCreatedInstance() { assertEquals(object, instanceManager.getInstance(identifier)); assertTrue(instanceManager.containsInstance(object)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test public void removeReturnsRemovedObjectAndClearsIdentifier() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); @@ -58,60 +58,12 @@ public void removeReturnsRemovedObjectAndClearsIdentifier() { assertNull(instanceManager.getInstance(0)); - instanceManager.close(); - } - - @Test - public void removeReturnsNullWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertNull(instanceManager.remove(0)); - } - - @Test - public void getIdentifierForStrongReferenceReturnsNullWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertNull(instanceManager.getIdentifierForStrongReference(object)); - } - - @Test - public void addHostCreatedInstanceReturnsNegativeOneWhenClosed() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.close(); - - assertEquals(instanceManager.addHostCreatedInstance(new Object()), -1L); - } - - @Test - public void getInstanceReturnsNullWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertNull(instanceManager.getInstance(0)); - } - - @Test - public void containsInstanceReturnsFalseWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertFalse(instanceManager.containsInstance(object)); + instanceManager.stopFinalizationListener(); } @Test public void clear() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object instance = new Object(); @@ -121,12 +73,12 @@ public void clear() { instanceManager.clear(); assertFalse(instanceManager.containsInstance(instance)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test public void canAddSameObjectWithAddDartCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object instance = new Object(); @@ -138,37 +90,51 @@ public void canAddSameObjectWithAddDartCreatedInstance() { assertEquals(instanceManager.getInstance(0), instance); assertEquals(instanceManager.getInstance(1), instance); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test(expected = IllegalArgumentException.class) public void cannotAddSameObjectsWithAddHostCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object instance = new Object(); instanceManager.addHostCreatedInstance(instance); instanceManager.addHostCreatedInstance(instance); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test(expected = IllegalArgumentException.class) public void cannotUseIdentifierLessThanZero() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); instanceManager.addDartCreatedInstance(new Object(), -1); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test(expected = IllegalArgumentException.class) public void identifiersMustBeUnique() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); instanceManager.addDartCreatedInstance(new Object(), 0); instanceManager.addDartCreatedInstance(new Object(), 0); - instanceManager.close(); + instanceManager.stopFinalizationListener(); + } + + @Test + public void managerIsUsableWhileListenerHasStopped() { + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); + instanceManager.stopFinalizationListener(); + + final Object instance = new Object(); + final long identifier = 0; + + instanceManager.addDartCreatedInstance(instance, identifier); + assertEquals(instanceManager.getInstance(identifier), instance); + assertEquals(instanceManager.getIdentifierForStrongReference(instance), (Long) identifier); + assertTrue(instanceManager.containsInstance(instance)); } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/JavaObjectHostApiTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/JavaObjectHostApiTest.java index cce3341aaa8..d05bf5bc46b 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/JavaObjectHostApiTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/JavaObjectHostApiTest.java @@ -11,7 +11,7 @@ public class JavaObjectHostApiTest { @Test public void dispose() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final JavaObjectHostApiImpl hostApi = new JavaObjectHostApiImpl(instanceManager); @@ -27,6 +27,6 @@ public void dispose() { assertNull(instanceManager.getInstance(0)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } } diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java index b76a4c91842..abf99257368 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/PreviewTest.java @@ -49,12 +49,12 @@ public class PreviewTest { @Before public void setUp() { - testInstanceManager = spy(InstanceManager.open(identifier -> {})); + testInstanceManager = spy(InstanceManager.create(identifier -> {})); } @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java index bfe375854c4..f7987143b98 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ProcessCameraProviderTest.java @@ -52,13 +52,13 @@ public class ProcessCameraProviderTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); context = ApplicationProvider.getApplicationContext(); } @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/camera/camera_android_camerax/example/android/app/build.gradle b/packages/camera/camera_android_camerax/example/android/app/build.gradle index 2d59a59a420..e2001f9ab77 100644 --- a/packages/camera/camera_android_camerax/example/android/app/build.gradle +++ b/packages/camera/camera_android_camerax/example/android/app/build.gradle @@ -62,5 +62,6 @@ flutter { dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test:runner:1.2.0' - api 'androidx.test:core:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + api 'androidx.test:core:1.4.0' } diff --git a/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/cameraxexample/InstanceManagerTest.java b/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/cameraxexample/InstanceManagerTest.java new file mode 100644 index 00000000000..5e4454de80d --- /dev/null +++ b/packages/camera/camera_android_camerax/example/android/app/src/androidTest/java/io/flutter/plugins/cameraxexample/InstanceManagerTest.java @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.cameraexample; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.plugins.camerax.InstanceManager; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class InstanceManagerTest { + @Test + public void managerDoesNotTriggerFinalizationListenerWhenStopped() throws InterruptedException { + final boolean[] callbackTriggered = {false}; + final InstanceManager instanceManager = + InstanceManager.create(identifier -> callbackTriggered[0] = true); + instanceManager.stopFinalizationListener(); + + Object object = new Object(); + instanceManager.addDartCreatedInstance(object, 0); + + assertEquals(object, instanceManager.remove(0)); + + // To allow for object to be garbage collected. + //noinspection UnusedAssignment + object = null; + + Runtime.getRuntime().gc(); + + // Wait for the interval after finalized callbacks are made for garbage collected objects. + // See InstanceManager.CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL. + Thread.sleep(30000); + + assertNull(instanceManager.getInstance(0)); + assertFalse(callbackTriggered[0]); + } +} diff --git a/packages/camera/camera_android_camerax/example/android/build.gradle b/packages/camera/camera_android_camerax/example/android/build.gradle index 7c909c6116c..45cb407e8d0 100644 --- a/packages/camera/camera_android_camerax/example/android/build.gradle +++ b/packages/camera/camera_android_camerax/example/android/build.gradle @@ -26,7 +26,7 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/packages/camera/camera_android_camerax/example/pubspec.yaml b/packages/camera/camera_android_camerax/example/pubspec.yaml index 1a055e7913c..8e6c02572b9 100644 --- a/packages/camera/camera_android_camerax/example/pubspec.yaml +++ b/packages/camera/camera_android_camerax/example/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: video_player: ^2.4.10 dev_dependencies: + espresso: ^0.2.0 flutter_test: sdk: flutter integration_test: diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md index 1e1796de73b..f63588727f8 100644 --- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md +++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.5.2 + +* Updates internal Java InstanceManager to only stop finalization callbacks when stopped. + ## 3.5.1 * Updates pigeon dev dependency to `9.2.4`. diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java index 5fa19d21ce2..38f4044c51e 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java @@ -31,17 +31,13 @@ */ @SuppressWarnings("unchecked") public class InstanceManager { - /// Constant returned from #addHostCreatedInstance() if the manager is closed. - public static final int INSTANCE_CLOSED = -1; - // Identifiers are locked to a specific range to avoid collisions with objects // created simultaneously from Dart. // Host uses identifiers >= 2^16 and Dart is expected to use values n where, // 0 <= n < 2^16. private static final long MIN_HOST_CREATED_IDENTIFIER = 65536; - private static final long CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL = 30000; + private static final long CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL = 3000; private static final String TAG = "InstanceManager"; - private static final String CLOSED_WARNING = "Method was called while the manager was closed."; /** Interface for listening when a weak reference of an instance is removed from the manager. */ public interface FinalizationListener { @@ -60,18 +56,18 @@ public interface FinalizationListener { private final FinalizationListener finalizationListener; private long nextIdentifier = MIN_HOST_CREATED_IDENTIFIER; - private boolean isClosed = false; + private boolean hasFinalizationListenerStopped = false; /** * Instantiate a new manager. * - *

When the manager is no longer needed, {@link #close()} must be called. + *

When the manager is no longer needed, {@link #stopFinalizationListener()} must be called. * * @param finalizationListener the listener for garbage collected weak references. * @return a new `InstanceManager`. */ @NonNull - public static InstanceManager open(@NonNull FinalizationListener finalizationListener) { + public static InstanceManager create(@NonNull FinalizationListener finalizationListener) { return new InstanceManager(finalizationListener); } @@ -87,14 +83,12 @@ private InstanceManager(FinalizationListener finalizationListener) { * * @param identifier the identifier paired to an instance. * @param the expected return type. - * @return the removed instance if the manager contains the given identifier, otherwise null if - * the manager doesn't contain the value or the manager is closed. + * @return the removed instance if the manager contains the given identifier, otherwise `null` if + * the manager doesn't contain the value. */ @Nullable public T remove(long identifier) { - if (assertNotClosed()) { - return null; - } + logWarningIfFinalizationListenerHasStopped(); return (T) strongInstances.remove(identifier); } @@ -112,13 +106,12 @@ public T remove(long identifier) { * * @param instance an instance that may be stored in the manager. * @return the identifier associated with `instance` if the manager contains the value, otherwise - * null if the manager doesn't contain the value or the manager is closed. + * `null` if the manager doesn't contain the value. */ @Nullable public Long getIdentifierForStrongReference(@Nullable Object instance) { - if (assertNotClosed()) { - return null; - } + logWarningIfFinalizationListenerHasStopped(); + final Long identifier = identifiers.get(instance); if (identifier != null) { strongInstances.put(identifier, instance); @@ -133,16 +126,12 @@ public Long getIdentifierForStrongReference(@Nullable Object instance) { * allows two objects that are equivalent (e.g. the `equals` method returns true and their * hashcodes are equal) to both be added. * - *

If the manager is closed, the addition is ignored and a warning is logged. - * * @param instance the instance to be stored. * @param identifier the identifier to be paired with instance. This value must be >= 0 and * unique. */ public void addDartCreatedInstance(@NonNull Object instance, long identifier) { - if (assertNotClosed()) { - return; - } + logWarningIfFinalizationListenerHasStopped(); addInstance(instance, identifier); } @@ -150,13 +139,10 @@ public void addDartCreatedInstance(@NonNull Object instance, long identifier) { * Adds a new instance that was instantiated from the host platform. * * @param instance the instance to be stored. This must be unique to all other added instances. - * @return the unique identifier stored with instance. If the manager is closed, returns -1. - * Otherwise, returns a value >= 0. + * @return the unique identifier (>= 0) stored with instance. */ public long addHostCreatedInstance(@NonNull Object instance) { - if (assertNotClosed()) { - return INSTANCE_CLOSED; - } + logWarningIfFinalizationListenerHasStopped(); if (containsInstance(instance)) { throw new IllegalArgumentException( @@ -173,13 +159,11 @@ public long addHostCreatedInstance(@NonNull Object instance) { * @param identifier the identifier associated with an instance. * @param the expected return type. * @return the instance associated with `identifier` if the manager contains the value, otherwise - * null if the manager doesn't contain the value or the manager is closed. + * `null` if the manager doesn't contain the value. */ @Nullable public T getInstance(long identifier) { - if (assertNotClosed()) { - return null; - } + logWarningIfFinalizationListenerHasStopped(); final WeakReference instance = (WeakReference) weakInstances.get(identifier); if (instance != null) { @@ -192,25 +176,23 @@ public T getInstance(long identifier) { * Returns whether this manager contains the given `instance`. * * @param instance the instance whose presence in this manager is to be tested. - * @return whether this manager contains the given `instance`. If the manager is closed, returns - * `false`. + * @return whether this manager contains the given `instance`. */ public boolean containsInstance(@Nullable Object instance) { - if (assertNotClosed()) { - return false; - } + logWarningIfFinalizationListenerHasStopped(); return identifiers.containsKey(instance); } /** - * Closes the manager and releases resources. + * Stop the periodic run of the {@link FinalizationListener} for instances that have been garbage + * collected. * - *

Methods called after this one will be ignored and log a warning. + *

The InstanceManager can continue to be used, but the {@link FinalizationListener} will no + * longer be called and methods will log a warning. */ - public void close() { + public void stopFinalizationListener() { handler.removeCallbacks(this::releaseAllFinalizedInstances); - isClosed = true; - clear(); + hasFinalizationListenerStopped = true; } /** @@ -226,15 +208,20 @@ public void clear() { } /** - * Whether the manager has released resources and is no longer usable. + * Whether the {@link FinalizationListener} is still being called for instances that are garbage + * collected. * - *

See {@link #close()}. + *

See {@link #stopFinalizationListener()}. */ - public boolean isClosed() { - return isClosed; + public boolean hasFinalizationListenerStopped() { + return hasFinalizationListenerStopped; } private void releaseAllFinalizedInstances() { + if (hasFinalizationListenerStopped()) { + return; + } + WeakReference reference; while ((reference = (WeakReference) referenceQueue.poll()) != null) { final Long identifier = weakReferencesToIdentifiers.remove(reference); @@ -263,11 +250,9 @@ private void addInstance(Object instance, long identifier) { strongInstances.put(identifier, instance); } - private boolean assertNotClosed() { - if (isClosed()) { - Log.w(TAG, CLOSED_WARNING); - return true; + private void logWarningIfFinalizationListenerHasStopped() { + if (hasFinalizationListenerStopped()) { + Log.w(TAG, "The manager was used after calls to the FinalizationListener have been stopped."); } - return false; } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index d1c239c3636..1434e57c080 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -78,7 +78,7 @@ private void setUp( Context context, FlutterAssetManager flutterAssetManager) { instanceManager = - InstanceManager.open( + InstanceManager.create( identifier -> new GeneratedAndroidWebView.JavaObjectFlutterApi(binaryMessenger) .dispose(identifier, reply -> {})); @@ -145,7 +145,7 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { @Override public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { if (instanceManager != null) { - instanceManager.close(); + instanceManager.stopFinalizationListener(); instanceManager = null; } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java index caffbb9a95e..ae0029648eb 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/DownloadListenerTest.java @@ -29,7 +29,7 @@ public class DownloadListenerTest { @Before public void setUp() { - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = InstanceManager.create(identifier -> {}); final DownloadListenerCreator downloadListenerCreator = new DownloadListenerCreator() { @@ -48,7 +48,7 @@ public DownloadListenerImpl createDownloadListener( @After public void tearDown() { - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java index 3172ea4330c..0fb21cc474c 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/FileChooserParamsTest.java @@ -35,12 +35,12 @@ public class FileChooserParamsTest { @Before public void setUp() { - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = InstanceManager.create(identifier -> {}); } @After public void tearDown() { - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java index 2bda2d12c75..2f2ea7769dd 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java @@ -15,7 +15,7 @@ public class InstanceManagerTest { @Test public void addDartCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); @@ -24,12 +24,12 @@ public void addDartCreatedInstance() { assertEquals((Long) 0L, instanceManager.getIdentifierForStrongReference(object)); assertTrue(instanceManager.containsInstance(object)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test public void addHostCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object object = new Object(); long identifier = instanceManager.addHostCreatedInstance(object); @@ -38,12 +38,12 @@ public void addHostCreatedInstance() { assertEquals(object, instanceManager.getInstance(identifier)); assertTrue(instanceManager.containsInstance(object)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test public void remove() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); Object object = new Object(); instanceManager.addDartCreatedInstance(object, 0); @@ -58,60 +58,12 @@ public void remove() { assertNull(instanceManager.getInstance(0)); - instanceManager.close(); - } - - @Test - public void removeReturnsNullWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertNull(instanceManager.remove(0)); - } - - @Test - public void getIdentifierForStrongReferenceReturnsNullWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertNull(instanceManager.getIdentifierForStrongReference(object)); - } - - @Test - public void addHostCreatedInstanceReturnsNegativeOneWhenClosed() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.close(); - - assertEquals(instanceManager.addHostCreatedInstance(new Object()), -1L); - } - - @Test - public void getInstanceReturnsNullWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertNull(instanceManager.getInstance(0)); - } - - @Test - public void containsInstanceReturnsFalseWhenClosed() { - final Object object = new Object(); - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); - instanceManager.addDartCreatedInstance(object, 0); - instanceManager.close(); - - assertFalse(instanceManager.containsInstance(object)); + instanceManager.stopFinalizationListener(); } @Test public void clear() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object instance = new Object(); @@ -121,12 +73,12 @@ public void clear() { instanceManager.clear(); assertFalse(instanceManager.containsInstance(instance)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test public void canAddSameObjectWithAddDartCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object instance = new Object(); @@ -138,37 +90,51 @@ public void canAddSameObjectWithAddDartCreatedInstance() { assertEquals(instanceManager.getInstance(0), instance); assertEquals(instanceManager.getInstance(1), instance); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test(expected = IllegalArgumentException.class) public void cannotAddSameObjectsWithAddHostCreatedInstance() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final Object instance = new Object(); instanceManager.addHostCreatedInstance(instance); instanceManager.addHostCreatedInstance(instance); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test(expected = IllegalArgumentException.class) public void cannotUseIdentifierLessThanZero() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); instanceManager.addDartCreatedInstance(new Object(), -1); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test(expected = IllegalArgumentException.class) public void identifiersMustBeUnique() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); instanceManager.addDartCreatedInstance(new Object(), 0); instanceManager.addDartCreatedInstance(new Object(), 0); - instanceManager.close(); + instanceManager.stopFinalizationListener(); + } + + @Test + public void managerIsUsableWhileListenerHasStopped() { + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); + instanceManager.stopFinalizationListener(); + + final Object instance = new Object(); + final long identifier = 0; + + instanceManager.addDartCreatedInstance(instance, identifier); + assertEquals(instanceManager.getInstance(identifier), instance); + assertEquals(instanceManager.getIdentifierForStrongReference(instance), (Long) identifier); + assertTrue(instanceManager.containsInstance(instance)); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java index 8ac349e7641..88c759d1082 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaObjectHostApiTest.java @@ -11,7 +11,7 @@ public class JavaObjectHostApiTest { @Test public void dispose() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final JavaObjectHostApiImpl hostApi = new JavaObjectHostApiImpl(instanceManager); @@ -27,6 +27,6 @@ public void dispose() { assertNull(instanceManager.getInstance(0)); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } } diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java index 391c6c833e5..0c9647a8cb9 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/JavaScriptChannelTest.java @@ -30,7 +30,7 @@ public class JavaScriptChannelTest { @Before public void setUp() { - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = InstanceManager.create(identifier -> {}); final JavaScriptChannelCreator javaScriptChannelCreator = new JavaScriptChannelCreator() { @@ -57,7 +57,7 @@ public JavaScriptChannel createJavaScriptChannel( @After public void tearDown() { - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java index f7a174091a9..ccd26f0cdc3 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebChromeClientTest.java @@ -46,7 +46,7 @@ public class WebChromeClientTest { @Before public void setUp() { - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = InstanceManager.create(identifier -> {}); final WebChromeClientCreator webChromeClientCreator = new WebChromeClientCreator() { @@ -66,7 +66,7 @@ public WebChromeClientImpl createWebChromeClient( @After public void tearDown() { - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java index 5c7c7a6f470..4a299766988 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebSettingsTest.java @@ -32,7 +32,7 @@ public class WebSettingsTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); when(mockWebSettingsCreator.createWebSettings(any())).thenReturn(mockWebSettings); testHostApiImpl = new WebSettingsHostApiImpl(testInstanceManager, mockWebSettingsCreator); @@ -43,7 +43,7 @@ public void setUp() { @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java index b4f38f1702d..5756ce7a174 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImplTest.java @@ -28,7 +28,7 @@ public class WebStorageHostApiImplTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); when(mockWebStorageCreator.createWebStorage()).thenReturn(mockWebStorage); testHostApiImpl = new WebStorageHostApiImpl(testInstanceManager, mockWebStorageCreator); @@ -37,7 +37,7 @@ public void setUp() { @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java index 230e81441a3..8e6b58149d0 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewClientTest.java @@ -42,7 +42,7 @@ public class WebViewClientTest { @Before public void setUp() { - instanceManager = InstanceManager.open(identifier -> {}); + instanceManager = InstanceManager.create(identifier -> {}); final WebViewClientCreator webViewClientCreator = new WebViewClientCreator() { @@ -62,7 +62,7 @@ public WebViewClient createWebViewClient( @After public void tearDown() { - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java index e0e641ac35a..00887275577 100644 --- a/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java +++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/WebViewTest.java @@ -54,7 +54,7 @@ public class WebViewTest { @Before public void setUp() { - testInstanceManager = InstanceManager.open(identifier -> {}); + testInstanceManager = InstanceManager.create(identifier -> {}); when(mockWebViewProxy.createWebView(mockContext, mockBinaryMessenger, testInstanceManager)) .thenReturn(mockWebView); @@ -66,7 +66,7 @@ public void setUp() { @After public void tearDown() { - testInstanceManager.close(); + testInstanceManager.stopFinalizationListener(); } @Test @@ -325,7 +325,7 @@ public void destroy() { @Test public void flutterApiCreate() { - final InstanceManager instanceManager = InstanceManager.open(identifier -> {}); + final InstanceManager instanceManager = InstanceManager.create(identifier -> {}); final WebViewFlutterApiImpl flutterApiImpl = new WebViewFlutterApiImpl(mockBinaryMessenger, instanceManager); @@ -339,7 +339,7 @@ public void flutterApiCreate() { Objects.requireNonNull(instanceManager.getIdentifierForStrongReference(mockWebView)); verify(mockFlutterApi).create(eq(instanceIdentifier), any()); - instanceManager.close(); + instanceManager.stopFinalizationListener(); } @Test diff --git a/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/InstanceManagerTest.java b/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/InstanceManagerTest.java new file mode 100644 index 00000000000..54cff982d4b --- /dev/null +++ b/packages/webview_flutter/webview_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/webviewflutterexample/InstanceManagerTest.java @@ -0,0 +1,43 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.webviewflutterexample; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.flutter.plugins.webviewflutter.InstanceManager; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class InstanceManagerTest { + @Test + public void managerDoesNotTriggerFinalizationListenerWhenStopped() throws InterruptedException { + final boolean[] callbackTriggered = {false}; + final InstanceManager instanceManager = + InstanceManager.create(identifier -> callbackTriggered[0] = true); + instanceManager.stopFinalizationListener(); + + Object object = new Object(); + instanceManager.addDartCreatedInstance(object, 0); + + assertEquals(object, instanceManager.remove(0)); + + // To allow for object to be garbage collected. + //noinspection UnusedAssignment + object = null; + + Runtime.getRuntime().gc(); + + // Wait for the interval after finalized callbacks are made for garbage collected objects. + // See InstanceManager.CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL. + Thread.sleep(30000); + + assertNull(instanceManager.getInstance(0)); + assertFalse(callbackTriggered[0]); + } +} diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml index 7fca5618dd2..bd22b7673ee 100644 --- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml +++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml @@ -2,7 +2,7 @@ name: webview_flutter_android description: A Flutter plugin that provides a WebView widget on Android. repository: https://github.com/flutter/packages/tree/main/packages/webview_flutter/webview_flutter_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22 -version: 3.5.1 +version: 3.5.2 environment: sdk: ">=2.18.0 <4.0.0"