本文首發於 binaryshao的博客java
談到 MVVM
架構,不得不祭出官方的架構圖,架構圖能幫助咱們更好地理解,以下所示: react
paging
的使用和理解,我將架構圖擴展成下面這樣:
有背景顏色的3處是
paging
組件須要多用到的。
MVP
中 V
層和 P
層互相持有對方的引用,在V
層調用 P
層邏輯後,P
層回調V
層的相應方法更新 UI
。android
而在 MVVM
中,上層只依賴直接下層,不能跨層持有引用,那 View
層調用 ViewModel
處理數據後,又如何更新本身呢?git
答案就在 ViewModel
中的 LiveData
,這是一種可觀察的數據類型,在 View
層中觀察者 Observer
對須要的數據進行訂閱,當數據發生變化後,觀察者 Observer
的回調方法 onChanged()
中會收到新的數據,從而能夠更新 UI
。github
LiveData
的相關代碼以下:數據庫
//package androidx.lifecycle.LiveData;
……
……
……
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
@SuppressWarnings("WeakerAccess") /* synthetic access */
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
複製代碼
整個架構解析以下:服務器
View
層調用 ViewModel
獲取數據ViewModel
調用 Repository
獲取數據Repository
是數據倉庫,根據實際業務,再經過 Dao
訪問本地數據庫或者 Retrofit
訪問服務器。ViewModel
中的 LiveData
類型數據獲得更新View
層的觀察者 Observer
的回調方法 onChanged()
中收到新的數據,更新 UI
。paging
組件,就多了上圖中的3處調用Jetpack
是 Google
爲咱們提供的架構組件,對於這些組件,我有如下理解和使用心得:網絡
PagedListAdapter
和 PagedList
PagedListAdapter
調用 getItem()
時,這裏會調用 PagedList
的 loadAround()
方法mEnablePlaceholders
爲 true
或 mPrefetchDistance
大於 0適用於數據繁雜的頁面,能夠減小大量 java
代碼,在列表頁面沒必要使用。數據結構
Activity
或 Fragment
的數據Activity
或 Fragment
內,頁面被銷燬前,ViewModel
會一直存在ViewModel
不會銷燬,它會被用於新的頁面實例ViewModel
中配合 LiveData
使用ViewModelProviders
獲取 ViewModelProvider
,再用它的 get()
方法獲取 ViewModel
get()
方法中會調用 Factory
的 create()
方法建立 ViewModel
ViewModel
被存入 ViewModelStore
的HashMap
中,以便下次直接獲取,不用再建立ViewModelStore
是經過 Activity
或 Fragment
獲取的ComponentActivity
的構造函數中有這麼一段代碼getLifecycle().addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
});
複製代碼
可見當不是配置變化致使 Activity
銷燬時,會調用 ViewModelStore
的 clear()
方法:架構
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.clear();
}
mMap.clear();
}
複製代碼
這裏會調用 ViewModel
的 clear()
方法,其中又會調用 onCleared()
方法,咱們能夠在這個方法中取消訂閱,以防內存泄漏。
下面根據個人開源項目 WanAndroid-MVVM 進一步講解 MVVM
架構的運用,如下全部代碼均來自於該項目。
首先對於數據加載,通常有【加載中、加載成功、加載失敗】這3種狀態, UI
上須要有對應的變化。
不一樣於 MVP
中 P
層回調 V
層的相應方法更新 UI
的方式, MVVM
中 View
層只能經過觀察數據的方式來更新 UI
。
因此須要一種數據結構來表示不一樣的數據加載狀態,並在 View
層對其進行觀察和響應,定義這種數據結構以下:
package com.sbingo.wanandroid_mvvm.base
/** * Author: Sbingo666 * Date: 2019/4/12 */
enum class Status {
LOADING,
SUCCESS,
ERROR,
}
data class RequestState<out T>(val status: Status, val data: T?, val message: String? = null) {
companion object {
fun <T> loading(data: T? = null) = RequestState(Status.LOADING, data)
fun <T> success(data: T? = null) = RequestState(Status.SUCCESS, data)
fun <T> error(msg: String? = null, data: T? = null) = RequestState(Status.ERROR, data, msg)
}
fun isLoading(): Boolean = status == Status.LOADING
fun isSuccess(): Boolean = status == Status.SUCCESS
fun isError(): Boolean = status == Status.ERROR
}
複製代碼
能夠看到,RequestState
對應了3種數據加載狀態,接着看它的具體使用:
package com.sbingo.wanandroid_mvvm.repository
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.sbingo.wanandroid_mvvm.base.RequestState
import com.sbingo.wanandroid_mvvm.data.http.HttpManager
import com.sbingo.wanandroid_mvvm.model.Chapter
import com.sbingo.wanandroid_mvvm.utils.asyncSubscribe
/** * Author: Sbingo666 * Date: 2019/4/22 */
class WeChatRepository(private val httpManager: HttpManager) {
fun getWXChapters(): LiveData<RequestState<List<Chapter>>> {
val liveData = MutableLiveData<RequestState<List<Chapter>>>()
//數據加載中
liveData.value = RequestState.loading()
httpManager.wanApi.getWXChapters()
.asyncSubscribe({
//數據加載成功
liveData.postValue(RequestState.success(it.data))
}, {
//數據加載失敗
liveData.postValue(RequestState.error(it.message))
})
return liveData
}
}
複製代碼
這裏將 RequestState
做爲 LiveData
的泛型參數,這樣 View
層就能夠對這個 LiveData
進行觀察了。
爲了簡化代碼,統一處理重複邏輯,我將觀察代碼寫入了 base
中:
protected fun <T> handleData(liveData: LiveData<RequestState<T>>, action: (T) -> Unit) =
liveData.observe(this, Observer { result ->
if (result.isLoading()) {
showLoading()
} else if (result?.data != null && result.isSuccess()) {
finishLoading()
action(result.data)
} else {
finishLoading()
}
})
fun showLoading() {
}
fun finishLoading() {
}
複製代碼
根據本身的業務需求,方便地實現 showLoading()
和 finishLoading()
的邏輯,數據處理就在每一個頁面傳入的 action
中。
到這裏,完整的數據加載顯示流程就走通了!!!
本項目中使用了 RxJava2
來異步加載數據,調用的代碼很簡單。
若是對線程切換的原理感興趣,能夠看我以前的一篇文章:【源碼分析】RxJava 1.2.2 實現簡單事件流的原理
但每一個調用的地方都要異步切換也挺麻煩的,所以我對 Observable
作了一個擴展,以下:
package com.sbingo.wanandroid_mvvm.utils
import com.sbingo.wanandroid_mvvm.data.http.RxHttpObserver
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
/** * Author: Sbingo666 * Date: 2019/4/23 */
fun <T> Observable<T>.async(): Observable<T> {
return this.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
fun <T> Observable<T>.asyncSubscribe(onNext: (T) -> Unit, onError: (Throwable) -> Unit) {
this.async()
.subscribe(object : RxHttpObserver<T>() {
override fun onNext(it: T) {
super.onNext(it)
onNext(it)
}
override fun onError(e: Throwable) {
super.onError(e)
onError(e)
}
})
}
複製代碼
這兩個方法均可以使用,具體就看是否想用 RxHttpObserver
這個自定義的觀察者咯。
若是使用 asyncSubscribe()
方法,調用方只需傳入數據加載成功和失敗的邏輯,很是簡單,就像這樣:
httpManager.wanApi.getWXChapters()
.asyncSubscribe({
liveData.postValue(RequestState.success(it.data))
}, {
liveData.postValue(RequestState.error(it.message))
})
複製代碼
剛纔說到自定義的觀察者 RxHttpObserver
,這又是啥呢?
package com.sbingo.wanandroid_mvvm.data.http
import com.sbingo.wanandroid_mvvm.R
import com.sbingo.wanandroid_mvvm.WanApplication
import com.sbingo.wanandroid_mvvm.utils.ExecutorUtils
import com.sbingo.wanandroid_mvvm.utils.NetUtils
import com.sbingo.wanandroid_mvvm.utils.ToastUtils
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
abstract class RxHttpObserver<T> : Observer<T> {
override fun onSubscribe(d: Disposable) {
if (!NetUtils.isConnected(WanApplication.instance)) {
onError(RuntimeException(WanApplication.instance.getString(R.string.network_error)))
}
}
override fun onError(e: Throwable) {
e.message?.let {
ExecutorUtils.main_thread(Runnable { ToastUtils.show(it) })
}
}
override fun onNext(it: T) {
//業務失敗
val result = it as? HttpResponse<*>
if (result?.errorCode != 0) {
onError(
RuntimeException(
if (result?.errorMsg.isNullOrBlank())
WanApplication.instance.getString(R.string.business_error)
else {
result?.errorMsg
}
)
)
}
}
override fun onComplete() {
}
}
複製代碼
這個自定義的觀察者,主要乾了三件事:
errorCode
的值判斷業務處理是否成功,失敗就調用錯誤處理方法。以前提到過,若是加入了 paging
組件,架構流程略微不一樣。
paging
組件主要用於列表頁面,根據列表頁面的特性,我對其進行了一些封裝,主要封裝邏輯以下:
package com.sbingo.wanandroid_mvvm.base.paging
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
/** * Author: Sbingo666 * Date: 2019/4/12 */
open class BasePagingViewModel<T>(repository: BasePagingRepository<T>) : ViewModel() {
private val pageSize = MutableLiveData<Int>()
private val repoResult = Transformations.map(pageSize) {
repository.getData(it)
}
val pagedList = Transformations.switchMap(repoResult) { it.pagedList }
val networkState = Transformations.switchMap(repoResult) { it.networkState }
val refreshState = Transformations.switchMap(repoResult) { it.refreshState }
fun refresh() {
repoResult.value?.refresh?.invoke()
}
fun setPageSize(newSize: Int = 10): Boolean {
if (pageSize.value == newSize)
return false
pageSize.value = newSize
return true
}
fun retry() {
repoResult.value?.retry?.invoke()
}
}
複製代碼
BasePagingViewModel
中的邏輯很好理解,repoResult
根據 pageSize
變化,其餘數據又根據repoResult
變化,最後在 View
層對這些數據進行觀察就能夠了。
package com.sbingo.wanandroid_mvvm.base.paging
import androidx.lifecycle.Transformations
import androidx.paging.Config
import androidx.paging.toLiveData
/** * Author: Sbingo666 * Date: 2019/4/12 */
abstract class BasePagingRepository<T> {
fun getData(pageSize: Int): Listing<T> {
val sourceFactory = createDataBaseFactory()
val pagedList = sourceFactory.toLiveData(
config = Config(
pageSize = pageSize,
enablePlaceholders = false,
initialLoadSizeHint = pageSize * 2
)
)
val refreshState = Transformations.switchMap(sourceFactory.sourceLivaData) { it.refreshStatus }
val networkStatus = Transformations.switchMap(sourceFactory.sourceLivaData) { it.networkStatus }
return Listing(
pagedList,
networkStatus,
refreshState,
refresh = {
sourceFactory.sourceLivaData.value?.invalidate()
},
retry = {
sourceFactory.sourceLivaData.value?.retryFailed()
}
)
}
abstract fun createDataBaseFactory(): BaseDataSourceFactory<T>
}
複製代碼
BasePagingRepository
中對 PagedList
配置了每頁數據量大小,初始加載量等參數,最後包裝成數據結構 Listing
返回,這種結構以下:
package com.sbingo.wanandroid_mvvm.base.paging
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import com.sbingo.wanandroid_mvvm.base.RequestState
/** * Author: Sbingo666 * Date: 2019/4/12 */
data class Listing<T>(
//數據
val pagedList: LiveData<PagedList<T>>,
//上拉加載更多狀態
val networkState: LiveData<RequestState<String>>,
//下拉刷新狀態
val refreshState: LiveData<RequestState<String>>,
//刷新邏輯
val refresh: () -> Unit,
//重試邏輯,刷新或加載更多
val retry: () -> Unit
)
複製代碼
而數據源來自 BaseDataSourceFactory
:
package com.sbingo.wanandroid_mvvm.base.paging
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
/** * Author: Sbingo666 * Date: 2019/4/12 */
abstract class BaseDataSourceFactory<T> : DataSource.Factory<Int,T>() {
val sourceLivaData = MutableLiveData<BaseItemKeyedDataSource<T>>()
override fun create(): BaseItemKeyedDataSource<T> {
val dataSource: BaseItemKeyedDataSource<T> = createDataSource()
sourceLivaData.postValue(dataSource)
return dataSource
}
abstract fun createDataSource(): BaseItemKeyedDataSource<T>
}
複製代碼
這裏的 sourceLivaData
將 BaseItemKeyedDataSource
做爲值,而 BaseItemKeyedDataSource
纔是真正獲取數據的地方:
package com.sbingo.wanandroid_mvvm.base.paging
import androidx.lifecycle.MutableLiveData
import androidx.paging.ItemKeyedDataSource
import com.sbingo.wanandroid_mvvm.base.RequestState
import com.sbingo.wanandroid_mvvm.utils.ExecutorUtils
/** * Author: Sbingo666 * Date: 2019/4/12 */
abstract class BaseItemKeyedDataSource<T> : ItemKeyedDataSource<Int, T>() {
private var retry: (() -> Any)? = null
private var retryExecutor = ExecutorUtils.NETWORK_IO
val networkStatus by lazy {
MutableLiveData<RequestState<String>>()
}
val refreshStatus by lazy {
MutableLiveData<RequestState<String>>()
}
fun retryFailed() {
val preRetry = retry
retry = null
preRetry.let {
retryExecutor.execute {
it?.invoke()
}
}
}
//初始加載(包括刷新)時,系統回調此方法
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<T>) {
refreshStatus.postValue(RequestState.loading())
onLoadInitial(params, callback)
}
//加載更多時,系統回調此方法
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<T>) {
networkStatus.postValue(RequestState.loading())
onLoadAfter(params, callback)
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<T>) {
}
fun refreshSuccess() {
refreshStatus.postValue(RequestState.success())
retry = null
}
fun networkSuccess() {
retry = null
networkStatus.postValue(RequestState.success())
}
fun networkFailed(msg: String?, params: LoadParams<Int>, callback: LoadCallback<T>) {
networkStatus.postValue(RequestState.error(msg))
retry = {
loadAfter(params, callback)
}
}
fun refreshFailed(msg: String?, params: LoadInitialParams<Int>, callback: LoadInitialCallback<T>) {
refreshStatus.postValue(RequestState.error(msg))
retry = {
loadInitial(params, callback)
}
}
override fun getKey(item: T) = setKey(item)
abstract fun setKey(item: T): Int
abstract fun onLoadAfter(params: LoadParams<Int>, callback: LoadCallback<T>)
abstract fun onLoadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<T>)
}
複製代碼
子類只須要複寫父類的 onLoadInitial()
和 onLoadAfter()
方法就能執行刷新和加載更多的邏輯了。
這裏實現了【重試】的邏輯和【加載中、加載成功、加載失敗】這3種狀態,這3種狀態使用了以前提到的數據結構 RequestState
,不過加載成功後數據並不會在這裏的 RequestState
中,這裏的 RequestState
只表示加載狀態。那數據怎麼更新呢?
咱們來看一個 BaseItemKeyedDataSource
的子類吧:
package com.sbingo.wanandroid_mvvm.paging.source
import com.sbingo.wanandroid_mvvm.base.paging.BaseItemKeyedDataSource
import com.sbingo.wanandroid_mvvm.data.http.HttpManager
import com.sbingo.wanandroid_mvvm.model.Article
import com.sbingo.wanandroid_mvvm.utils.asyncSubscribe
/** * Author: Sbingo666 * Date: 2019/4/23 */
class WXDataSource(private val httpManager: HttpManager, private val wxId: Int) : BaseItemKeyedDataSource<Article>() {
var pageNo = 1
override fun setKey(item: Article) = item.id
override fun onLoadAfter(params: LoadParams<Int>, callback: LoadCallback<Article>) {
httpManager.wanApi.getWXArticles(wxId, pageNo)
.asyncSubscribe({
pageNo += 1
networkSuccess()
callback.onResult(it.data?.datas!!)
}, {
networkFailed(it.message, params, callback)
})
}
override fun onLoadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Article>) {
httpManager.wanApi.getWXArticles(wxId, pageNo)
.asyncSubscribe({
pageNo += 1
refreshSuccess()
callback.onResult(it.data?.datas!!)
}, {
refreshFailed(it.message, params, callback)
})
}
}
複製代碼
能夠看到和以前 WeChatRepository
中相似,數據也是從服務器上獲取的,只不過獲取的數據是經過 callback.onResult()
方法返回給 View
層的。
在 View
層這邊,列表的【重試】按鈕是封裝在 BasePagingAdapter
中的, 根據觀察到的 networkState
,動態設置按鈕的顯示與隱藏,相關代碼以下:
private fun hasFooter() =
if (requestState == null)
false
else {
!requestState?.isSuccess()!!
}
override fun getItemViewType(position: Int): Int {
return if (hasFooter() && position == itemCount - 1) {
TYPE_FOOTER
} else {
TYPE_ITEM
}
}
override fun getItemCount(): Int {
return super.getItemCount() + if (hasFooter()) 1 else 0
}
fun setRequestState(newRequestState: RequestState<Any>) {
val previousState = this.requestState
val hadExtraRow = hasFooter()
this.requestState = newRequestState
val hasExtraRow = hasFooter()
if (hadExtraRow != hasExtraRow) {
if (hadExtraRow) {
notifyItemRemoved(super.getItemCount())
} else {
notifyItemInserted(super.getItemCount())
}
} else if (hasExtraRow && previousState != newRequestState) {
notifyItemChanged(itemCount - 1)
}
}
複製代碼
根據這些封裝類,在業務中實現它們的子類,就能輕鬆使用 paging
組件啦!!!
到這裏,MVVM
架構的理論與實踐都已打通!