Android架构 - Data Layer

秦承安
2023-12-01

一、职责

封装统一的数据来源,对外提供单一可信来源。(DataSource封装API供Repository使用并暴露给外部)对异常的处理留给ViewModel?

1.1 数据源 DataSource

  • 封装操作数据的行为(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)
    }
}

1.2 仓库 Repository

  • 对外屏蔽了数据来源和内部实现细节,外层不需要了解数据的来源只需要拿走就行,里层可以根据需求修改不会影响外层,两者的分离用可以帮助协同开发。处理业务逻辑和整合多个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。

四、对外暴露API

一般接口回调 callback
一次性操作挂起函数 suspend
数据流操作分配 Channel
共享构建 Flow
更新历史 SharedFlow
最新 StateFlow
 类似资料: