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()