From ab7865143f053a554005147e30a088f872355f26 Mon Sep 17 00:00:00 2001 From: jim-daf Date: Wed, 22 Apr 2026 21:35:29 +0200 Subject: [PATCH] Properly destroy LilicoWebView instances on tab and activity teardown The browser keeps a list of LilicoWebView instances in BrowserTabs.kt and never calls destroy on them. popBrowserTab only removes the view from its parent and clearBrowserTabs only empties the list. WebViewActivity has the same gap, the WebView is held by the layout for the lifetime of the activity and is dropped on the floor when the user leaves. The chromium-side resources behind a WebView (renderer process, GPU buffers, JS heap, parcel buffers) stay live until destroy is called, so each navigation that creates or rotates a tab leaks memory. The OOM cluster of crash reports against ExploreFragment and the dialogs that hover above the browser are consistent with that leak. The same root cause shows up in the RxJava OutOfMemory wrappers and in the various 16 to 64 byte allocation failures reported from random Parcel and SystemSensor sites. Changes: - Add a private LilicoWebView.destroyWebView extension that runs the standard cleanup sequence (stopLoading, clear callback, about:blank, clearHistory, detach, destroy) inside runCatching. - Call destroyWebView from popBrowserTab after the view is removed from the container. - Call destroyWebView for every tab inside clearBrowserTabs before the list is emptied. - Override onDestroy in WebViewActivity to apply the same cleanup. --- .../wallet/page/browser/tools/BrowserTabs.kt | 24 +++++++++++++++++++ .../wallet/page/common/WebViewActivity.kt | 21 ++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/app/src/main/java/com/flowfoundation/wallet/page/browser/tools/BrowserTabs.kt b/app/src/main/java/com/flowfoundation/wallet/page/browser/tools/BrowserTabs.kt index 5de0c4159..3194fbebc 100644 --- a/app/src/main/java/com/flowfoundation/wallet/page/browser/tools/BrowserTabs.kt +++ b/app/src/main/java/com/flowfoundation/wallet/page/browser/tools/BrowserTabs.kt @@ -11,6 +11,7 @@ import com.flowfoundation.wallet.page.browser.widgets.LilicoWebView import com.flowfoundation.wallet.page.window.WindowFrame import com.flowfoundation.wallet.utils.extensions.removeFromParent import com.flowfoundation.wallet.utils.extensions.setVisible +import com.flowfoundation.wallet.utils.loge import java.util.* private val tabs = mutableListOf() @@ -24,6 +25,7 @@ fun popBrowserTab(tabId: String) { tab.webView.saveRecentRecord() webViewContainer.removeWebView(tab.webView) + tab.webView.destroyWebView() if (tabs.isEmpty()) { releaseBrowser() @@ -72,6 +74,11 @@ fun newAndPushBrowserTab(url: String? = null): BrowserTab? { } fun clearBrowserTabs() { + // Destroy each WebView before dropping the references. Without this the + // chromium-side resources (renderer process, GPU buffers, JS heap) stay + // alive until the GC happens to collect the wrapper, which is a major + // contributor to the OOM reports filed against ExploreFragment. + tabs.forEach { runCatching { it.webView.destroyWebView() }.onFailure { loge(it) } } tabs.clear() } @@ -100,6 +107,23 @@ private fun cleanCallbacks() { tabs.forEach { it.webView.setWebViewCallback(null) } } +/** + * Standard cleanup sequence recommended by the Android WebView team. Skipping + * any of these steps tends to leave the renderer process or the chromium JS + * heap allocated, which shows up later as an OOM in an unrelated allocation. + */ +private fun LilicoWebView.destroyWebView() { + runCatching { + stopLoading() + setWebViewCallback(null) + loadUrl("about:blank") + clearHistory() + removeFromParent() + removeAllViews() + destroy() + }.onFailure { loge(it) } +} + class BrowserTab( val id: String, val webView: LilicoWebView, diff --git a/app/src/main/java/com/flowfoundation/wallet/page/common/WebViewActivity.kt b/app/src/main/java/com/flowfoundation/wallet/page/common/WebViewActivity.kt index d0379cc91..45c28d45f 100644 --- a/app/src/main/java/com/flowfoundation/wallet/page/common/WebViewActivity.kt +++ b/app/src/main/java/com/flowfoundation/wallet/page/common/WebViewActivity.kt @@ -7,6 +7,8 @@ import android.view.MenuItem import com.flowfoundation.wallet.R import com.flowfoundation.wallet.base.activity.BaseActivity import com.flowfoundation.wallet.page.browser.widgets.LilicoWebView +import com.flowfoundation.wallet.utils.extensions.removeFromParent +import com.flowfoundation.wallet.utils.loge class WebViewActivity : BaseActivity() { @@ -20,6 +22,25 @@ class WebViewActivity : BaseActivity() { } } + override fun onDestroy() { + // Without an explicit destroy the chromium-side resources backing the + // WebView outlive this Activity and accumulate across launches. That + // leak is one of the contributors to the OOM crashes reported against + // browser-adjacent screens. + runCatching { + findViewById(R.id.webview)?.let { web -> + web.stopLoading() + web.setWebViewCallback(null) + web.loadUrl("about:blank") + web.clearHistory() + web.removeFromParent() + web.removeAllViews() + web.destroy() + } + }.onFailure { loge(it) } + super.onDestroy() + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> finish()