You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

KMM升级Kotlin至1.9.20后iOS位置Delegate停止更新问题

问题分析与解决方案

核心原因

Kotlin 1.9.x版本的Kotlin/Native内存管理机制发生变更,导致LocationDelegate实例被提前回收。iOS的CLLocationManager对delegate持有的是弱引用,当Kotlin侧的局部LocationDelegate实例被GC回收后,系统无法再将位置更新回调到该delegate,表现为位置更新停止,但CLLocationManager本身仍在运行(因此隐私通知持续显示)。

在你的代码中,LocationDelegate是在getCurrentLocationAsFlow()的callbackFlow块内创建的局部变量,虽然赋值给了locationManager.delegate,但Kotlin/Native的自动内存管理在1.9.x版本中未保留对这个局部实例的强引用,最终导致其被回收。

修复步骤

1. 将LocationDelegate改为类成员变量

把LocationDelegate从callbackFlow内部移到LocationProviderImpl的成员位置,确保实例始终被强引用持有:

actual class LocationProviderImpl : LocationProvider {
    private val locationManager = CLLocationManager()
    // 将Delegate改为类成员,保持强引用
    private val locationDelegate = LocationDelegate()

    private class LocationDelegate : NSObject(), CLLocationManagerDelegateProtocol {
        var onLocationUpdate: ((Location?) -> Unit)? = null
        override fun locationManager(manager: CLLocationManager, didUpdateLocations: List<*>) {
            didUpdateLocations.firstOrNull()?.let {
                val location = it as CLLocation
                location.coordinate.useContents {
                    onLocationUpdate?.invoke(Location(latitude, longitude, it.altitude))
                }
            }
        }

        override fun locationManager(manager: CLLocationManager, didFailWithError: NSError) {
            onLocationUpdate?.invoke(null)
        }
    }

    override fun getCurrentLocationAsFlow(): Flow<Location?> = callbackFlow {
        locationManager.requestAlwaysAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = kCLDistanceFilterNone
        
        // 复用成员变量的Delegate,而非创建局部实例
        locationDelegate.onLocationUpdate = { location ->
            trySend(location).isSuccess
        }
        locationManager.delegate = locationDelegate
        locationManager.startUpdatingLocation()
        
        awaitClose {
            locationManager.stopUpdatingLocation()
            // 清空回调,避免内存泄漏
            locationDelegate.onLocationUpdate = null
        }
    }.flowOn(Dispatchers.IO)

    // 其他方法保持不变...
    fun requestBackground() {
        locationManager.allowsBackgroundLocationUpdates = true
    }

    fun stopBackground() {
        locationManager.allowsBackgroundLocationUpdates = false
    }
    fun stopLocationUpdates() {
        locationManager.stopUpdatingLocation()
    }
}

2. 优化协程生命周期管理

startLocationListener()每次调用都会启动新的协程,若重复调用会导致多个Flow同时监听位置,可能引发冲突。添加协程Job管理,确保每次启动前取消之前的监听:

object GpsService {
    private lateinit var locationClient: LocationProviderImpl
    private var initialized = false
    private var serviceScope = CoroutineScope(Dispatchers.IO)
    // 添加Job变量管理监听协程
    private var locationJob: Job? = null

    fun initialize(appModule: AppModule) {
        if (!initialized) {
            locationClient = LocationProviderImpl()
            initialized = true
        }
    }

    fun startLocationListener() {
        // 先取消之前的监听协程
        locationJob?.cancel()
        locationJob = locationClient.getCurrentLocationAsFlow().cancellable()
            .catch { e ->
                e.printStackTrace()
            }
            .mapNotNull { it }
            .onEach { location ->
                _state.update {
                    it.copy(
                        currentLocation = location,
                        formattedAltitude = getFormattedAltitude(location.altitude)
                    )
                }
            }.launchIn(serviceScope)
    }

    // 可选:添加停止监听的方法
    fun stopLocationListener() {
        locationJob?.cancel()
        locationClient.stopLocationUpdates()
    }
}

3. 验证Delegate回收问题(可选)

在LocationDelegate中添加finalize方法,可验证实例是否被提前回收:

private class LocationDelegate : NSObject(), CLLocationManagerDelegateProtocol {
    // ...其他方法

    override fun finalize() {
        println("LocationDelegate被回收了")
    }
}

修复前若能看到该日志,即可确认是Delegate被提前回收导致的问题。

额外注意事项

  • 确保iOS端Info.plist已正确配置位置权限描述(NSLocationAlwaysAndWhenInUseUsageDescription、NSLocationWhenInUseUsageDescription等)。
  • 若需后台位置更新,除调用requestBackground(),还需在Xcode中开启"Background Modes"的"Location updates"权限。

内容的提问来源于stack exchange,提问作者Charlie Walker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 15:26:33