基于Retrofit2的实时比分APP:无需重启更新赛事数据的实现问询
实时比分应用的数据自动更新方案(基于Retrofit2 + ViewModel)
一、用Retrofit2实现持续API调用(定时轮询方案)
你的ViewModel目前只在初始化时调用一次API,要实现无需重启更新数据,核心是定时重复发起请求,同时结合生命周期管理避免无效请求和内存泄漏。
修改后的ViewModel代码
@HiltViewModel class LiveMatchesViewModel @Inject constructor(private val liveMatchesRepository: LiveMatchesRepository): ViewModel() { private var _liveMatchesState = MutableStateFlow<MatchState>(MatchState.Empty) val liveMatchesState: StateFlow<MatchState> = _liveMatchesState init { startPeriodicUpdates() } // 启动定时更新任务 private fun startPeriodicUpdates() { viewModelScope.launch { // 每隔30秒发起一次请求(可根据业务调整间隔) while (true) { fetchLiveMatches() delay(30000) } } } // 抽取单独的请求方法,复用逻辑 private suspend fun fetchLiveMatches() { _liveMatchesState.value = MatchState.Loading try { val liveMatchesResponse = liveMatchesRepository.getLiveMatches() _liveMatchesState.value = MatchState.Success(liveMatchesResponse) } catch (exception: HttpException) { _liveMatchesState.value = MatchState.Error("请求失败,请稍后重试") } catch (exception: IOException) { _liveMatchesState.value = MatchState.Error("无网络连接") } } }
额外优化:界面生命周期感知
在Activity/Fragment中观察StateFlow时,用repeatOnLifecycle确保只有界面活跃时才接收更新,避免后台浪费资源:
// 示例:Fragment中观察数据 lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.liveMatchesState.collect { state -> // 根据状态更新UI(加载中、成功展示数据、错误提示) } } }
二、实时比分应用的高效更新方案(低延迟)
定时轮询实现简单但延迟高,适合小流量场景。如果需要更实时的比分更新,推荐用Server-Sent Events(SSE) 或WebSocket,以下是结合Retrofit的SSE实现:
1. 添加SSE依赖
implementation 'com.squareup.okhttp3:okhttp-sse:4.11.0'
2. 定义SSE接口
interface LiveMatchesApi { @GET("live/matches/sse") fun getLiveMatchesSse(): EventSource }
3. 仓库层实现SSE监听
class LiveMatchesRepository @Inject constructor(private val api: LiveMatchesApi) { fun observeLiveMatches(callback: (Result<List<Match>>) -> Unit): EventSource { val listener = object : EventSourceListener() { // 接收服务器推送的实时数据 override fun onEvent(eventSource: EventSource, id: String?, type: String?, data: String) { val matches = Gson().fromJson(data, Array<Match>::class.java).toList() callback(Result.success(matches)) } // 处理连接失败 override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) { callback(Result.failure(t ?: IOException("SSE连接中断"))) } } return api.getLiveMatchesSse().newEventSource(listener) } }
4. ViewModel中管理SSE连接
@HiltViewModel class LiveMatchesViewModel @Inject constructor(private val liveMatchesRepository: LiveMatchesRepository): ViewModel() { private var _liveMatchesState = MutableStateFlow<MatchState>(MatchState.Empty) val liveMatchesState: StateFlow<MatchState> = _liveMatchesState private var eventSource: EventSource? = null init { startSseUpdates() } private fun startSseUpdates() { eventSource = liveMatchesRepository.observeLiveMatches { result -> viewModelScope.launch { result.fold( onSuccess = { matches -> _liveMatchesState.value = MatchState.Success(matches) }, onFailure = { throwable -> val errorMsg = if (throwable is IOException) "无网络连接" else "实时更新中断" _liveMatchesState.value = MatchState.Error(errorMsg) // 连接失败后10秒自动重连 delay(10000) startSseUpdates() } ) } } } // ViewModel销毁时关闭SSE连接,防止内存泄漏 override fun onCleared() { super.onCleared() eventSource?.cancel() } }
关键注意事项
- 定时轮询的间隔不要过短(推荐15-60秒),避免消耗过多流量和服务器资源。
- 无论用轮询还是SSE,都要处理错误场景,比如网络断开时展示缓存数据或友好提示。
- 务必在ViewModel销毁时停止定时任务或关闭SSE连接,防止内存泄漏。
内容的提问来源于stack exchange,提问作者Mahmoud Nabil
相关产品推荐
相关产品推荐

