improve shutdown routine

This commit is contained in:
Fabian Freund
2026-03-18 09:09:39 +01:00
parent 5d3f394f7b
commit 54b3821ed0
22 changed files with 226 additions and 26 deletions
@@ -31,9 +31,8 @@ import org.mozilla.geckoview.GeckoRuntimeSettings
object EngineProvider {
private var runtime: GeckoRuntime? = null
private val components by lazy {
requireNotNull(GlobalComponents.components) { "Components not initialized" }
}
private val components: Components
get() = requireNotNull(GlobalComponents.components) { "Components not initialized" }
@Synchronized
fun getOrCreateRuntime(context: Context): GeckoRuntime {
@@ -148,4 +147,13 @@ object EngineProvider {
val runtime = getOrCreateRuntime(context)
return GeckoViewFetchClient(context, runtime)
}
@Synchronized
fun shutdown() {
runtime?.let {
Logger.debug("Shutting down GeckoRuntime")
it.shutdown()
runtime = null
}
}
}
@@ -12,6 +12,7 @@ import android.view.View
import androidx.fragment.app.FragmentActivity
import eu.weblibre.flutter_mozilla_components.BrowserFragment
import eu.weblibre.flutter_mozilla_components.GeckoViewFactory
import eu.weblibre.flutter_mozilla_components.EngineProvider
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.ProfileContext
import eu.weblibre.flutter_mozilla_components.activities.ExternalAppBrowserActivity
@@ -455,4 +456,49 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
currentActivity.startActivity(intent)
}
override fun shutdown() {
logger.debug("$TAG: Shutting down GeckoView engine")
// 1. Remove the browser fragment so its onDestroyView runs while the
// runtime is still alive. This tears down EngineView, features, and
// clears component references cleanly.
try {
val fragmentActivity = activity as? FragmentActivity
if (fragmentActivity != null && !fragmentActivity.isFinishing && !fragmentActivity.isDestroyed) {
val fm = fragmentActivity.supportFragmentManager
if (!fm.isStateSaved) {
val existing = fm.findFragmentById(FRAGMENT_CONTAINER_ID)
if (existing != null) {
fm.beginTransaction().remove(existing).commitNow()
logger.debug("$TAG: Browser fragment removed")
}
}
}
} catch (e: Exception) {
logger.error("$TAG: Error removing browser fragment", e)
}
// 2. Stop component-level services
try {
GlobalComponents.components?.let { components ->
// Stop the FxA web channel feature
runCatching { components.services.fxaWebChannelFeature.stop() }
// Close the account manager
runCatching { components.backgroundServices.accountManager.close() }
}
} catch (e: Exception) {
logger.error("$TAG: Error during component shutdown", e)
}
// 3. Shutdown GeckoRuntime (safe now that no views reference it)
try {
EngineProvider.shutdown()
} catch (e: Exception) {
logger.error("$TAG: Error shutting down GeckoRuntime", e)
}
isGeckoInitialized = false
}
}
@@ -4847,6 +4847,7 @@ interface GeckoBrowserApi {
fun openInCustomTab(url: String, private: Boolean, contextId: String?)
fun isDefaultBrowser(): Boolean
fun requestDefaultBrowser()
fun shutdown()
companion object {
/** The codec used by GeckoBrowserApi. */
@@ -4980,6 +4981,22 @@ interface GeckoBrowserApi {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.flutter_mozilla_components.GeckoBrowserApi.shutdown$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { _, reply ->
val wrapped: List<Any?> = try {
api.shutdown()
listOf(null)
} catch (exception: Throwable) {
GeckoPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}