谈到 MVVM
架构,不得不祭出官方的架构图,架构图能帮助我们更好地理解,如下所示:
在实践中,根据对架构组件 paging
的使用和理解,我将架构图扩展成下面这样:
有背景颜色的3处是 paging
组件需要多用到的。
MVP
中 V
层和 P
层互相持有对方的引用,在V
层调用 P
层逻辑后,P
层回调V
层的相应方法更新 UI
。
而在 MVVM
中,上层只依赖直接下层,不能跨层持有引用,那 View
层调用 ViewModel
处理数据后,又如何更新自己呢?
答案就在 ViewModel
中的 LiveData
,这是一种可观察的数据类型,在 View
层中观察者 Observer
对需要的数据进行订阅,当数据发生变化后,观察者 Observer
的回调方法 onChanged()
中会收到新的数据,从而可以更新 UI
。
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
架构的理论与实践都已打通!