封装统一的数据来源,对外提供单一可信来源。(DataSource封装API供Repository使用并暴露给外部)
对异常的处理留给ViewModel?
- 封装操作数据的行为(API):文件读写、位置信息、Retrofit、Room等。
- 命名规范:数据类型+来源类型+DataSource。数据类型例如:User、Weather,来源类型例如:Local、Remote、File,组合起来就是:UserLocalDataResource、WeatherRemoteDataResource。不要使用具体实现的技术来命名,不需要知道后期代码也会变动,例如:
UserSharedPreferenceDataSource。
class LoginRemoteResource {
//系统API及大部分三方SDK大都以callback形式返回数据
//可以使用suspendCancellableCoroutine或者callbackFlow转换
suspend fun login(userName: String, password: String) = withContext(Dispatchers.IO) {
RetrofitClient.apiService.login(userName, password)
}
}
- 对外屏蔽了数据来源和内部实现细节,外层不需要了解数据的来源只需要拿走就行,里层可以根据需求修改不会影响外层,两者的分离用可以帮助协同开发。处理业务逻辑和整合多个DataSource并将其统一处理作为单一信源对外暴露。
- 命名规范:数据类型+Repository。(NewsRepository、UserRepository)。
interface INewsRepository {
suspend fun getData(): List<News>
}
class NewsRepositoryImpl(
//DataSource的引用可以通过构造函数传入,也可以做成单例
private val newsRemoteDataSource: NewsRemoteDataSource,
private val newsLocalDataSource: NewsLocalDataSource
) : INewsRepository {
override suspend fun getData(): List<News> {
//添加同步锁,确保在多线程中安全
val mutex = Mutex()
//将最新数据缓存在内存中
var ram: List<News> = emptyList()
//先判断内存中是否有数据,没有的话从本地中读取
if (ram.isEmpty()) {
val local = newsLocalDataSource.getData()
//再判断Local中是否有数据,没有的话从网络读取
if (local.isEmpty()) {
val remote = newsRemoteDataSource.getData()
//将网络中获取的数据保存到内存和本地
mutex.withLock {
newsLocalDataSource.saveData(remote)
}
ram = remote
}
ram = local
}
return mutex.withLock { ram }
}
}
ViewModel、UseCase、Repository、DataSource无论哪一层处理都能保证最终在主线程(UI层)调用安全,采取就近原则,谁生产数据谁就保证安全性,因此选择DataSource。当在 Repository 中需要整合多个数据源,可以再切到子线程中处理。
面向UI的操作 | 这部分的业务会根据生命周期变化而变化,保证主线程调用安全即可。 | viewModelScope、lifecycleScope。 |
面向APP的操作 | 任务执行周期比UI更长,需要在UI销毁后仍可以执行,就需要在协程中传入一个全局的作用域对象。 | CoroutineScope()、withContext()....中传入自定义的上下文对象。 |
面向业务的操作 | 业务在APP终止后仍希望进行。 | WorkManager。 |
一般 | 接口回调 callback | ||
一次性操作 | 挂起函数 suspend | ||
数据流操作 | 分配 Channel | ||
共享 | 构建 Flow | ||
更新 | 历史 SharedFlow | ||
最新 StateFlow |