You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android架构:Repository模式+RxJava,选Flowable还是Single?

设计新闻流App的Repository接口:适配MVVM与View数据订阅

先明确核心:单一数据源(SSOT)的意义

你提到的SSOT在Repository层的核心价值,就是让View层只从一个统一入口获取数据——不管数据是来自网络API、本地缓存还是其他来源,Repository都要把这些细节封装起来,对外暴露一致的数据流。这样View不用关心数据从哪来,只需要专注于展示即可。

具体的接口设计思路

我结合你用的新闻API和MVVM架构,给你一套可落地的方案:

1. 定义Repository接口:暴露可观察的数据流

因为View需要订阅数据更新,接口里的方法应该返回可观察类型(比如Kotlin的Flow,或者Android的LiveData)。这里以Kotlin Flow为例,更适合现代Android开发:

// 先定义一个密封类处理数据状态,方便View区分加载、成功、失败
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
}

interface NewsRepository {
    // 获取指定国家的头条新闻,返回可订阅的Flow
    fun getTopHeadlines(country: String): Flow<Result<List<Article>>>
    // 搜索新闻的方法,同样返回Flow
    fun searchNews(query: String): Flow<Result<List<Article>>>
}

用Result密封类封装状态,能让View层清晰处理不同场景(比如加载时显示进度条,失败时显示错误提示)。

2. 实现Repository:封装多数据源逻辑(SSOT的关键)

Repository的实现类要负责整合网络请求和本地缓存,对外只暴露统一的数据流。比如:

class NewsRepositoryImpl(
    private val newsApi: NewsApi, // 你封装的新闻API请求类
    private val newsDao: NewsDao   // Room数据库的Dao,用于本地缓存
) : NewsRepository {

    override fun getTopHeadlines(country: String): Flow<Result<List<Article>>> = flow {
        // 第一步:先发射本地缓存数据,让View快速显示内容
        emit(Result.Success(newsDao.getTopHeadlines(country)))
        
        try {
            // 第二步:发起网络请求获取最新数据
            val apiResponse = newsApi.getTopHeadlines(country)
            
            if (apiResponse.isSuccessful) {
                apiResponse.body()?.articles?.let { remoteArticles ->
                    // 更新本地缓存
                    newsDao.insertTopHeadlines(remoteArticles, country)
                    // 发射最新的网络数据,View会自动更新
                    emit(Result.Success(remoteArticles))
                } ?: emit(Result.Error("接口返回空数据"))
            } else {
                emit(Result.Error("请求失败:${apiResponse.message()}"))
            }
        } catch (e: Exception) {
            // 网络异常时,发射错误状态,但保留之前的缓存数据
            emit(Result.Error("网络错误:${e.localizedMessage ?: "未知错误"}"))
        }
    }.flowOn(Dispatchers.IO) // 指定数据流在IO线程执行,避免阻塞主线程
}

这里完全体现了SSOT:View只需要订阅getTopHeadlines的Flow,Repository自己处理“先缓存后网络”的逻辑,View无需感知数据源的切换。

3. 在MVVM中连接ViewModel与Repository

ViewModel作为View和Repository的中间层,持有Repository实例,并将数据流转换为View可观察的状态:

class NewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {
    // 内部可变状态,外部只暴露不可变的StateFlow
    private val _topHeadlines = MutableStateFlow<Result<List<Article>>>(Result.Loading)
    val topHeadlines: StateFlow<Result<List<Article>>> = _topHeadlines

    // 触发获取头条新闻的方法
    fun fetchTopHeadlines(country: String = "us") {
        viewModelScope.launch {
            newsRepository.getTopHeadlines(country).collect { result ->
                _topHeadlines.value = result
            }
        }
    }
}

View层(比如Compose组件或者Fragment)只需要订阅topHeadlines这个StateFlow,就能自动收到数据更新,无需手动管理订阅生命周期。

几个关键注意点

  • 单一职责:Repository只负责数据的获取、缓存和分发,不要加入任何UI相关的逻辑(比如Toast提示、Activity跳转)。
  • 状态封装:一定要用类似Result的类封装数据状态,让View层能明确处理加载、成功、失败三种场景。
  • 线程安全:Repository里的数据流处理要放在IO线程,ViewModel用viewModelScope管理协程,确保主线程安全。

内容的提问来源于stack exchange,提问作者Elias

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:04:57