Android Kotlin 待办事项 APP:Retrofit 网络异常处理与离线缓存

发布时间:2026/9/4 5:38:43
Android Kotlin 待办事项 APP:Retrofit 网络异常处理与离线缓存 在 Retrofit 接入之后网络请求失败、接口超时和用户离线仍然是实际项目中必须处理的问题。本文继续改造待办事项 APP分析 Room本地缓存、网络刷新、错误状态和重试机制的职责划分让页面在有网和无网环境下都能保持可用。一、为什么需要离线缓存如果页面每次打开都直接请求 Retrofit网络较慢时会出现空白页面没有网络时用户甚至无法查看之前保存的待办事项。Room 可以作为本地数据源Retrofit 负责从服务端刷新数据页面始终观察 Room 的数据变化。推荐的数据流如下服务端 API ↓ Retrofit Repository 处理刷新结果 ↓ 写入 Room 数据库 ↓ Flow ViewModel ↓ UiState Activity 渲染页面这种结构的重点是页面不直接依赖网络。即使接口暂时不可用Room 中的旧数据仍然可以显示。二、把 Room 作为主要数据源Repository 可以同时持有 DAO 和 ApiServiceclassTodoRepositoryInjectconstructor(privatevaltodoDao:TodoDao,privatevalapiService:TodoApiService){funobserveTodos():FlowListTodotodoDao.observeAllTodos()suspendfunrefresh():ResultUnitrunCatching{valremoteapiService.getTodos()todoDao.replaceAll(remote.map{it.toEntity()})}}observeTodos()不直接请求网络而是持续观察数据库。refresh()只负责拉取服务端数据并更新数据库。数据库更新后Room 的 Flow 会自动通知 ViewModel页面不需要手动刷新列表。三、设计刷新状态加载状态和错误提示不能只用一个 Boolean 表示。首次进入页面、已有缓存时刷新、刷新失败但仍有旧数据这些情况应该区分开dataclassTodoUiState(valtodos:ListTodoemptyList(),valisInitialLoading:Booleantrue,valisRefreshing:Booleanfalse,valerrorMessage:String?null,vallastSyncTime:Long?null)首次加载时可以显示进度条已有本地数据时只显示较轻量的刷新提示。这样不会因为一次网络请求而遮挡用户已经能够查看的内容。四、启动时先读本地再刷新网络ViewModel 可以让本地数据先显示再启动后台刷新init{viewModelScope.launch{repository.observeTodos().collect{todos-_uiState.update{it.copy(todostodos,isInitialLoadingfalse)}}}refresh()}funrefresh(){if(_uiState.value.isRefreshing)returnviewModelScope.launch{_uiState.update{it.copy(isRefreshingtrue,errorMessagenull)}repository.refresh().onSuccess{_uiState.update{it.copy(isRefreshingfalse,lastSyncTimeSystem.currentTimeMillis())}}.onFailure{error-_uiState.update{it.copy(isRefreshingfalse,errorMessageerror.toUserMessage())}}}}实际项目中要注意协程生命周期避免页面重复创建时启动多个刷新任务。isRefreshing只能减少重复点击若存在多个入口还可以进一步使用请求管理器或统一事件流。五、区分网络错误类型直接显示error.message往往不适合用户。可以根据异常类型转换为简短提示funThrowable.toUserMessage():String{returnwhen(this){isjava.net.UnknownHostException-当前没有网络连接isjava.net.SocketTimeoutException-请求超时请稍后重试else-数据刷新失败请稍后重试}}HTTP 401、403、404 和 500 并不是同一种问题。建议在网络层读取响应状态码再转换为统一的业务异常。认证失效应引导用户重新登录服务器错误可以提供重试按钮数据解析失败则需要检查接口字段变化。六、刷新失败时保留旧数据刷新失败不等于本地数据失效。Repository 不应该在请求开始前删除 Room 中的全部记录也不应在失败时把列表替换成空列表。正确的处理方式是保留旧数据只更新errorMessage和刷新状态。页面可以同时显示列表和错误提示state.errorMessage?.let{message-showSnackbar(message)}adapter.submitList(state.todos)这样用户仍然可以勾选、编辑和删除本地待办。若这些操作还没有实现服务端同步就应该在界面上明确它们当前只影响本地数据避免造成已经上传成功的误解。七、重试机制的边界重试适合处理临时超时或短暂网络波动不适合所有异常。可以采用有限次数和递增等待时间suspendfunTretryRequest(times:Int3,block:suspend()-T):T{varlastError:Throwable?nullrepeat(times){index-try{returnblock()}catch(error:Throwable){lastErrorerrorif(indextimes-1){delay((index1)*1_000L)}}}throwlastError?:IllegalStateException(request failed)}不应对所有 4xx 请求自动重试也不应在用户快速点击时无限发送请求。对于新增、编辑和删除操作还要考虑重复提交问题必要时使用请求 ID 或服务端幂等设计。八、Room 数据库的同步字段如果下一步要实现双向同步单纯的id、title和completed字段还不够。可以增加valsyncState:SyncStateSyncState.SYNCEDvalupdatedAt:LongSystem.currentTimeMillis()valdeleted:Booleanfalse删除操作不一定立即物理删除可以先使用软删除标记等服务端确认后再清理。updatedAt可用于判断较新的版本syncState则能区分待上传、同步成功和同步失败的记录。九、测试重点与开发边界至少需要测试首次启动、已有缓存时刷新、断网启动、请求超时、服务端返回错误、重复点击刷新和本地数据持续显示等场景。Repository 可以注入假的 ApiService使用固定返回值模拟成功与失败不必依赖真实服务器。当前工作区只有文章和示例配图没有完整 Android 源码、接口文档或后端服务因此本文无法确认真实的网络状态监听、错误码、字段结构和运行结果。Room 缓存与 ViewModel 状态属于客户端可独立改造的部分认证、双向同步、冲突解决和离线队列则需要结合服务端协议共同确定。总结离线缓存不是简单地把网络数据保存一份而是要明确数据源、刷新时机、错误状态和同步边界。以 Room 作为页面观察的数据源以 Retrofit 作为刷新渠道可以让待办事项 APP 在弱网或无网环境下仍保持基本可用也为后续登录、双向同步和后台任务留下清晰的扩展位置。文章标签Android Kotlin Retrofit Room 离线缓存 网络异常处理 MVVM APP开发