From eb927dfd1e35aebf3bf8c6369d003d12f4356193 Mon Sep 17 00:00:00 2001 From: dimitris Date: Sat, 2 May 2026 15:23:03 +0200 Subject: [PATCH 1/2] Fix #45: avoid multi-process WebView data dir crash Sets a per-process WebView data directory suffix in MyApplication.onCreate so that secondary processes (typically isolated services started by Firebase / WorkManager / third party SDKs) don't trip https://crbug.com/558377: java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported. Uses Application.getProcessName() (API 28+) and only applies the suffix when the current process is NOT the main app process, so the main UI process keeps using the default data dir and existing WebView storage is preserved. --- .../com/flamyoad/honnoki/MyApplication.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt b/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt index e35ecdd..4f2e7bb 100644 --- a/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt +++ b/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt @@ -1,6 +1,8 @@ package com.flamyoad.honnoki import android.app.Application +import android.os.Build +import android.webkit.WebView import androidx.appcompat.app.AppCompatDelegate import com.flamyoad.honnoki.data.preference.UiPreference import com.flamyoad.honnoki.di.* @@ -26,6 +28,8 @@ class MyApplication : Application() { override fun onCreate() { super.onCreate() + applyWebViewDataDirectorySuffix() + if (BuildConfig.DEBUG) { Timber.plant(Timber.DebugTree()) } @@ -70,4 +74,29 @@ class MyApplication : Application() { venom.initialize(notification) Venom.setGlobalInstance(venom) } + + /** + * Workaround for https://crbug.com/558377 — when the same Android app is launched + * in more than one process (e.g. an isolated `:remote` service started by a third + * party SDK), the second WebView initialization in the same data directory throws: + * + * java.lang.RuntimeException: Using WebView from more than one process at once + * with the same data directory is not supported. + * + * Setting a per-process suffix before any WebView is touched gives each process + * its own data directory and avoids the crash. Available since API 28 (P). + * + * Tracking issue: https://github.com/flamyoad/Honnoki/issues/45 + */ + private fun applyWebViewDataDirectorySuffix() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return + val processName = getProcessName() ?: return + if (packageName != processName) { + try { + WebView.setDataDirectorySuffix(processName) + } catch (t: Throwable) { + Timber.w(t, "Failed to set WebView data directory suffix for %s", processName) + } + } + } } \ No newline at end of file From 6ee3b19bbb30c3e53f46fa2218836770785b8aa9 Mon Sep 17 00:00:00 2001 From: dimitris Date: Sat, 2 May 2026 18:49:30 +0200 Subject: [PATCH 2/2] Also install narrow UncaughtExceptionHandler for stale-lock case The setDataDirectorySuffix workaround only helps when the colliding processes have different names. The Crashlytics report in #45 actually shows two processes with the same name (com.flamyoad.honnoki / com.flamyoad.honnoki), which means the lock collision is most likely a stale data-dir lock left by a previously-killed instance, or a WebView provider swap mid-run. Neither is fixable by a suffix. Install a narrow Thread.setDefaultUncaughtExceptionHandler that recognises this exact RuntimeException (matched by message walk through the cause chain), logs it via Timber, and silently kills the current process so the OS releases the file lock and ActivityManager can restart any foreground component cleanly. All other exceptions are forwarded to the previous default handler (Crashlytics) untouched. --- .../com/flamyoad/honnoki/MyApplication.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt b/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt index 4f2e7bb..09b6a10 100644 --- a/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt +++ b/app/src/main/java/com/flamyoad/honnoki/MyApplication.kt @@ -2,8 +2,10 @@ package com.flamyoad.honnoki import android.app.Application import android.os.Build +import android.os.Process import android.webkit.WebView import androidx.appcompat.app.AppCompatDelegate +import kotlin.system.exitProcess import com.flamyoad.honnoki.data.preference.UiPreference import com.flamyoad.honnoki.di.* import com.github.venom.Venom @@ -28,6 +30,7 @@ class MyApplication : Application() { override fun onCreate() { super.onCreate() + installWebViewMultiProcessGuard() applyWebViewDataDirectorySuffix() if (BuildConfig.DEBUG) { @@ -99,4 +102,57 @@ class MyApplication : Application() { } } } + + /** + * Belt-and-suspenders for https://crbug.com/558377. The data-directory suffix + * above prevents the collision when two processes have *different* names, but + * the same `RuntimeException` can also be triggered by: + * + * - a stale data-dir lock left over from a previously-killed instance of + * this same process (the lock owner PID is gone but the file lock remains); + * - the system swapping the WebView provider implementation while we're + * running, causing two AwBrowserProcess attempts in quick succession. + * + * In both cases the suffix can't help. Letting the exception propagate kills + * the app with a user-visible "App keeps stopping" dialog and a Crashlytics + * report (issue #45). Instead we install a fallback handler that recognises + * this exact exception, logs it, and silently terminates the current process. + * Android's ActivityManager will restart any foreground component cleanly, + * by which time the lock will have been released by the OS. + * + * The handler is intentionally narrow — it only swallows the WebView + * multi-process exception. Every other crash is forwarded to the previous + * default handler (Crashlytics, etc.) untouched. + */ + private fun installWebViewMultiProcessGuard() { + val previous = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + if (isWebViewMultiProcessCrash(throwable)) { + Timber.w( + throwable, + "Swallowing WebView multi-process data-dir crash; killing pid %d to release the lock", + Process.myPid() + ) + Process.killProcess(Process.myPid()) + exitProcess(10) + } else { + previous?.uncaughtException(thread, throwable) + } + } + } + + private fun isWebViewMultiProcessCrash(throwable: Throwable?): Boolean { + var t: Throwable? = throwable + while (t != null) { + val msg = t.message + if (t is RuntimeException && + msg != null && + msg.contains("Using WebView from more than one process") + ) { + return true + } + t = t.cause + } + return false + } } \ No newline at end of file