前言
Android真响应式架构系列文章:
Android真响应式架构——MvRx
Epoxy——RecyclerView的绝佳助手
Android真响应式架构——Model层设计
Android真响应式架构——数据流动性
Android真响应式架构——Epoxy的使用
Android真响应式架构——MvRx和Epoxy的结合
上篇文章介绍了Model层的设计,其本质是数据流的设计,即以RxJava的方式包装Model层的数据,然后进行合理的数据分层,以实现对数据流的分层管控。但是,对于最常用的网络数据而言(基于HTTP的网络请求),它只是单一的数据流,也就是说,一次网络请求只返回一组数据,然后数据流就切断了(onComplete被调用),如果数据发生了变化,需要再次发送网络请求。因此,有人建议应该使用Single
来表示网络请求:
/**
* Retrofit接口
*/
interface UserApi {
/**
* 获取用户信息
* 使用 Observable表示网络数据流
*/
@GET
fun getUserInfo(): Observable<UserInfo>
/**
* 获取用户信息
* 使用 Single表示网络数据流
*/
@GET
fun getUserInfo(): Single<UserInfo>
}
这种建议有它的合理性,毕竟即使使用Observable
,onNext
也只会调用一次,根本不会再有Next。这篇文章就是要介绍,如何让数据流动起来,让网络数据流真的有Next数据,而不是无脑地再次发送网络请求。
1. 数据流动性需求是广泛存在的
先看几张图,它们描述了一种常见的设计:
需求是一目了然的,消息的阅读状态决定了首页以及个人信息页中的显示状态,如果是你,你会怎么办?这是我面试中常问的一个问题。我听到最多的答案是,EventBus,或者广播。这些答案都体现了一种命令式更新的思想,发出一个命令,通知哪个哪个你该更新了。对于少数几个界面而言,这种方案也未尝不可。但是这种多个界面数据状态的联系在应用中是普遍存在的,满天飞的Event不是一个好的选择。
回头看看我们的需求,总结一下就是,数据可能因为用户的操作而发生变化,而这种数据变化应该体现在多个界面上。更抽象的说就是,数据是流动的,界面是响应式的。数据的流动性还是应该交由数据层解决。
2. 构建数据的联系
无论是使用Observable
还是使用Single
来表示网络数据流,网络数据都是一次性的,成功或者失败,然后数据流就会被切断。但是,有些时候数据的更新并不需要依赖于网络。例如,上面的例子中,假设我们清空了所有消息,只要这个网络请求成功了,我们就可以将用户的未读消息数设置为0。
再举个更加常见的例子
用户信息设置在各个应用中都非常常见。每一项设置都可能影响其它界面的显示状态,我们想让用户数据流动起来。
/**
* Retrofit接口
*/
interface UserApi {
/**
* 获取用户信息
*/
@GET
fun getUserInfo(): Observable<StatusSuccess<UserInfo>>
/**
* 设置用户信息
*/
@FormUrlEncoded
@POST
fun setUserInfo(@Field("nickname") nickname: String?,
@Field("sex") gender: Int?,
@Field("grade") gradeID: Int?,
@Field("area") areaID: Int?,
@Field("school") schoolID: Int?): Observable<StatusSuccess<String>>
}
/**
* 数据中间层
* 对后台一个接口进行了拆分,分成若干个更加清晰明了的接口
*/
interface UserService {
fun getUserInfo(): Observable<UserInfo>
fun setNickname(nickname: String): Observable<String>
fun setGender(gender: Int): Observable<String>
fun setGrade(gradeID: Int, name: String): Observable<String>
fun setArea(areaID: Int, name: String): Observable<String>
fun setSchool(schoolID: Int, name: String): Observable<String>
}
/**
* 数据中间层的实现类
*/
@Singleton
class UserClient @Inject constructor(
private val userApi: UserApi
) : UserService {
//unwrapData方法在上一篇文章中提到过,用于获取想要的数据
override fun getUserInfo(): Observable<UserInfo> =
userApi.getUserInfo().map(unwrapData())
override fun setNickname(nickname: String): Observable<String> =
userApi.setUserInfo(nickname, null, null, null, null).map(unwrapData())
override fun setGender(gender: Int): Observable<String> =
userApi.setUserInfo(null, gender, null, null, null).map(unwrapData())
override fun setGrade(gradeID: Int, name: String): Observable<String> =
userApi.setUserInfo(null, null, gradeID, null, null).map(unwrapData())
override fun setArea(areaID: Int, name: String): Observable<String> =
userApi.setUserInfo(null, null, null, areaID, null).map(unwrapData())
override fun setSchool(schoolID: Int, name: String): Observable<String> =
userApi.setUserInfo(null, null, null, null, schoolID).map(unwrapData())
}
/**
* 数据仓库
*/
@Singleton
class UserRepo @Inject constructor(
private val userClient: UserClient
) : UserService by userClient {
//保存上次的用户数据
private lateinit var userInfo: UserInfo
//创建 Observable<UserInfo>,可以多次发射 UserInfo数据
private lateinit var userInfoEmitter: ObservableEmitter<UserInfo>
private val userInfoObservable: Observable<UserInfo> by lazy {
Observable.create<UserInfo> {
userInfoEmitter = it
}
}
//使用创建的 userInfoObservable,但是以网络数据userClient.getUserInfo()开始
//这样每次调用这个方法都会请求最新的网络数据,但又不会在网络请求结束时调用onComplete导致数据流被切断
override fun getUserInfo(): Observable<UserInfo> =
userInfoObservable.startWith(userClient.getUserInfo())
.doOnNext {
userInfo = it
}
override fun setNickname(nickname: String): Observable<String> =
userClient.setNickname(nickname)
.doOnComplete {
//设置了用户昵称,做出对应更新
if (::userInfoEmitter.isInitialized &&
::userInfo.isInitialized &&
!userInfoEmitter.isDisposed
) {
userInfoEmitter.onNext(userInfo.copy(nickname = nickname))
}
}
//其它方法都是类似的...
}
注释中已经讲得很清楚了,关键就是用自定义的userInfoObservable
代替原本的网络数据流,这样可以多次调用onNext
方法,使用户数据得到对应更新。以这种方式,在数据仓库Repository中建立数据之间的联系,既体现出数据之间的联系性,又避免了满天飞的Event事件,还可以避免以startActivityForResult
的方式建立的界面之间的联系。尘归尘,土归土,数据之间的联系还是应该在数据层去解决。
数据库ORM一般可以将数据的变动反应到数据库对外提供的Observable中,不需要我们额外处理。
总结
网络数据通常都是一次性的,结束之后数据流就会被切断,但是数据的流动性不应该局限于此,通过创建我们自己的Observable
去替代原本的网络数据流,我们可以构建数据之间的广泛联系,使得数据流变成真正的数据流,而不仅仅是回调的一种变形。