Skip to content

[webview_flutter_android] Fix the leak in the legacy implementation #3483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:webview_flutter_android/src/android_webview.dart' as android;
import 'package:webview_flutter_android/src/android_webview_api_impls.dart';
import 'package:webview_flutter_android/src/instance_manager.dart';
import 'package:webview_flutter_android/src/weak_reference_utils.dart';
import 'package:webview_flutter_android/src/webview_flutter_android_legacy.dart';
Expand Down Expand Up @@ -126,6 +128,76 @@ Future<void> main() async {
expect(gcIdentifier, 0);
}, timeout: const Timeout(Duration(seconds: 10)));

testWidgets(
'WebView is released by garbage collection',
(WidgetTester tester) async {
final Completer<void> webViewGCCompleter = Completer<void>();

late final InstanceManager instanceManager;
instanceManager =
InstanceManager(onWeakReferenceRemoved: (int identifier) {
final Copyable instance =
instanceManager.getInstanceWithWeakReference(identifier)!;
if (instance is android.WebView && !webViewGCCompleter.isCompleted) {
webViewGCCompleter.complete();
}
});

android.WebView.api = WebViewHostApiImpl(
instanceManager: instanceManager,
);
android.WebSettings.api = WebSettingsHostApiImpl(
instanceManager: instanceManager,
);
android.WebChromeClient.api = WebChromeClientHostApiImpl(
instanceManager: instanceManager,
);
android.WebViewClient.api = WebViewClientHostApiImpl(
instanceManager: instanceManager,
);
android.DownloadListener.api = DownloadListenerHostApiImpl(
instanceManager: instanceManager,
);

// Continually recreate web views until one is disposed through garbage
// collection.
while (!webViewGCCompleter.isCompleted) {
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
return AndroidWebView(instanceManager: instanceManager).build(
context: context,
creationParams: CreationParams(
webSettings: WebSettings(
hasNavigationDelegate: false,
userAgent: const WebSetting<String>.of('woeifj'),
),
),
javascriptChannelRegistry: JavascriptChannelRegistry(
<JavascriptChannel>{},
),
webViewPlatformCallbacksHandler: TestPlatformCallbacksHandler(),
);
},
),
);
await tester.pumpAndSettle();

await tester.pumpWidget(Container());
await tester.pumpAndSettle();
}

android.WebView.api = WebViewHostApiImpl();
android.WebSettings.api = WebSettingsHostApiImpl();
android.WebChromeClient.api = WebChromeClientHostApiImpl();
android.WebViewClient.api = WebViewClientHostApiImpl();
android.DownloadListener.api = DownloadListenerHostApiImpl();

// Create a new `WebStorage` with the default InstanceManager.
android.WebStorage.instance = android.WebStorage();
},
);

testWidgets('evaluateJavascript', (WidgetTester tester) async {
final Completer<WebViewController> controllerCompleter =
Completer<WebViewController>();
Expand Down Expand Up @@ -1572,3 +1644,25 @@ class ClassWithCallbackClass {

late final CopyableObjectWithCallback callbackClass;
}

class TestPlatformCallbacksHandler implements WebViewPlatformCallbacksHandler {
@override
FutureOr<bool> onNavigationRequest({
required String url,
required bool isForMainFrame,
}) async {
return true;
}

@override
void onPageStarted(String url) {}

@override
void onPageFinished(String url) {}

@override
void onProgress(int progress) {}

@override
void onWebResourceError(WebResourceError error) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart';
import 'package:webview_flutter_platform_interface/src/webview_flutter_platform_interface_legacy.dart';

import '../android_webview.dart';
import '../instance_manager.dart';
import 'webview_android_widget.dart';

/// Builds an Android webview.
Expand All @@ -20,6 +21,15 @@ import 'webview_android_widget.dart';
/// an [AndroidView] to embed the webview in the widget hierarchy, and uses a method channel to
/// communicate with the platform code.
class AndroidWebView implements WebViewPlatform {
/// Constructs an [AndroidWebView].
AndroidWebView({@visibleForTesting InstanceManager? instanceManager})
: instanceManager = instanceManager ?? JavaObject.globalInstanceManager;

/// Maintains instances used to communicate with the native objects they
/// represent.
@protected
final InstanceManager instanceManager;

@override
Widget build({
required BuildContext context,
Expand Down Expand Up @@ -55,8 +65,7 @@ class AndroidWebView implements WebViewPlatform {
gestureRecognizers: gestureRecognizers,
layoutDirection:
Directionality.maybeOf(context) ?? TextDirection.rtl,
creationParams: JavaObject.globalInstanceManager
.getIdentifier(controller.webView),
creationParams: instanceManager.getIdentifier(controller.webView),
creationParamsCodec: const StandardMessageCodec(),
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,26 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
final Map<String, WebViewAndroidJavaScriptChannel> _javaScriptChannels =
<String, WebViewAndroidJavaScriptChannel>{};

late final android_webview.WebViewClient _webViewClient = withWeakReferenceTo(
this, (WeakReference<WebViewAndroidPlatformController> weakReference) {
return webViewProxy.createWebViewClient(
onPageStarted: (_, String url) {
late final android_webview.WebViewClient _webViewClient =
webViewProxy.createWebViewClient(
onPageStarted: withWeakReferenceTo(this, (
WeakReference<WebViewAndroidPlatformController> weakReference,
) {
return (_, String url) {
weakReference.target?.callbacksHandler.onPageStarted(url);
},
onPageFinished: (_, String url) {
};
}),
onPageFinished: withWeakReferenceTo(this, (
WeakReference<WebViewAndroidPlatformController> weakReference,
) {
return (_, String url) {
weakReference.target?.callbacksHandler.onPageFinished(url);
},
onReceivedError: (
};
}),
onReceivedError: withWeakReferenceTo(this, (
WeakReference<WebViewAndroidPlatformController> weakReference,
) {
return (
_,
int errorCode,
String description,
Expand All @@ -160,8 +170,12 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
failingUrl: failingUrl,
errorType: _errorCodeToErrorType(errorCode),
));
},
onReceivedRequestError: (
};
}),
onReceivedRequestError: withWeakReferenceTo(this, (
WeakReference<WebViewAndroidPlatformController> weakReference,
) {
return (
_,
android_webview.WebResourceRequest request,
android_webview.WebResourceError error,
Expand All @@ -175,21 +189,29 @@ class WebViewAndroidPlatformController extends WebViewPlatformController {
errorType: _errorCodeToErrorType(error.errorCode),
));
}
},
urlLoading: (_, String url) {
};
}),
urlLoading: withWeakReferenceTo(this, (
WeakReference<WebViewAndroidPlatformController> weakReference,
) {
return (_, String url) {
weakReference.target?._handleNavigationRequest(
url: url,
isForMainFrame: true,
);
},
requestLoading: (_, android_webview.WebResourceRequest request) {
};
}),
requestLoading: withWeakReferenceTo(this, (
WeakReference<WebViewAndroidPlatformController> weakReference,
) {
return (_, android_webview.WebResourceRequest request) {
weakReference.target?._handleNavigationRequest(
url: request.url,
isForMainFrame: request.isForMainFrame,
);
},
);
});
};
}),
);

bool _hasNavigationDelegate = false;
bool _hasProgressTracking = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import 'webview_android_widget.dart';
/// https://github.com/flutter/flutter/wiki/Hybrid-Composition for more
/// information.
class SurfaceAndroidWebView extends AndroidWebView {
/// Constructs a [SurfaceAndroidWebView].
SurfaceAndroidWebView({@visibleForTesting super.instanceManager});

@override
Widget build({
required BuildContext context,
Expand Down Expand Up @@ -74,8 +77,8 @@ class SurfaceAndroidWebView extends AndroidWebView {
// directionality.
layoutDirection:
Directionality.maybeOf(context) ?? TextDirection.ltr,
webViewIdentifier: JavaObject.globalInstanceManager
.getIdentifier(controller.webView)!,
webViewIdentifier:
instanceManager.getIdentifier(controller.webView)!,
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..addOnPlatformViewCreatedListener((int id) {
Expand Down