Skip to content
Merged
Next Next commit
update webview_flutter instance mangaer
  • Loading branch information
bparrishMines committed Mar 28, 2023
commit 1fe8a4e4e6728b37933bed983cff80d530763b1c
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,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 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 {
Expand All @@ -59,17 +55,17 @@ 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.
*
* <p>When the manager is no longer needed, {@link #close()} must be called.
* <p>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) {
public static InstanceManager create(FinalizationListener finalizationListener) {
return new InstanceManager(finalizationListener);
}

Expand All @@ -85,14 +81,12 @@ private InstanceManager(FinalizationListener finalizationListener) {
*
* @param identifier the identifier paired to an instance.
* @param <T> 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> T remove(long identifier) {
if (assertNotClosed()) {
return null;
}
logWarningIfFinalizationListenerHasStopped();
return (T) strongInstances.remove(identifier);
}

Expand All @@ -110,13 +104,12 @@ public <T> 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 (assertNotClosed()) {
return null;
}
logWarningIfFinalizationListenerHasStopped();

final Long identifier = identifiers.get(instance);
if (identifier != null) {
strongInstances.put(identifier, instance);
Expand All @@ -131,30 +124,23 @@ 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.
*
* <p>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 (assertNotClosed()) {
return;
}
logWarningIfFinalizationListenerHasStopped();
addInstance(instance, 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 stored with instance. Otherwise, returns a value >= 0.
*/
public long addHostCreatedInstance(Object instance) {
if (assertNotClosed()) {
return INSTANCE_CLOSED;
}
logWarningIfFinalizationListenerHasStopped();

if (containsInstance(instance)) {
throw new IllegalArgumentException(
Expand All @@ -171,13 +157,11 @@ public long addHostCreatedInstance(Object instance) {
* @param identifier the identifier associated with an instance.
* @param <T> 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> T getInstance(long identifier) {
if (assertNotClosed()) {
return null;
}
logWarningIfFinalizationListenerHasStopped();

final WeakReference<T> instance = (WeakReference<T>) weakInstances.get(identifier);
if (instance != null) {
Expand All @@ -190,25 +174,23 @@ public <T> 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 (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.
*
* <p>Methods called after this one will be ignored and log a warning.
* <p>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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to derive this value instead of using a boolean?
For example if handler.removeCallbacks does not have any more callbacks?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is a Handler.hasCallbacks, but it requires Android 29+ unfortunately.

}

/**
Expand All @@ -224,15 +206,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.
*
* <p>See {@link #close()}.
* <p>See {@link #stopFinalizationListener()}.
*/
public boolean isClosed() {
return isClosed;
public boolean hasFinalizationListenerStopped() {
return hasFinalizationListenerStopped;
}

private void releaseAllFinalizedInstances() {
if (hasFinalizationListenerStopped()) {
return;
}

WeakReference<Object> reference;
while ((reference = (WeakReference<Object>) referenceQueue.poll()) != null) {
final Long identifier = weakReferencesToIdentifiers.remove(reference);
Expand Down Expand Up @@ -261,11 +248,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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private void setUp(
View containerView,
FlutterAssetManager flutterAssetManager) {
instanceManager =
InstanceManager.open(
InstanceManager.create(
identifier ->
new GeneratedAndroidWebView.JavaObjectFlutterApi(binaryMessenger)
.dispose(identifier, reply -> {}));
Expand Down Expand Up @@ -152,7 +152,7 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
if (instanceManager != null) {
instanceManager.close();
instanceManager.stopFinalizationListener();
instanceManager = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class DownloadListenerTest {

@Before
public void setUp() {
instanceManager = InstanceManager.open(identifier -> {});
instanceManager = InstanceManager.create(identifier -> {});

final DownloadListenerCreator downloadListenerCreator =
new DownloadListenerCreator() {
Expand All @@ -48,7 +48,7 @@ public DownloadListenerImpl createDownloadListener(

@After
public void tearDown() {
instanceManager.close();
instanceManager.stopFinalizationListener();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading