如何每隔一分钟周期性调用协程挂起函数实现天气API请求
问题:Android天气APP实现每分钟调用API更新数据
我正在开发一款天气APP,需要每隔一分钟调用一次API获取数据。API调用函数是挂起函数,目前在MainActivity的协程中执行,请问怎么实现每隔60秒调用这些协程方法发起请求?
现有代码片段
MainActivity中的调用逻辑
val lat = location.latitude val lon = location.longitude val vmFactory = weatherVMFactory(application,appcontext,netwReq,lat.toString(),lon.toString()) val viewModel = ViewModelProvider(this,vmFactory).get(weatherVM::class.java) GlobalScope.launch(Dispatchers.Main) { // 需每分钟调用以下方法 val wInfo = async { viewModel.otherData() } setOtherData(wInfo.await()) val dInfo = async { viewModel.dailyData() } setDailyData(dInfo.await()) val hInfo = async { viewModel.hourlyData() } setHourlyData(hInfo.await()) }
ViewModel类
package com.example.weatherapp import android.app.Application import android.content.Context import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import kotlinx.coroutines.* class weatherVM(application: Application, appcon: Context, jQueue: Singleton, latitude: String, longitude: String) : AndroidViewModel(application) { private val wRepo = WeatherRepo(appcon,jQueue,latitude,longitude) suspend fun otherData(): MutableLiveData<OtherInfo> { return wRepo.getOtherData() } suspend fun dailyData(): MutableLiveData<ArrayList<DailyInfo>> { return wRepo.getDailyData() } suspend fun hourlyData(): MutableLiveData<ArrayList<HourlyInfo>>{ return wRepo.getHourlyData() } }
Repository中的API调用示例
suspend fun getDailyData(): MutableLiveData<ArrayList<DailyInfo>> = withContext(Dispatchers.IO){ val tinfoArr = ArrayList<DailyInfo>() val jsonO = JsonObjectRequest(Request.Method.GET,url,null,{ val dailyArr = it.getJSONArray("daily") for (i in 1 until dailyArr.length()) { val dailyObj = dailyArr.getJSONObject(i) val daily = DailyInfo(dailyObj.getLong("dt"),dailyObj.getInt("humidity"),dailyObj.getJSONObject("temp").getInt("day"),dailyObj.getJSONObject("feels_like").getInt("day")) tinfoArr.add(daily) } dInfo.value = tinfoArr },{ Toast.makeText(appCon,"Error fetching Daily Forecast",Toast.LENGTH_LONG).show() }) jQueue.addToRequestQueue(jsonO) return@withContext dInfo }
解决方案与优化建议
一、用ViewModelScope实现可控的定时任务
不要使用GlobalScope(生命周期不受控,易引发内存泄漏),改用ViewModel自带的viewModelScope,页面销毁时会自动取消协程。
在ViewModel中添加定时刷新逻辑
class weatherVM(...) : AndroidViewModel(application) { // 原有代码... private var refreshJob: Job? = null fun startPeriodicRefresh() { refreshJob = viewModelScope.launch(Dispatchers.Main) { while (isActive) { // 协程活跃时持续执行 // 并行发起三个API请求 val otherDeferred = async { otherData() } val dailyDeferred = async { dailyData() } val hourlyDeferred = async { hourlyData() } // 等待所有请求完成后更新UI setOtherData(otherDeferred.await().value) setDailyData(dailyDeferred.await().value) setHourlyData(hourlyDeferred.await().value) delay(60000) // 间隔60秒 } } } // 手动停止刷新的方法 fun stopPeriodicRefresh() { refreshJob?.cancel() } override fun onCleared() { super.onCleared() stopPeriodicRefresh() // ViewModel销毁时自动停止 } // 注:建议将setXXXData逻辑移至ViewModel或通过LiveData观察更新UI,更符合MVVM规范 }
在MainActivity中启动定时任务
val viewModel = ViewModelProvider(this, vmFactory).get(weatherVM::class.java) viewModel.startPeriodicRefresh()
二、修复Repository中挂起函数的逻辑问题
当前Repository的挂起函数存在缺陷:withContext(Dispatchers.IO)仅切换线程,但JsonObjectRequest是异步回调,挂起函数会直接返回空的LiveData,需用suspendCoroutine将回调转为挂起风格:
优化后的Repository方法
suspend fun getDailyData(): ArrayList<DailyInfo>? = suspendCoroutine { continuation -> val jsonO = JsonObjectRequest(Request.Method.GET, url, null, { response -> val tinfoArr = ArrayList<DailyInfo>() val dailyArr = response.getJSONArray("daily") for (i in 1 until dailyArr.length()) { val dailyObj = dailyArr.getJSONObject(i) val daily = DailyInfo( dailyObj.getLong("dt"), dailyObj.getInt("humidity"), dailyObj.getJSONObject("temp").getInt("day"), dailyObj.getJSONObject("feels_like").getInt("day") ) tinfoArr.add(daily) } continuation.resume(tinfoArr) }, { error -> Toast.makeText(appCon, "Error fetching Daily Forecast", Toast.LENGTH_LONG).show() continuation.resume(null) }) jQueue.addToRequestQueue(jsonO) }
对应ViewModel的调整
private val _dailyData = MutableLiveData<ArrayList<DailyInfo>>() val dailyData: LiveData<ArrayList<DailyInfo>> = _dailyData suspend fun fetchDailyData() { val data = wRepo.getDailyData() data?.let { _dailyData.postValue(it) } }
Activity中通过LiveData观察更新UI
// MainActivity初始化后 viewModel.dailyData.observe(this) { dailyList -> setDailyData(dailyList) } // 其他数据接口同理
三、额外注意事项
- 避免请求堆积:添加
isRefreshing标志位,每次循环前检查是否有未完成的请求,防止重复发起。 - 后台适配:APP退至后台时,建议降低刷新频率或改用
WorkManager实现周期性任务,减少电量消耗。 - 异常处理:给API请求添加超时、重试逻辑,避免单次请求失败导致后续定时任务中断。
内容的提问来源于stack exchange,提问作者Anurag Hale
相关产品推荐
相关产品推荐

