[webview_flutter_android] Updates the Java InstanceManager to take a listener for when an object is garbage collected (#6082)

diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index ceaef43..5706d65 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.9.2
+
+* Updates the Java InstanceManager to take a listener for when an object is garbage collected.
+  See https://github.com/flutter/flutter/issues/107199.
+
 ## 2.9.1
 
 * Updates Android WebView classes as Copyable. This is a part of moving the api to handle garbage
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java
index 2dd98c4..1981d8e 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerFlutterApiImpl.java
@@ -38,7 +38,7 @@
       long contentLength,
       Reply<Void> callback) {
     onDownloadStart(
-        instanceManager.getInstanceId(downloadListener),
+        getIdentifierForListener(downloadListener),
         url,
         userAgent,
         contentDisposition,
@@ -54,11 +54,18 @@
    * @param callback reply callback with return value from Dart
    */
   public void dispose(DownloadListener downloadListener, Reply<Void> callback) {
-    final Long instanceId = instanceManager.removeInstance(downloadListener);
-    if (instanceId != null) {
-      dispose(instanceId, callback);
+    if (instanceManager.containsInstance(downloadListener)) {
+      dispose(getIdentifierForListener(downloadListener), callback);
     } else {
       callback.reply(null);
     }
   }
+
+  private long getIdentifierForListener(DownloadListener listener) {
+    final Long identifier = instanceManager.getIdentifierForStrongReference(listener);
+    if (identifier == null) {
+      throw new IllegalStateException("Could not find identifier for DownloadListener.");
+    }
+    return identifier;
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java
index 9694f39..ed0c2ae 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java
@@ -91,6 +91,6 @@
   public void create(Long instanceId) {
     final DownloadListener downloadListener =
         downloadListenerCreator.createDownloadListener(flutterApi);
-    instanceManager.addInstance(downloadListener, instanceId);
+    instanceManager.addDartCreatedInstance(downloadListener, instanceId);
   }
 }
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 a368baf..306dc20 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
@@ -4,81 +4,202 @@
 
 package io.flutter.plugins.webviewflutter;
 
-import android.util.LongSparseArray;
+import android.os.Handler;
+import android.os.Looper;
+import androidx.annotation.Nullable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
 import java.util.HashMap;
-import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
- * Maintains instances to intercommunicate with Dart objects.
+ * Maintains instances used to communicate with the corresponding objects in Dart.
  *
- * <p>When an instance is added with an instanceId, either can be used to retrieve the other.
+ * <p>When an instance is added with an identifier, either can be used to retrieve the other.
+ *
+ * <p>Added instances are added as a weak reference and a strong reference. When the strong
+ * reference is removed with `{@link #remove(long)}` and the weak reference is deallocated, the
+ * `finalizationListener` is made with the instance's identifier. However, if the strong reference
+ * is removed and then the identifier is retrieved with the intention to pass the identifier to Dart
+ * (e.g. calling {@link #getIdentifierForStrongReference(Object)}), the strong reference to the
+ * instance is recreated. The strong reference will then need to be removed manually again.
  */
+@SuppressWarnings("unchecked")
 public class InstanceManager {
-  private final LongSparseArray<Object> instanceIdsToInstances = new LongSparseArray<>();
-  private final Map<Object, Long> instancesToInstanceIds = new HashMap<>();
+  // 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;
+
+  /** Interface for listening when a weak reference of an instance is removed from the manager. */
+  public interface FinalizationListener {
+    void onFinalize(long identifier);
+  }
+
+  private final WeakHashMap<Object, Long> identifiers = new WeakHashMap<>();
+  private final HashMap<Long, WeakReference<Object>> weakInstances = new HashMap<>();
+  private final HashMap<Long, Object> strongInstances = new HashMap<>();
+
+  private final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
+  private final HashMap<WeakReference<Object>, Long> weakReferencesToIdentifiers = new HashMap<>();
+
+  private final Handler handler = new Handler(Looper.getMainLooper());
+
+  private final FinalizationListener finalizationListener;
+
+  private long nextIdentifier = MIN_HOST_CREATED_IDENTIFIER;
+  private boolean isClosed = false;
 
   /**
-   * Add a new instance to the manager.
+   * Instantiate a new manager.
    *
-   * <p>If an instance or instanceId has already been added, it will be replaced by the new values.
+   * <p>When the manager is no longer needed, {@link #close()} must be called.
    *
-   * @param instance the new object to be added
-   * @param instanceId unique id of the added object
+   * @param finalizationListener the listener for garbage collected weak references.
+   * @return a new `InstanceManager`.
    */
-  public void addInstance(Object instance, long instanceId) {
-    instancesToInstanceIds.put(instance, instanceId);
-    instanceIdsToInstances.append(instanceId, instance);
+  public static InstanceManager open(FinalizationListener finalizationListener) {
+    return new InstanceManager(finalizationListener);
+  }
+
+  private InstanceManager(FinalizationListener finalizationListener) {
+    this.finalizationListener = finalizationListener;
+    handler.postDelayed(
+        this::releaseAllFinalizedInstances, CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL);
   }
 
   /**
-   * Remove the instance with instanceId from the manager.
+   * Removes `identifier` and its associated strongly referenced instance, if present, from the
+   * manager.
    *
-   * @param instanceId the id of the instance to be removed
-   * @return the removed instance if the manager contains the instanceId, otherwise null
+   * @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.
    */
-  public Object removeInstanceWithId(long instanceId) {
-    final Object instance = instanceIdsToInstances.get(instanceId);
+  @Nullable
+  public <T> T remove(long identifier) {
+    assertManagerIsNotClosed();
+    return (T) strongInstances.remove(identifier);
+  }
+
+  /**
+   * Retrieves the identifier paired with an instance.
+   *
+   * <p>If the manager contains `instance`, as a strong or weak reference, the strong reference to
+   * `instance` will be recreated and will need to be removed again with {@link #remove(long)}.
+   *
+   * @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.
+   */
+  @Nullable
+  public Long getIdentifierForStrongReference(Object instance) {
+    assertManagerIsNotClosed();
+    final Long identifier = identifiers.get(instance);
+    if (identifier != null) {
+      strongInstances.put(identifier, instance);
+    }
+    return identifier;
+  }
+
+  /**
+   * Adds a new instance that was instantiated from Dart.
+   *
+   * <p>If an instance or identifier has already been added, it will be replaced by the new values.
+   * The Dart InstanceManager is considered the source of truth and has the capability to overwrite
+   * stored pairs in response to hot restarts.
+   *
+   * @param instance the instance to be stored.
+   * @param identifier the identifier to be paired with instance. This value must be >= 0.
+   */
+  public void addDartCreatedInstance(Object instance, long identifier) {
+    assertManagerIsNotClosed();
+    addInstance(instance, identifier);
+  }
+
+  /**
+   * Adds a new instance that was instantiated from the host platform.
+   *
+   * @param instance the instance to be stored.
+   * @return the unique identifier stored with instance.
+   */
+  public long addHostCreatedInstance(Object instance) {
+    assertManagerIsNotClosed();
+    final long identifier = nextIdentifier++;
+    addInstance(instance, identifier);
+    return identifier;
+  }
+
+  /**
+   * Retrieves the instance associated with identifier.
+   *
+   * @param identifier the identifier paired to an instance.
+   * @param <T> the expected return type.
+   * @return the instance associated with `identifier` if the manager contains the value, otherwise
+   *     null.
+   */
+  @Nullable
+  public <T> T getInstance(long identifier) {
+    assertManagerIsNotClosed();
+    final WeakReference<T> instance = (WeakReference<T>) weakInstances.get(identifier);
     if (instance != null) {
-      instanceIdsToInstances.remove(instanceId);
-      instancesToInstanceIds.remove(instance);
+      return instance.get();
     }
-    return instance;
+    return (T) strongInstances.get(identifier);
   }
 
   /**
-   * Remove the instance from the manager.
+   * Returns whether this manager contains the given `instance`.
    *
-   * @param instance the instance to be removed
-   * @return the instanceId of the removed instance if the manager contains the value, otherwise
-   *     null
+   * @param instance the instance whose presence in this manager is to be tested.
+   * @return whether this manager contains the given `instance`.
    */
-  public Long removeInstance(Object instance) {
-    final Long instanceId = instancesToInstanceIds.get(instance);
-    if (instanceId != null) {
-      instanceIdsToInstances.remove(instanceId);
-      instancesToInstanceIds.remove(instance);
+  public boolean containsInstance(Object instance) {
+    assertManagerIsNotClosed();
+    return identifiers.containsKey(instance);
+  }
+
+  /**
+   * Closes the manager and releases resources.
+   *
+   * <p>Calling a method after calling this one will throw an {@link AssertionError}. This method
+   * excluded.
+   */
+  public void close() {
+    handler.removeCallbacks(this::releaseAllFinalizedInstances);
+    isClosed = true;
+  }
+
+  private void releaseAllFinalizedInstances() {
+    WeakReference<Object> reference;
+    while ((reference = (WeakReference<Object>) referenceQueue.poll()) != null) {
+      final Long identifier = weakReferencesToIdentifiers.remove(reference);
+      if (identifier != null) {
+        weakInstances.remove(identifier);
+        strongInstances.remove(identifier);
+        finalizationListener.onFinalize(identifier);
+      }
     }
-    return instanceId;
+    handler.postDelayed(
+        this::releaseAllFinalizedInstances, CLEAR_FINALIZED_WEAK_REFERENCES_INTERVAL);
   }
 
-  /**
-   * Retrieve the Object paired with instanceId.
-   *
-   * @param instanceId the instanceId of the desired instance
-   * @return the instance stored with the instanceId if the manager contains the value, otherwise
-   *     null
-   */
-  public Object getInstance(long instanceId) {
-    return instanceIdsToInstances.get(instanceId);
+  private void addInstance(Object instance, long identifier) {
+    if (identifier < 0) {
+      throw new IllegalArgumentException("Identifier must be >= 0.");
+    }
+    final WeakReference<Object> weakReference = new WeakReference<>(instance, referenceQueue);
+    identifiers.put(instance, identifier);
+    weakInstances.put(identifier, weakReference);
+    weakReferencesToIdentifiers.put(weakReference, identifier);
+    strongInstances.put(identifier, instance);
   }
 
-  /**
-   * Retrieve the instanceId paired with an instance.
-   *
-   * @param instance the value paired with the desired instanceId
-   * @return the instanceId paired with instance if the manager contains the value, otherwise null
-   */
-  public Long getInstanceId(Object instance) {
-    return instancesToInstanceIds.get(instance);
+  private void assertManagerIsNotClosed() {
+    if (isClosed) {
+      throw new AssertionError("Manager has already been closed.");
+    }
   }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java
index 120f66d..dbac833 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java
@@ -30,7 +30,7 @@
   /** Passes arguments from {@link JavaScriptChannel#postMessage} to Dart. */
   public void postMessage(
       JavaScriptChannel javaScriptChannel, String messageArg, Reply<Void> callback) {
-    super.postMessage(instanceManager.getInstanceId(javaScriptChannel), messageArg, callback);
+    super.postMessage(getIdentifierForJavaScriptChannel(javaScriptChannel), messageArg, callback);
   }
 
   /**
@@ -40,11 +40,18 @@
    * @param callback Reply callback with return value from Dart.
    */
   public void dispose(JavaScriptChannel javaScriptChannel, Reply<Void> callback) {
-    final Long instanceId = instanceManager.removeInstance(javaScriptChannel);
-    if (instanceId != null) {
-      dispose(instanceId, callback);
+    if (instanceManager.containsInstance(javaScriptChannel)) {
+      dispose(getIdentifierForJavaScriptChannel(javaScriptChannel), callback);
     } else {
       callback.reply(null);
     }
   }
+
+  private long getIdentifierForJavaScriptChannel(JavaScriptChannel javaScriptChannel) {
+    final Long identifier = instanceManager.getIdentifierForStrongReference(javaScriptChannel);
+    if (identifier == null) {
+      throw new IllegalStateException("Could not find identifier for JavaScriptChannel.");
+    }
+    return identifier;
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java
index 3055c9f..44e3b8a 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelHostApiImpl.java
@@ -70,6 +70,6 @@
     final JavaScriptChannel javaScriptChannel =
         javaScriptChannelCreator.createJavaScriptChannel(
             flutterApi, channelName, platformThreadHandler);
-    instanceManager.addInstance(javaScriptChannel, instanceId);
+    instanceManager.addDartCreatedInstance(javaScriptChannel, instanceId);
   }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java
index 2ab9275..28d63ec 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientFlutterApiImpl.java
@@ -32,11 +32,12 @@
   /** Passes arguments from {@link WebChromeClient#onProgressChanged} to Dart. */
   public void onProgressChanged(
       WebChromeClient webChromeClient, WebView webView, Long progress, Reply<Void> callback) {
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
     super.onProgressChanged(
-        instanceManager.getInstanceId(webChromeClient),
-        instanceManager.getInstanceId(webView),
-        progress,
-        callback);
+        getIdentifierForClient(webChromeClient), webViewIdentifier, progress, callback);
   }
 
   /**
@@ -46,11 +47,18 @@
    * @param callback reply callback with return value from Dart
    */
   public void dispose(WebChromeClient webChromeClient, Reply<Void> callback) {
-    final Long instanceId = instanceManager.removeInstance(webChromeClient);
-    if (instanceId != null) {
-      dispose(instanceId, callback);
+    if (instanceManager.containsInstance(webChromeClient)) {
+      dispose(getIdentifierForClient(webChromeClient), callback);
     } else {
       callback.reply(null);
     }
   }
+
+  private long getIdentifierForClient(WebChromeClient webChromeClient) {
+    final Long identifier = instanceManager.getIdentifierForStrongReference(webChromeClient);
+    if (identifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebChromeClient.");
+    }
+    return identifier;
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java
index d2e1e59..0f50c82 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebChromeClientHostApiImpl.java
@@ -163,6 +163,6 @@
         (WebViewClient) instanceManager.getInstance(webViewClientInstanceId);
     final WebChromeClient webChromeClient =
         webChromeClientCreator.createWebChromeClient(flutterApi, webViewClient);
-    instanceManager.addInstance(webChromeClient, instanceId);
+    instanceManager.addDartCreatedInstance(webChromeClient, instanceId);
   }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
index b168e20..5b6f9e7 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
@@ -45,12 +45,13 @@
   @Override
   public void create(Long instanceId, Long webViewInstanceId) {
     final WebView webView = (WebView) instanceManager.getInstance(webViewInstanceId);
-    instanceManager.addInstance(webSettingsCreator.createWebSettings(webView), instanceId);
+    instanceManager.addDartCreatedInstance(
+        webSettingsCreator.createWebSettings(webView), instanceId);
   }
 
   @Override
   public void dispose(Long instanceId) {
-    instanceManager.removeInstanceWithId(instanceId);
+    instanceManager.remove(instanceId);
   }
 
   @Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java
index 42e7603..c06f2bc 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebStorageHostApiImpl.java
@@ -42,7 +42,7 @@
 
   @Override
   public void create(Long instanceId) {
-    instanceManager.addInstance(webStorageCreator.createWebStorage(), instanceId);
+    instanceManager.addDartCreatedInstance(webStorageCreator.createWebStorage(), instanceId);
   }
 
   @Override
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java
index b488568..c23e8e7 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientFlutterApiImpl.java
@@ -77,21 +77,21 @@
   /** Passes arguments from {@link WebViewClient#onPageStarted} to Dart. */
   public void onPageStarted(
       WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
-    onPageStarted(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
-        urlArg,
-        callback);
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
+    onPageStarted(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
   }
 
   /** Passes arguments from {@link WebViewClient#onPageFinished} to Dart. */
   public void onPageFinished(
       WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
-    onPageFinished(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
-        urlArg,
-        callback);
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
+    onPageFinished(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
   }
 
   /**
@@ -105,9 +105,13 @@
       WebResourceRequest request,
       WebResourceError error,
       Reply<Void> callback) {
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
     onReceivedRequestError(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
+        getIdentifierForClient(webViewClient),
+        webViewIdentifier,
         createWebResourceRequestData(request),
         createWebResourceErrorData(error),
         callback);
@@ -124,9 +128,13 @@
       WebResourceRequest request,
       WebResourceErrorCompat error,
       Reply<Void> callback) {
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
     onReceivedRequestError(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
+        getIdentifierForClient(webViewClient),
+        webViewIdentifier,
         createWebResourceRequestData(request),
         createWebResourceErrorData(error),
         callback);
@@ -143,9 +151,13 @@
       String descriptionArg,
       String failingUrlArg,
       Reply<Void> callback) {
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
     onReceivedError(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
+        getIdentifierForClient(webViewClient),
+        webViewIdentifier,
         errorCodeArg,
         descriptionArg,
         failingUrlArg,
@@ -162,9 +174,13 @@
       WebView webView,
       WebResourceRequest request,
       Reply<Void> callback) {
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
     requestLoading(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
+        getIdentifierForClient(webViewClient),
+        webViewIdentifier,
         createWebResourceRequestData(request),
         callback);
   }
@@ -174,11 +190,11 @@
    */
   public void urlLoading(
       WebViewClient webViewClient, WebView webView, String urlArg, Reply<Void> callback) {
-    urlLoading(
-        instanceManager.getInstanceId(webViewClient),
-        instanceManager.getInstanceId(webView),
-        urlArg,
-        callback);
+    final Long webViewIdentifier = instanceManager.getIdentifierForStrongReference(webView);
+    if (webViewIdentifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebView.");
+    }
+    urlLoading(getIdentifierForClient(webViewClient), webViewIdentifier, urlArg, callback);
   }
 
   /**
@@ -188,11 +204,18 @@
    * @param callback reply callback with return value from Dart
    */
   public void dispose(WebViewClient webViewClient, Reply<Void> callback) {
-    final Long instanceId = instanceManager.removeInstance(webViewClient);
-    if (instanceId != null) {
-      dispose(instanceId, callback);
+    if (instanceManager.containsInstance(webViewClient)) {
+      dispose(getIdentifierForClient(webViewClient), callback);
     } else {
       callback.reply(null);
     }
   }
+
+  private long getIdentifierForClient(WebViewClient webViewClient) {
+    final Long identifier = instanceManager.getIdentifierForStrongReference(webViewClient);
+    if (identifier == null) {
+      throw new IllegalStateException("Could not find identifier for WebViewClient.");
+    }
+    return identifier;
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java
index 6b659fa..4833ee9 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java
@@ -244,6 +244,6 @@
   public void create(Long instanceId, Boolean shouldOverrideUrlLoading) {
     final WebViewClient webViewClient =
         webViewClientCreator.createWebViewClient(flutterApi, shouldOverrideUrlLoading);
-    instanceManager.addInstance(webViewClient, instanceId);
+    instanceManager.addDartCreatedInstance(webViewClient, instanceId);
   }
 }
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 67202eb..8db976a 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
@@ -31,6 +31,8 @@
  * <p>Call {@link #registerWith} to use the stable {@code io.flutter.plugin.common} package instead.
  */
 public class WebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
+  private InstanceManager instanceManager;
+
   private FlutterPluginBinding pluginBinding;
   private WebViewHostApiImpl webViewHostApi;
   private JavaScriptChannelHostApiImpl javaScriptChannelHostApi;
@@ -75,7 +77,7 @@
       View containerView,
       FlutterAssetManager flutterAssetManager) {
 
-    InstanceManager instanceManager = new InstanceManager();
+    instanceManager = InstanceManager.open(identifier -> {});
 
     viewRegistry.registerViewFactory(
         "plugins.flutter.io/webview", new FlutterWebViewFactory(instanceManager));
@@ -135,7 +137,9 @@
   }
 
   @Override
-  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
+  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
+    instanceManager.close();
+  }
 
   @Override
   public void onAttachedToActivity(@NonNull ActivityPluginBinding activityPluginBinding) {
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java
index afc3efe..56761d1 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewHostApiImpl.java
@@ -335,7 +335,7 @@
             : webViewProxy.createInputAwareWebView(context, containerView);
 
     displayListenerProxy.onPostWebViewInitialization(displayManager);
-    instanceManager.addInstance(webView, instanceId);
+    instanceManager.addDartCreatedInstance(webView, instanceId);
   }
 
   @Override
@@ -343,7 +343,7 @@
     final WebView instance = (WebView) instanceManager.getInstance(instanceId);
     if (instance != null) {
       ((Releasable) instance).release();
-      instanceManager.removeInstance(instance);
+      instanceManager.remove(instanceId);
     }
   }
 
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 2391193..da25dac 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
@@ -13,6 +13,7 @@
 import android.webkit.DownloadListener;
 import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerCreator;
 import io.flutter.plugins.webviewflutter.DownloadListenerHostApiImpl.DownloadListenerImpl;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -31,7 +32,7 @@
 
   @Before
   public void setUp() {
-    instanceManager = new InstanceManager();
+    instanceManager = InstanceManager.open(identifier -> {});
 
     final DownloadListenerCreator downloadListenerCreator =
         new DownloadListenerCreator() {
@@ -48,6 +49,11 @@
     hostApiImpl.create(0L);
   }
 
+  @After
+  public void tearDown() {
+    instanceManager.close();
+  }
+
   @Test
   public void postMessage() {
     downloadListener.onDownloadStart(
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
new file mode 100644
index 0000000..4731e2a
--- /dev/null
+++ b/packages/webview_flutter/webview_flutter_android/android/src/test/java/io/flutter/plugins/webviewflutter/InstanceManagerTest.java
@@ -0,0 +1,62 @@
+// 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.webviewflutter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class InstanceManagerTest {
+  @Test
+  public void addDartCreatedInstance() {
+    final InstanceManager instanceManager = InstanceManager.open(identifier -> {});
+
+    final Object object = new Object();
+    instanceManager.addDartCreatedInstance(object, 0);
+
+    assertEquals(object, instanceManager.getInstance(0));
+    assertEquals((Long) 0L, instanceManager.getIdentifierForStrongReference(object));
+    assertTrue(instanceManager.containsInstance(object));
+
+    instanceManager.close();
+  }
+
+  @Test
+  public void addHostCreatedInstance() {
+    final InstanceManager instanceManager = InstanceManager.open(identifier -> {});
+
+    final Object object = new Object();
+    long identifier = instanceManager.addHostCreatedInstance(object);
+
+    assertNotNull(instanceManager.getInstance(identifier));
+    assertEquals(object, instanceManager.getInstance(identifier));
+    assertTrue(instanceManager.containsInstance(object));
+
+    instanceManager.close();
+  }
+
+  @Test
+  public void remove() {
+    final InstanceManager instanceManager = InstanceManager.open(identifier -> {});
+
+    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();
+
+    assertNull(instanceManager.getInstance(0));
+
+    instanceManager.close();
+  }
+}
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 3de81da..4bde211 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
@@ -12,6 +12,7 @@
 
 import android.os.Handler;
 import io.flutter.plugins.webviewflutter.JavaScriptChannelHostApiImpl.JavaScriptChannelCreator;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -30,7 +31,7 @@
 
   @Before
   public void setUp() {
-    instanceManager = new InstanceManager();
+    instanceManager = InstanceManager.open(identifier -> {});
 
     final JavaScriptChannelCreator javaScriptChannelCreator =
         new JavaScriptChannelCreator() {
@@ -52,6 +53,11 @@
     hostApiImpl.create(0L, "aChannelName");
   }
 
+  @After
+  public void tearDown() {
+    instanceManager.close();
+  }
+
   @Test
   public void postMessage() {
     javaScriptChannel.postMessage("A message post.");
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 63cd310..03d48d1 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
@@ -23,6 +23,7 @@
 import android.webkit.WebViewClient;
 import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientCreator;
 import io.flutter.plugins.webviewflutter.WebChromeClientHostApiImpl.WebChromeClientImpl;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -46,9 +47,10 @@
 
   @Before
   public void setUp() {
-    instanceManager = new InstanceManager();
-    instanceManager.addInstance(mockWebView, 0L);
-    instanceManager.addInstance(mockWebViewClient, 1L);
+    instanceManager = InstanceManager.open(identifier -> {});
+
+    instanceManager.addDartCreatedInstance(mockWebView, 0L);
+    instanceManager.addDartCreatedInstance(mockWebViewClient, 1L);
 
     final WebChromeClientCreator webChromeClientCreator =
         new WebChromeClientCreator() {
@@ -65,6 +67,11 @@
     hostApiImpl.create(2L, 1L);
   }
 
+  @After
+  public void tearDown() {
+    instanceManager.close();
+  }
+
   @Test
   public void onProgressChanged() {
     webChromeClient.onProgressChanged(mockWebView, 23);
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 8ef32dd..3217316 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
@@ -10,6 +10,7 @@
 
 import android.webkit.WebSettings;
 import io.flutter.plugins.webviewflutter.WebSettingsHostApiImpl.WebSettingsCreator;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -29,12 +30,18 @@
 
   @Before
   public void setUp() {
-    testInstanceManager = new InstanceManager();
+    testInstanceManager = InstanceManager.open(identifier -> {});
+
     when(mockWebSettingsCreator.createWebSettings(any())).thenReturn(mockWebSettings);
     testHostApiImpl = new WebSettingsHostApiImpl(testInstanceManager, mockWebSettingsCreator);
     testHostApiImpl.create(0L, 0L);
   }
 
+  @After
+  public void tearDown() {
+    testInstanceManager.close();
+  }
+
   @Test
   public void setDomStorageEnabled() {
     testHostApiImpl.setDomStorageEnabled(0L, true);
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 e2845c2..b4f38f1 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
@@ -8,6 +8,7 @@
 import static org.mockito.Mockito.when;
 
 import android.webkit.WebStorage;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -27,12 +28,18 @@
 
   @Before
   public void setUp() {
-    testInstanceManager = new InstanceManager();
+    testInstanceManager = InstanceManager.open(identifier -> {});
+
     when(mockWebStorageCreator.createWebStorage()).thenReturn(mockWebStorage);
     testHostApiImpl = new WebStorageHostApiImpl(testInstanceManager, mockWebStorageCreator);
     testHostApiImpl.create(0L);
   }
 
+  @After
+  public void tearDown() {
+    testInstanceManager.close();
+  }
+
   @Test
   public void deleteAllData() {
     testHostApiImpl.deleteAllData(0L);
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 c2abd25..5d0cb70 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
@@ -20,6 +20,7 @@
 import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCompatImpl;
 import io.flutter.plugins.webviewflutter.WebViewClientHostApiImpl.WebViewClientCreator;
 import java.util.HashMap;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -40,8 +41,9 @@
 
   @Before
   public void setUp() {
-    instanceManager = new InstanceManager();
-    instanceManager.addInstance(mockWebView, 0L);
+    instanceManager = InstanceManager.open(identifier -> {});
+
+    instanceManager.addDartCreatedInstance(mockWebView, 0L);
 
     final WebViewClientCreator webViewClientCreator =
         new WebViewClientCreator() {
@@ -60,6 +62,11 @@
     hostApiImpl.create(1L, true);
   }
 
+  @After
+  public void tearDown() {
+    instanceManager.close();
+  }
+
   @Test
   public void onPageStarted() {
     webViewClient.onPageStarted(mockWebView, "https://www.google.com", null);
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 5be39ab..89bbd7c 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
@@ -21,6 +21,7 @@
 import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.InputAwareWebViewPlatformView;
 import io.flutter.plugins.webviewflutter.WebViewHostApiImpl.WebViewPlatformView;
 import java.util.HashMap;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -43,13 +44,19 @@
 
   @Before
   public void setUp() {
-    testInstanceManager = new InstanceManager();
+    testInstanceManager = InstanceManager.open(identifier -> {});
+
     when(mockWebViewProxy.createWebView(mockContext)).thenReturn(mockWebView);
     testHostApiImpl =
         new WebViewHostApiImpl(testInstanceManager, mockWebViewProxy, mockContext, null);
     testHostApiImpl.create(0L, true);
   }
 
+  @After
+  public void tearDown() {
+    testInstanceManager.close();
+  }
+
   @Test
   public void releaseWebView() {
     final WebViewPlatformView webView = new WebViewPlatformView(mockContext);
@@ -308,7 +315,7 @@
   @Test
   public void setWebViewClient() {
     final WebViewClient mockWebViewClient = mock(WebViewClient.class);
-    testInstanceManager.addInstance(mockWebViewClient, 1L);
+    testInstanceManager.addDartCreatedInstance(mockWebViewClient, 1L);
 
     testHostApiImpl.setWebViewClient(0L, 1L);
     verify(mockWebView).setWebViewClient(mockWebViewClient);
@@ -318,7 +325,7 @@
   public void addJavaScriptChannel() {
     final JavaScriptChannel javaScriptChannel =
         new JavaScriptChannel(mock(JavaScriptChannelFlutterApiImpl.class), "aName", null);
-    testInstanceManager.addInstance(javaScriptChannel, 1L);
+    testInstanceManager.addDartCreatedInstance(javaScriptChannel, 1L);
 
     testHostApiImpl.addJavaScriptChannel(0L, 1L);
     verify(mockWebView).addJavascriptInterface(javaScriptChannel, "aName");
@@ -328,7 +335,7 @@
   public void removeJavaScriptChannel() {
     final JavaScriptChannel javaScriptChannel =
         new JavaScriptChannel(mock(JavaScriptChannelFlutterApiImpl.class), "aName", null);
-    testInstanceManager.addInstance(javaScriptChannel, 1L);
+    testInstanceManager.addDartCreatedInstance(javaScriptChannel, 1L);
 
     testHostApiImpl.removeJavaScriptChannel(0L, 1L);
     verify(mockWebView).removeJavascriptInterface("aName");
@@ -337,7 +344,7 @@
   @Test
   public void setDownloadListener() {
     final DownloadListener mockDownloadListener = mock(DownloadListener.class);
-    testInstanceManager.addInstance(mockDownloadListener, 1L);
+    testInstanceManager.addDartCreatedInstance(mockDownloadListener, 1L);
 
     testHostApiImpl.setDownloadListener(0L, 1L);
     verify(mockWebView).setDownloadListener(mockDownloadListener);
@@ -346,7 +353,7 @@
   @Test
   public void setWebChromeClient() {
     final WebChromeClient mockWebChromeClient = mock(WebChromeClient.class);
-    testInstanceManager.addInstance(mockWebChromeClient, 1L);
+    testInstanceManager.addDartCreatedInstance(mockWebChromeClient, 1L);
 
     testHostApiImpl.setWebChromeClient(0L, 1L);
     verify(mockWebView).setWebChromeClient(mockWebChromeClient);
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index e530b1e..f765341 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android.
 repository: https://github.com/flutter/plugins/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: 2.9.1
+version: 2.9.2
 
 environment:
   sdk: ">=2.14.0 <3.0.0"