如何用Kotlin协程实现多独立无限循环并解决应用卡顿
问题分析与解决方案
现有代码的核心问题
- 主线程阻塞:
viewModelScope默认使用主线程,GPS获取、数据库插入这些耗时操作在主线程执行,四次启动后主线程负载过载,直接导致卡顿。 - 协程无独立管理:每次调用方法都会启动新协程,没有记录每个乘客对应的协程实例,可能重复启动多个协程处理同一乘客,加剧资源消耗。
- 定位资源浪费:每个协程都创建新的
LocationGpsTracker和LocationGpsTrackerNetwork,多实例同时监听定位会消耗大量系统资源,甚至导致定位服务异常。 - LiveData更新异常:复用同一个
Way对象修改后postValue,LiveData可能因对象引用未变而不触发UI更新,同时多协程操作同一对象存在线程安全隐患。 - 空循环占用CPU:当开关关闭时,循环会无延迟空转,持续消耗CPU资源。
针对性解决方案
1. 独立管理每个乘客的协程生命周期
用Map存储每个乘客ID对应的协程Job,启动前先取消该乘客已有的协程,避免重复启动:
// ViewModel中新增协程管理容器 private val passengerJobs = mutableMapOf<Int, Job>() // 拆分启动/停止方法,明确职责 fun startLocationTracking(context: Context, tripId: Long, passengerId: Int) { // 先取消该乘客已在运行的协程 passengerJobs[passengerId]?.cancel() // 启动新协程并切换到IO线程执行耗时操作 val job = viewModelScope.launch(Dispatchers.IO) { // 后续逻辑... } passengerJobs[passengerId] = job } fun stopLocationTracking(passengerId: Int) { passengerJobs[passengerId]?.cancel() passengerJobs.remove(passengerId) }
2. 优化定位资源的创建与释放
每个协程初始化一次定位Tracker,协程取消时主动释放资源,避免资源泄漏:
// 在协程内部初始化Tracker,finally块中释放 val gpsTracker = LocationGpsTracker(context, 0, 0) val netGpsTracker = LocationGpsTrackerNetwork(context) try { // 循环逻辑... } finally { // 根据Tracker的API实现停止监听、释放资源 gpsTracker.stopTracking() netGpsTracker.stopTracking() }
3. 耗时操作切换到IO线程,优化循环逻辑
将GPS获取、数据库插入放到IO线程执行,开关关闭时增加延迟,避免空循环占用CPU:
viewModelScope.launch(Dispatchers.IO) { while (isActive) { val switchState = _passengerSwitch.value?.get(passengerId - 1) ?: false if (switchState) { // 执行GPS获取、数据库插入等操作 delay(2000) } else { // 开关关闭时降低检查频率,减少CPU消耗 delay(500) } } }
4. 修复LiveData更新问题
每次循环创建新的Way对象,确保LiveData能感知到数据变化;更新LiveData时切换回主线程:
// 每次循环创建新的Way实例 val currentWay = Way().apply { latitude2 = previousWay.latitude1 longitude2 = previousWay.longitude1 // ... 其他属性赋值 latitude1 = gpsTracker.getLatitude() longitude1 = gpsTracker.getLongitude() // ... 其他属性赋值 } // 切换到主线程更新LiveData withContext(Dispatchers.Main) { _way.value = currentWay }
5. 数据库操作异步化
确保数据库插入是挂起函数(比如Room的@Insert suspend),避免阻塞线程:
// Room Repository中定义挂起插入方法 suspend fun insertLocation(tripDetails: TripDetails) { locationDao.insert(tripDetails) }
完整修改后的代码示例
private val passengerJobs = mutableMapOf<Int, Job>() private val _way = MutableLiveData<Way>() val way: LiveData<Way> = _way fun startLocationTracking(context: Context, tripId: Long, passengerId: Int) { passengerJobs[passengerId]?.cancel() val job = viewModelScope.launch(Dispatchers.IO) { val gpsTracker = LocationGpsTracker(context, 0, 0) val netGpsTracker = LocationGpsTrackerNetwork(context) // 初始化初始位置 var previousWay = Way().apply { latitude1 = gpsTracker.getLatitude() longitude1 = gpsTracker.getLongitude() accuracy1 = gpsTracker.getAccuracy() latitudeNet1 = netGpsTracker.getLatitude() longitudeNet1 = netGpsTracker.getLongitude() accuracyNet1 = netGpsTracker.getAccuracy() } // 初始值同步到LiveData withContext(Dispatchers.Main) { _way.value = previousWay } try { while (isActive) { val switchState = _passengerSwitch.value?.get(passengerId - 1) ?: false if (switchState) { val currentWay = Way().apply { // 复制上一次的位置数据 latitude2 = previousWay.latitude1 longitude2 = previousWay.longitude1 accuracy2 = previousWay.accuracy1 latitudeNet2 = previousWay.latitudeNet1 longitudeNet2 = previousWay.longitudeNet1 accuracyNet2 = previousWay.accuracyNet1 // 获取最新位置数据 latitude1 = gpsTracker.getLatitude() longitude1 = gpsTracker.getLongitude() accuracy1 = gpsTracker.getAccuracy() latitudeNet1 = netGpsTracker.getLatitude() longitudeNet1 = netGpsTracker.getLongitude() accuracyNet1 = netGpsTracker.getAccuracy() } // 更新LiveData withContext(Dispatchers.Main) { _way.value = currentWay } // 插入数据库 val tripDetails = TripDetails( latitude1 = currentWay.latitude1, latitude2 = currentWay.latitude2, longitude1 = currentWay.longitude1, longitude2 = currentWay.longitude2, accuracy1 = currentWay.accuracy1, accuracy2 = currentWay.accuracy2, latitudeNet1 = currentWay.latitudeNet1, latitudeNet2 = currentWay.latitudeNet2, longitudeNet1 = currentWay.longitudeNet1, longitudeNet2 = currentWay.longitudeNet2, accuracy1Net = currentWay.accuracyNet1, accuracy2Net = currentWay.accuracyNet2, distance = null, isCalculated = false, tripId = tripId.toInt(), isEnded = false ) localRepository.insertLocation(tripDetails) previousWay = currentWay delay(2000) } else { delay(500) } } } finally { // 释放定位资源 gpsTracker.stopTracking() netGpsTracker.stopTracking() } } passengerJobs[passengerId] = job } fun stopLocationTracking(passengerId: Int) { passengerJobs[passengerId]?.cancel() passengerJobs.remove(passengerId) }
内容的提问来源于stack exchange,提问作者Hooman Hooshyar
相关产品推荐
相关产品推荐

