Android真響應式架構——資料流動性
前言
Android真響應式架構系列文章:
Android真響應式架構——MvRx Epoxy——RecyclerView的絕佳助手 Android真響應式架構——Model層設計 Android真響應式架構——資料流動性
上篇文章介紹了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
去替代原本的網路資料流,我們可以構建資料之間的廣泛聯絡,使得資料流變成真正的資料流,而不僅僅是回撥的一種變形。