mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 22:25:26 +00:00
fix: improve LocationChannelManager resource management
- Add managed CoroutineScope with SupervisorJob that is properly cancelled in cleanup() to prevent memory leaks from orphaned coroutines - Add 120-second timeout for fresh location requests on Android 11+ using CancellationSignal to prevent indefinite loading states - Clean up timeout handler and cancellation signal in cleanup()
This commit is contained in:
@@ -8,6 +8,9 @@ import android.location.Location
|
|||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.CancellationSignal
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@@ -25,6 +28,7 @@ class LocationChannelManager private constructor(private val context: Context) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "LocationChannelManager"
|
private const val TAG = "LocationChannelManager"
|
||||||
|
private const val LOCATION_TIMEOUT_MS = 120_000L // 120 seconds
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var INSTANCE: LocationChannelManager? = null
|
private var INSTANCE: LocationChannelManager? = null
|
||||||
@@ -51,6 +55,18 @@ class LocationChannelManager private constructor(private val context: Context) {
|
|||||||
private var isGeocoding: Boolean = false
|
private var isGeocoding: Boolean = false
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
private var dataManager: com.bitchat.android.ui.DataManager? = null
|
private var dataManager: com.bitchat.android.ui.DataManager? = null
|
||||||
|
|
||||||
|
// Managed coroutine scope for background operations - cancelled in cleanup()
|
||||||
|
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||||
|
|
||||||
|
// Cancellation signal for location requests with timeout
|
||||||
|
private var locationCancellationSignal: CancellationSignal? = null
|
||||||
|
private val locationTimeoutHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val locationTimeoutRunnable = Runnable {
|
||||||
|
Log.w(TAG, "Location request timed out after 120 seconds")
|
||||||
|
locationCancellationSignal?.cancel()
|
||||||
|
_isLoadingLocation.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// Published state for UI bindings (matching iOS @Published properties)
|
// Published state for UI bindings (matching iOS @Published properties)
|
||||||
private val _permissionState = MutableStateFlow(PermissionState.NOT_DETERMINED)
|
private val _permissionState = MutableStateFlow(PermissionState.NOT_DETERMINED)
|
||||||
@@ -402,12 +418,22 @@ class LocationChannelManager private constructor(private val context: Context) {
|
|||||||
Log.d(TAG, "Getting current location from $provider")
|
Log.d(TAG, "Getting current location from $provider")
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||||
// For Android 11+ (API 30+), use getCurrentLocation
|
// For Android 11+ (API 30+), use getCurrentLocation with timeout
|
||||||
|
locationCancellationSignal?.cancel()
|
||||||
|
locationCancellationSignal = CancellationSignal()
|
||||||
|
|
||||||
|
// Set up timeout
|
||||||
|
locationTimeoutHandler.removeCallbacks(locationTimeoutRunnable)
|
||||||
|
locationTimeoutHandler.postDelayed(locationTimeoutRunnable, LOCATION_TIMEOUT_MS)
|
||||||
|
|
||||||
locationManager.getCurrentLocation(
|
locationManager.getCurrentLocation(
|
||||||
provider,
|
provider,
|
||||||
null, // No cancellation signal
|
locationCancellationSignal,
|
||||||
context.mainExecutor,
|
context.mainExecutor,
|
||||||
{ location ->
|
{ location ->
|
||||||
|
// Cancel timeout since we got a response
|
||||||
|
locationTimeoutHandler.removeCallbacks(locationTimeoutRunnable)
|
||||||
|
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
Log.d(TAG, "Fresh location received: ${location.latitude}, ${location.longitude}")
|
Log.d(TAG, "Fresh location received: ${location.latitude}, ${location.longitude}")
|
||||||
lastLocation = location
|
lastLocation = location
|
||||||
@@ -526,7 +552,7 @@ class LocationChannelManager private constructor(private val context: Context) {
|
|||||||
|
|
||||||
isGeocoding = true
|
isGeocoding = true
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
coroutineScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
Log.d(TAG, "Starting reverse geocoding")
|
Log.d(TAG, "Starting reverse geocoding")
|
||||||
|
|
||||||
@@ -724,9 +750,15 @@ class LocationChannelManager private constructor(private val context: Context) {
|
|||||||
Log.d(TAG, "Cleaning up LocationChannelManager")
|
Log.d(TAG, "Cleaning up LocationChannelManager")
|
||||||
endLiveRefresh()
|
endLiveRefresh()
|
||||||
|
|
||||||
|
// Cancel any pending location timeout
|
||||||
|
locationTimeoutHandler.removeCallbacks(locationTimeoutRunnable)
|
||||||
|
locationCancellationSignal?.cancel()
|
||||||
|
|
||||||
|
// Cancel all coroutines to prevent memory leaks
|
||||||
|
coroutineScope.cancel()
|
||||||
|
|
||||||
// Remove listeners to prevent memory leaks
|
// Remove listeners to prevent memory leaks
|
||||||
try { locationManager.removeUpdates(oneShotLocationListener) } catch (_: Exception) {}
|
try { locationManager.removeUpdates(oneShotLocationListener) } catch (_: Exception) {}
|
||||||
try { locationManager.removeUpdates(continuousLocationListener) } catch (_: Exception) {}
|
try { locationManager.removeUpdates(continuousLocationListener) } catch (_: Exception) {}
|
||||||
// For Android 11+, getCurrentLocation doesn't need explicit cleanup as it's a one-time operation
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user