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
相关产品推荐
相关产品推荐

