[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"