[webview_flutter_android] Fix bugs in Java Implementation of the Pigeon API#4459
Conversation
...oid/android/src/main/java/io/flutter/plugins/webviewflutter/DownloadListenerHostApiImpl.java
Outdated
Show resolved
Hide resolved
| static class DownloadListenerImpl implements DownloadListener, Releasable { | ||
| private final Long instanceId; | ||
| private final DownloadListenerFlutterApiImpl flutterApi; | ||
| private boolean ignoreCallbacks = false; |
There was a problem hiding this comment.
I think this would benefit from a comment explaining what it's for, so people don't have to go read the implementation to understand it.
...flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java
Outdated
Show resolved
Hide resolved
| return instance; | ||
| } | ||
|
|
||
| /** Remove the instance from the manager. */ |
...flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java
Outdated
Show resolved
Hide resolved
| this.flutterApi = null; | ||
| } | ||
|
|
||
| /** |
...android/src/main/java/io/flutter/plugins/webviewflutter/JavaScriptChannelFlutterApiImpl.java
Outdated
Show resolved
Hide resolved
...view_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/Releasable.java
Show resolved
Hide resolved
...ndroid/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewClientHostApiImpl.java
Outdated
Show resolved
Hide resolved
| if (currentWebViewClient instanceof Releasable) { | ||
| ((Releasable) currentWebViewClient).release(); | ||
| } | ||
| currentWebViewClient = (WebViewClient) webViewClient; |
There was a problem hiding this comment.
Does Java have the ability to do subclass-restricted templates? You have this repeated pattern; it would be nice if you could instead have a simple smart pointer class that can only be created for things that inherit from Releasable (but is still typed with the correct specific type), that would handle the call-release-if-necessary-then-assign logic internally.
There was a problem hiding this comment.
I wasn't sure what you meant by this. But, my assumption is that you were thinking something like this
private static class ReleasableChild<T> {
@Nullable private T child;
private ReleasableChild(@Nullable T child) {
this.child = child;
}
private void release() {
if (child instanceof Releasable) {
((Releasable) child).release();
}
child = null;
}
}I replaced all the variables with this value.
There was a problem hiding this comment.
(It looks like the Java name for what I was looking for support for is "bounded types".)
This still relies on release being called manually on every assignment, which is error-prone. I was looking for something like (probably doesn't compile as written since it's freehand):
private static class ReleasableValue<T extends Releasable> {
@Nullable private T value;
ReleasableValue(@Nullable T value) {
this.value = value;
}
void set(@Nullable T newValue) {
release();
value = newValue;
}
@Nullable T get() {
return value;
}
void release() {
if (value != null) {
value.release();
}
value = null;
}
void finalize() {
release();
}
}
There was a problem hiding this comment.
ah ok that makes sense. I changed it to:
private static class ReleasableValue<T> {
@Nullable private T value;
ReleasableValue() {}
ReleasableValue(@Nullable T value) {
this.value = value;
}
void set(@Nullable T newValue) {
release();
value = newValue;
}
@Nullable
T get() {
return value;
}
void release() {
if (value instanceof Releasable) {
((Releasable) value).release();
}
value = null;
}
}I removed finalize since it was only calling release. I also changed ReleasableValue<T extends Releasable> to ReleasableValue<T> because the WebView.setWebViewClient will take either a WebViewClientImpl or WebViewClientCompatImpl and their only shared interface is WebViewClient; which doesn't extend Releasable.
There was a problem hiding this comment.
But I suppose I could just cast WebViewClient to Releasable as I set it. Maybe that is a better solution?
|
|
||
| private static class ReleasableChild<T> { | ||
| @Nullable private T child; | ||
| private static class ReleasableValue<T> { |
There was a problem hiding this comment.
Why isn't this using bounded types?
There was a problem hiding this comment.
(Which would avoid the need for the type check in release, in addition to making sure incorrect use of ReleasableValue wouldn't compile.)
| public void addJavascriptInterface(Object object, String name) { | ||
| super.addJavascriptInterface(object, name); | ||
| if (object instanceof JavaScriptChannel) { | ||
| javaScriptInterfaces.put(name, new ReleasableValue<>((JavaScriptChannel) object)); |
There was a problem hiding this comment.
If this replaces a value, the old values won't get release() called until the next GC cycle; is that okay, or does any previous value need to be explicitly found and released here?
There was a problem hiding this comment.
Good catch. Release actually needs to be called because the Dart Object would still need to be disposed.
There was a problem hiding this comment.
This is why I think ReleasableValue should have a finalize that calls release.
There was a problem hiding this comment.
I started moving away from finalize because I remembered that it is deprecated in Java 9+: https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#finalize--. And we are already using Java 8.
stuartmorgan-g
left a comment
There was a problem hiding this comment.
LGTM modulo the two comments above.
Split of #4455
The pigeon implementation and current implementation has potential leaking callbacks when a
WebViewis disposed. This is a possible cause of flaky tests for WebView, so this PR attempts to prevent callbacks when the correspondingWebViewobject is disposed. In #4455, the integration tests now seem to pass consistently. Along with fixing the resize test: #4460This also fixes some trivial bugs found though the integration and widget tests in #4455.
Dart Portion: #4461
Pre-launch Checklist
dart format.)[shared_preferences]///).If you need help, consider asking for advice on the #hackers-new channel on Discord.