存储模式
在开发移动应用程序的许多情况下,我们还需要提供对数据的脱机访问。想象一下,我们正在开发一个新闻阅读类APP,并且您还希望您的用户可以在他们乘坐飞机时或者他们在没有互联网访问的任何区域访问数据。在某些情况下,您希望显示存储的数据,同时从API加载新数据。如果从应用程序开始就没有在体系结构中实现或考虑这一点,这可能会成为一项复杂的任务,并且可能需要进行大量更改,这也会影响UI层,为新的潜在错误留出空间在你的代码。存储模式正好解决了这个问题,我们从应用程序的开头实现了它。
为什么使用数据存储模式?
- 将应用程序与数据源分离
- 提供来自多个源(DB,API)的数据
- 隔离数据层
- 集中,一致的数据访问方式
- 通过单元测试可测试业务逻辑
- 很容易增加新数据源
如何使用数据库
在Android中,我们有很多操作数据库的库:
- 原生SQLite(太多的样板代码)
- Realm(太复杂了,我们不需要它的大部分功能)
- GreenDao(非常好用的ORM)
- Room(Google官方支持的新的ORM框架)
这个例子里面,我准备使用Room来演示。
Room 的一些特点
-
编译时 sql 语句检查。相信大家都有过 app 跑起来,执行到 db 语句的时候 crash,检查之后发现原来是 sql 语句少了一个
)
或者其它符号之类的经历。Room 会在编译阶段检查你的 DAO 中的 sql 语句,如果写错了(包括 sql 语法错误跟表名、字段名等等错误),会直接编译失败并提醒你哪里不对。 - sql 查询直接关联到 Java 对象。这个应该不用详细解释了,虽然很多第三方 db 库早已经实现。
- 耗时操作主动要求异步处理。这一点还是挺值得注意的,Room 会在执行 db 操作时判断是不是在 UI 线程,比如当你需要插入一条记录到数据库时,Room 会让你放到异步线程去做,否则会直接 crash 掉 app 来告诉你不这样做容易阻塞 UI 线程。虽说死相难看了点(个人觉得打个警告不就完了么?),但对于开发者开发出高质量的应用还是有帮助的。
- 基于注解编译时自动生成代码。这个应该算是 Room 工作原理的核心所在了,你要写的代码之所以这么少,说白了还不是因为 Google 给你写好了很多?希望以后有时间能写一篇源码分析出来,那个时候再讲吧。
- API 设计符合 Sql 标准。方便扩展进行各种 db 操作。
Room的使用
首先创建User的数据类,使用@Entity注解建表。
@Entity(tableName = "users")
data class User(
@PrimaryKey
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "name")
val name: String
)
现在我们要声明数据库和UserDao。
@DataBase(entities = [User::class], version = 1)
abstract class AppDataBase: RoomDataBase(){
abstract fun userDao():UserDao
}
@Dao
interface UserDao{
@Query("SELCET * FROM users")
fun getUsers(): Single<List<User>>
@Insert(onConfilct = OnConflictStrategy.REPLACE)
fun insert(user: User)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(users: List<User>)
}
接下来,我们使用Room来创建数据库(通常在项目中会使用Dagger依赖注入的方式)。
val appDataBase = Room.databaseBuilder(applicationContext,
AppDataBase::class.java,"mvvm-database").builde()
val userDao = appDatabase.userDao()
可以看得出来,使用Room相比其它的数据库框架还是非常非常简单的。现在我们可以对我们的UserRepository进行改造了,加入DataBase数据源。
class UserRepository(val userApi: UserApi, val userDao: UserDao){
fun getUsers(): Observable<List<User>>{
return Observable.concatArray(
getUsersFromDb(),
getUsersFromApi()
)
}
fun getUsersFromDb(): Observabel<List<User>>{
return userDao.getUsers().filter { it.isNotEmpty() }
.toObservable()
.doOnNext{
//获取数据成功
}
}
fun getUserFromApi(): Observable<List<User>>{
return userApi.getUsers()
.doOnNext{
...
storeUsersInDb(it)
}
}
fun storeUserInDb(users: List<User>){
Observable.fromCallable{ userDao.insertAll(users) }
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe {
//保存成功
}
}
}
我们使用Observable.concatArray()并传递我们的2个源,即DB和API Observables。这将首先向用户提供来自DB的数据,其次是来自API的数据。当从API接收数据时,我们还在doOnNext()内部触发异步操作以将数据存储在我们的DB中。
可以看到,我们非常简单的实现了一系列的数据存储操作,并且完全与View层脱离了关系。