4. 网络数据源实现:Retrofit接口定义、Response封装、suspend函数与Flow转换

网络数据源是缓存架构中“远端数据”的唯一出口。本章围绕 Retrofit 如何定义接口、如何封装通用 Response、如何通过 suspend 函数获取一次快照以及如何将 suspend 转为 Flow 实现全生命周期观测,拆解完整链路。

4.1 整体流程

客户端请求经过三层转换:API 定义 → 悬浮挂起 → 流化推送。下图展示从 Retrofit 接口到 Flow 输出的核心节点与数据流向。

网络数据源:Retrofit→suspend→Flow 转换流程 Retrofit API @GET("/user/info") Response<T> data class + 状态码 suspend fun 挂起,等待结果 Repository Flow<T> 包装 Flow<T> 上游数据源 💡 核心转换:suspend 一次请求 → Flow 持续观察,统一数据源出口 通常配合 NetworkBoundResource 或 CacheStrategy 做缓存更新

4.2 Retrofit 接口定义

使用 Retrofit 2.6+ 原生支持 suspend 函数。优先在接口方法上添加 @Headers、@Query 或 @Path,并保持返回值类型为 Response<T>,方便手动处理 HTTP 状态码。

// ApiService.kt
interface ApiService {
    @GET("user/profile")
    suspend fun getProfile(
        @Query("userId") userId: String
    ): Response<UserProfileResponse>

    @POST("user/update")
    suspend fun updateProfile(
        @Body request: UpdateRequest
    ): Response<UserProfileResponse>
}
📌 最佳实践: 始终返回 Response<T> 而不是直接返回 T,这样可以在 Repository 层获取 code、headers 以及 errorBody(),避免 Retrofit 自动抛出异常导致 Flow 中断。

4.3 Response 封装

定义一个通用数据类统一承载成功/失败信息。示例包含状态码、错误消息和数据体。

// NetworkResponse.kt
sealed class NetworkResponse<out T> {
    data class Success<T>(val data: T) : NetworkResponse<T>()
    data class Error(val code: Int, val message: String?) : NetworkResponse<Nothing>()
    data class Exception(val throwable: Throwable) : NetworkResponse<Nothing>()
}

借助 Kotlin 扩展函数将 Retrofit 的 Response 转换为自定义封装:

suspend fun <T> safeApiCall(apiCall: suspend () -> Response<T>): NetworkResponse<T> {
    return try {
        val response = apiCall()
        if (response.isSuccessful) {
            val body = response.body()
            if (body != null) NetworkResponse.Success(body)
            else NetworkResponse.Error(response.code(), "Body is null")
        } else {
            NetworkResponse.Error(response.code(), response.errorBody()?.string())
        }
    } catch (e: Exception) {
        NetworkResponse.Exception(e)
    }
}

4.4 suspend 函数与 Flow 转换

Repository 层通过 flow { } 构造器将单次 suspend 调用转为冷流。每次收集时都会执行一次网络请求,适合“网络请求 + 缓存更新”场景。

// UserRepository.kt
class UserRepository(private val api: ApiService) {

    fun fetchProfile(userId: String): Flow<NetworkResponse<UserProfileResponse>> = flow {
        val result = safeApiCall { api.getProfile(userId) }
        emit(result)
    }

    // 带重试和超时控制的增强版本
    fun fetchProfileWithRetry(userId: String): Flow<NetworkResponse<UserProfileResponse>> = flow {
        var attempts = 0
        var lastResult: NetworkResponse<UserProfileResponse>? = null
        while (attempts < 3) {
            val result = safeApiCall { api.getProfile(userId) }
            if (result is NetworkResponse.Success) {
                emit(result)
                return@flow
            }
            lastResult = result
            attempts++
            delay(1000L * attempts) // 指数退避
        }
        emit(lastResult ?: NetworkResponse.Exception(IOException("Max retries reached")))
    }.retry(2) { e ->
        e is IOException && e.message?.contains("timeout") == true
    }
}

4.5 Flow 转换中的异常处理

直接在 flow { } 内使用 try-catch,避免异常泄漏导致 Flow 结束。亦可使用 catch 操作符:

fun fetchProfileSafe(userId: String): Flow<NetworkResponse<UserProfileResponse>> =
    flow {
        val result = safeApiCall { api.getProfile(userId) }
        emit(result)
    }.catch { e ->
        emit(NetworkResponse.Exception(e))
    }
⚠️ 性能注意: 不要在 flow { } 中使用 blocking 调用或同步网络请求。确保 safeApiCall 内只使用 suspend 函数,否则会阻塞协程调度器,引发线程饥饿。

4.6 常见错误与调试

错误场景原因调试方法
Flow 不发射数据flow 构造器未调用 emit() 或提前 return在 emit 前后加日志,检查条件分支
Retrofit 抛出 HttpException直接返回 T 而非 Response<T>改用 Response<T> 捕获状态码
Flow 在异常后“死掉”未使用 catch 或 retry添加 catch 操作符,将异常转为 NetworkResponse.Exception
UI 端无法感知 Loadingflow 只发射最终结果使用 onStart 或 map 发射 Loading 状态

4.7 性能与稳定性优化

  • 超时控制:在 Retrofit 构建器设置 callTimeout、connectTimeout、readTimeout,避免挂起过长。
  • 流去重:使用 distinctUntilChanged() 避免连续相同 Result 导致 UI 重复刷新。
  • Scope 管理:在 ViewModel 中使用 viewModelScope 收集 Flow,确保网络请求能在界面销毁时自动取消。
  • 缓存策略隔离:将网络数据源封装为独立模块,Flow 转换只做“请求→封装→发射”,缓存逻辑由另一层(如 CacheManager)组合。
// Retrofit 超时配置示例
val okHttpClient = OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
🎯 核心结论: Retrofit 接口 + suspend 函数 + sealed class NetworkResponse + flow { } 构成网络数据源的标准四件套。这套组合确保了类型安全、状态可枚举、异常不泄露、自然支持重试与轮询,是 Kotlin Flow 缓存更新案例中网络层的基石。