improve lifecycle management
This commit is contained in:
+4
-2
@@ -18,13 +18,15 @@ import io.flutter.plugin.platform.PlatformView
|
||||
import io.flutter.plugin.platform.PlatformViewFactory
|
||||
|
||||
class GeckoViewFactory(
|
||||
private val activity: Activity,
|
||||
private val activityProvider: () -> Activity?,
|
||||
private val containerId: Int,
|
||||
private val flutterEvents: GeckoStateEvents
|
||||
) : PlatformViewFactory(
|
||||
StandardMessageCodec.INSTANCE) {
|
||||
override fun create(context: Context?, id: Int, args: Any?): PlatformView {
|
||||
return NativeFragmentView(this.activity, this.containerId, this.flutterEvents)
|
||||
val activity = activityProvider()
|
||||
?: throw IllegalStateException("No activity available when creating GeckoView platform view")
|
||||
return NativeFragmentView(activity, this.containerId, this.flutterEvents)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-11
@@ -134,26 +134,27 @@ class GeckoBrowserApiImpl : GeckoBrowserApi {
|
||||
_flutterPluginBinding = flutterPluginBinding
|
||||
_flutterEvents = GeckoStateEvents(_flutterPluginBinding.binaryMessenger)
|
||||
|
||||
// Register platform view factory once per engine binding.
|
||||
// The factory resolves the current activity lazily via activityProvider,
|
||||
// so it always uses the latest activity after recreation/config changes.
|
||||
_flutterPluginBinding.platformViewRegistry.registerViewFactory(
|
||||
"eu.weblibre/gecko", GeckoViewFactory(
|
||||
activityProvider = { this.activity },
|
||||
FRAGMENT_CONTAINER_ID,
|
||||
_flutterEvents
|
||||
)
|
||||
)
|
||||
isPlatformViewRegistered = true
|
||||
|
||||
isGeckoInitialized = false
|
||||
}
|
||||
|
||||
fun attachActivity(activity: Activity) {
|
||||
this.activity = activity
|
||||
|
||||
_flutterPluginBinding.platformViewRegistry.registerViewFactory(
|
||||
"eu.weblibre/gecko", GeckoViewFactory(
|
||||
activity,
|
||||
FRAGMENT_CONTAINER_ID,
|
||||
_flutterEvents
|
||||
)
|
||||
)
|
||||
|
||||
isPlatformViewRegistered = true
|
||||
}
|
||||
|
||||
fun detachActivity() {
|
||||
this.activity = null
|
||||
isPlatformViewRegistered = false
|
||||
}
|
||||
|
||||
override fun getGeckoVersion(): String {
|
||||
|
||||
+18
-1
@@ -10,8 +10,14 @@ import java.io.File
|
||||
/**
|
||||
* Manages pluggable transports via IPtProxy
|
||||
* Supports: obfs4, snowflake, meek, webtunnel
|
||||
*
|
||||
* IMPORTANT: This is a process-level singleton. The IPtProxy.Controller (Go object bound
|
||||
* via gomobile) uses reference tracking that breaks if multiple Controller instances are
|
||||
* created in the same process. By keeping a single PluggableTransportManager (and thus a
|
||||
* single lazy Controller), we avoid "trackGoRef called with Java refnum" crashes when
|
||||
* TorService is destroyed and recreated.
|
||||
*/
|
||||
class PluggableTransportManager(private val context: Context) {
|
||||
class PluggableTransportManager private constructor(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PTManager"
|
||||
@@ -23,6 +29,17 @@ class PluggableTransportManager(private val context: Context) {
|
||||
private val SNOWFLAKE_FRONTS = listOf("foursquare.com", "github.githubassets.com")
|
||||
private val SNOWFLAKE_AMP_FRONTS = listOf("www.google.com")
|
||||
private const val SNOWFLAKE_ICE_SERVERS = "stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478"
|
||||
|
||||
@Volatile
|
||||
private var instance: PluggableTransportManager? = null
|
||||
|
||||
fun getInstance(context: Context): PluggableTransportManager {
|
||||
return instance ?: synchronized(this) {
|
||||
instance ?: PluggableTransportManager(context.applicationContext).also {
|
||||
instance = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val stateDir = File(context.cacheDir, "iptproxy")
|
||||
|
||||
@@ -35,7 +35,7 @@ class TorManager(
|
||||
private var controlConnection: TorControlConnection? = null
|
||||
private var torService: TorService? = null
|
||||
|
||||
val pluggableTransportManager = PluggableTransportManager(context)
|
||||
val pluggableTransportManager = PluggableTransportManager.getInstance(context)
|
||||
private val geoIpManager = GeoIpManager(context)
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
@@ -199,9 +199,20 @@ class TorService : Service() {
|
||||
super.onDestroy()
|
||||
Log.d(TAG, "Service destroyed")
|
||||
|
||||
scope.launch {
|
||||
torManager?.destroy()
|
||||
}
|
||||
// Stop pluggable transports synchronously to release Go references.
|
||||
// Previously this was scope.launch { torManager?.destroy() } followed by
|
||||
// scope.cancel(), which meant the cleanup coroutine was immediately cancelled
|
||||
// and never ran — causing "trackGoRef called with Java refnum" crashes when
|
||||
// TorService was recreated and tried to create a new IPtProxy.Controller.
|
||||
//
|
||||
// Note: We only stop transports here. The PluggableTransportManager singleton
|
||||
// and its Controller persist across service restarts by design.
|
||||
// Full TorManager.destroy() is not called because it would deadlock
|
||||
// (cleanup() uses runBlocking(Dispatchers.Main) while onDestroy runs on Main).
|
||||
torManager?.pluggableTransportManager?.stopAll()
|
||||
|
||||
torManager = null
|
||||
logHandler = null
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user