-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[webview_flutter_android] Fix bugs in Java Implementation of the Pigeon API #4459
[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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.
...flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/InstanceManager.java
Outdated
Show resolved
Hide resolved
| this.flutterApi = null; | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have a summary fragment
...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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this using bounded types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is why I think ReleasableValue should have a finalize that calls release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.