From 58cc0824c22ab879d2eeaa78ec6c3fd187fa63e0 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Fri, 8 Nov 2024 15:00:03 -0300 Subject: [PATCH] fix(android): drop MainPipe on activity destroy (#1415) fixes a memory leak when the activity is destroyed but the app is opened in the foreground ref https://github.com/tauri-apps/tauri/issues/11609 --- src/android/binding.rs | 7 +++++++ src/android/kotlin/WryActivity.kt | 2 ++ src/android/main_pipe.rs | 13 +++++++++++-- src/android/mod.rs | 17 +++++++++++------ src/error.rs | 3 +++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/android/binding.rs b/src/android/binding.rs index fa79b9952..fafad29e5 100644 --- a/src/android/binding.rs +++ b/src/android/binding.rs @@ -32,6 +32,8 @@ macro_rules! android_binding { ($domain:ident, $package:ident, $wry:path) => {{ use $wry::{android_setup as _, prelude::*}; + android_fn!($domain, $package, WryActivity, onActivityDestroy, [JObject]); + android_fn!( $domain, $package, @@ -258,6 +260,11 @@ fn handle_request( Ok(*JObject::null()) } +#[allow(non_snake_case)] +pub unsafe fn onActivityDestroy(_: JNIEnv, _: JClass, _: JObject) { + super::MainPipe::send(super::WebViewMessage::OnDestroy); +} + #[allow(non_snake_case)] pub unsafe fn handleRequest( mut env: JNIEnv, diff --git a/src/android/kotlin/WryActivity.kt b/src/android/kotlin/WryActivity.kt index 9ef0c38f1..3b3013502 100644 --- a/src/android/kotlin/WryActivity.kt +++ b/src/android/kotlin/WryActivity.kt @@ -93,6 +93,7 @@ abstract class WryActivity : AppCompatActivity() { override fun onDestroy() { super.onDestroy() destroy() + onActivityDestroy() } override fun onLowMemory() { @@ -125,6 +126,7 @@ abstract class WryActivity : AppCompatActivity() { private external fun stop() private external fun save() private external fun destroy() + private external fun onActivityDestroy() private external fun memory() private external fun focus(focus: Boolean) diff --git a/src/android/main_pipe.rs b/src/android/main_pipe.rs index 34cc33efe..3b8e93d55 100644 --- a/src/android/main_pipe.rs +++ b/src/android/main_pipe.rs @@ -21,6 +21,11 @@ pub static MAIN_PIPE: Lazy<[OwnedFd; 2]> = Lazy::new(|| { unsafe { pipe.map(|fd| OwnedFd::from_raw_fd(fd)) } }); +pub enum MainPipeState { + Alive, + Destroyed, +} + pub struct MainPipe<'a> { pub env: JNIEnv<'a>, pub activity: GlobalRef, @@ -42,7 +47,7 @@ impl<'a> MainPipe<'a> { } } - pub fn recv(&mut self) -> JniResult<()> { + pub fn recv(&mut self) -> JniResult { let activity = self.activity.as_obj(); if let Ok(message) = CHANNEL.1.recv() { match message { @@ -341,9 +346,12 @@ impl<'a> MainPipe<'a> { .unwrap(); } } + WebViewMessage::OnDestroy => { + return Ok(MainPipeState::Destroyed); + } } } - Ok(()) + Ok(MainPipeState::Alive) } } @@ -413,6 +421,7 @@ pub(crate) enum WebViewMessage { LoadUrl(String, Option), LoadHtml(String), ClearAllBrowsingData, + OnDestroy, } pub(crate) struct CreateWebViewAttributes { diff --git a/src/android/mod.rs b/src/android/mod.rs index 208b9c71b..e284f389b 100644 --- a/src/android/mod.rs +++ b/src/android/mod.rs @@ -27,15 +27,17 @@ use std::{ collections::HashMap, os::fd::{AsFd as _, AsRawFd as _}, sync::{mpsc::channel, Mutex}, + time::Duration, }; pub(crate) mod binding; mod main_pipe; -use main_pipe::{CreateWebViewAttributes, MainPipe, WebViewMessage, MAIN_PIPE}; +use main_pipe::{CreateWebViewAttributes, MainPipe, MainPipeState, WebViewMessage, MAIN_PIPE}; use crate::util::Counter; static COUNTER: Counter = Counter::new(); +const MAIN_PIPE_TIMEOUT: Duration = Duration::from_secs(10); pub struct Context<'a, 'b> { pub env: &'a mut JNIEnv<'b>, @@ -133,8 +135,11 @@ pub unsafe fn android_setup( let size = std::mem::size_of::(); let mut wake = false; if libc::read(fd.as_raw_fd(), &mut wake as *mut _ as *mut _, size) == size as libc::ssize_t { - main_pipe.recv().is_ok() + let res = main_pipe.recv(); + // unregister itself on errors or destroy event + matches!(res, Ok(MainPipeState::Alive)) } else { + // unregister itself false } }) @@ -300,7 +305,7 @@ impl InnerWebView { }); (custom_protocol.1)(webview_id, request, RequestAsyncResponder { responder }); - return Some(rx.recv().unwrap()); + return Some(rx.recv_timeout(MAIN_PIPE_TIMEOUT).unwrap()); } None }, @@ -337,7 +342,7 @@ impl InnerWebView { pub fn url(&self) -> crate::Result { let (tx, rx) = bounded(1); MainPipe::send(WebViewMessage::GetUrl(tx)); - rx.recv().map_err(Into::into) + rx.recv_timeout(MAIN_PIPE_TIMEOUT).map_err(Into::into) } pub fn eval(&self, js: &str, callback: Option) -> Result<()> { @@ -391,7 +396,7 @@ impl InnerWebView { pub fn cookies_for_url(&self, url: &str) -> Result>> { let (tx, rx) = bounded(1); MainPipe::send(WebViewMessage::GetCookies(tx, url.to_string())); - rx.recv().map_err(Into::into) + rx.recv_timeout(MAIN_PIPE_TIMEOUT).map_err(Into::into) } pub fn cookies(&self) -> Result>> { @@ -440,7 +445,7 @@ impl JniHandle { pub fn platform_webview_version() -> Result { let (tx, rx) = bounded(1); MainPipe::send(WebViewMessage::GetWebViewVersion(tx)); - rx.recv().unwrap() + rx.recv_timeout(MAIN_PIPE_TIMEOUT).unwrap() } fn with_html_head(document: &mut NodeRef, f: F) { diff --git a/src/error.rs b/src/error.rs index a7fad37e7..d9e8ef6d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,9 @@ pub enum Error { NulError(#[from] std::ffi::NulError), #[error(transparent)] ReceiverError(#[from] std::sync::mpsc::RecvError), + #[cfg(target_os = "android")] + #[error(transparent)] + ReceiverTimeoutError(#[from] crossbeam_channel::RecvTimeoutError), #[error(transparent)] SenderError(#[from] std::sync::mpsc::SendError), #[error("Failed to send the message")]