MVVM 架构解析及 Jetpack 架构组件的使用

本文首发于 binaryshao的博客java

MVVM 架构图

谈到 MVVM 架构,不得不祭出官方的架构图,架构图能帮助咱们更好地理解,以下所示: react

在这里插入图片描述
在实践中,根据对架构组件 paging 的使用和理解,我将架构图扩展成下面这样:

在这里插入图片描述
有背景颜色的3处是 paging 组件须要多用到的。

MVVM 和 MVP 的区别

MVPV 层和 P 层互相持有对方的引用,在V 层调用 P 层逻辑后,P 层回调V 层的相应方法更新 UIandroid

而在 MVVM 中,上层只依赖直接下层,不能跨层持有引用,那 View 层调用 ViewModel 处理数据后,又如何更新本身呢?git

答案就在 ViewModel 中的 LiveData,这是一种可观察的数据类型,在 View 层中观察者 Observer 对须要的数据进行订阅,当数据发生变化后,观察者 Observer 的回调方法 onChanged() 中会收到新的数据,从而能够更新 UIgithub

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);
}
复制代码

MVVM 架构解析

整个架构解析以下:服务器

  1. View 层调用 ViewModel 获取数据
  2. ViewModel 调用 Repository 获取数据
  3. Repository 是数据仓库,根据实际业务,再经过 Dao 访问本地数据库或者 Retrofit 访问服务器。
  4. ViewModel 中的 LiveData 类型数据获得更新
  5. View 层的观察者 Observer 的回调方法 onChanged() 中收到新的数据,更新 UI
  6. 若是须要使用 paging 组件,就多了上图中的3处调用

Jetpack 架构组件

JetpackGoogle 为咱们提供的架构组件,对于这些组件,我有如下理解和使用心得:网络

paging
  • 适用于列表页面,能够配置每页加载的数据量和预加载距离
  • 须要使用 PagedListAdapterPagedList
  • 加载下一页的逻辑就在 PagedListAdapter 调用 getItem() 时,这里会调用 PagedListloadAround() 方法
  • 相关参数要求:mEnablePlaceholderstruemPrefetchDistance 大于 0
DataBinding

适用于数据繁杂的页面,能够减小大量 java 代码,在列表页面没必要使用。数据结构

Navigation
  • 适用于能触发两个明确页面之间跳转的操做
  • 不适用不能肯定从哪一个页面来或去往哪一个页面的操做
ViewModel
  • 管理 ActivityFragment 的数据
  • 建立于ActivityFragment 内,页面被销毁前,ViewModel 会一直存在
  • 若是因配置变化致使页面销毁,ViewModel 不会销毁,它会被用于新的页面实例
  • 通常在 ViewModel 中配合 LiveData 使用
  • 通常用 ViewModelProviders 获取 ViewModelProvider,再用它的 get() 方法获取 ViewModel
  • get() 方法中会调用 Factorycreate() 方法建立 ViewModel
  • 建立的 ViewModel 被存入 ViewModelStoreHashMap 中,以便下次直接获取,不用再建立
  • ViewModelStore 是经过 ActivityFragment 获取的
  • 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 销毁时,会调用 ViewModelStoreclear() 方法:架构

public final void clear() {
      for (ViewModel vm : mMap.values()) {
          vm.clear();
      }
      mMap.clear();
  }
复制代码

这里会调用 ViewModelclear() 方法,其中又会调用 onCleared()方法,咱们能够在这个方法中取消订阅,以防内存泄漏。

MVVM 案例实战

下面根据个人开源项目 WanAndroid-MVVM 进一步讲解 MVVM 架构的运用,如下全部代码均来自于该项目。

不一样的 UI 状态

首先对于数据加载,通常有【加载中、加载成功、加载失败】这3种状态, UI 上须要有对应的变化。

不一样于 MVPP 层回调 V 层的相应方法更新 UI 的方式, MVVMView 层只能经过观察数据的方式来更新 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() {
    }
}
复制代码

这个自定义的观察者,主要干了三件事:

  1. 在网络请求前,判断网络是否链接,没有链接就调用错误处理方法。
  2. 根据 errorCode 的值判断业务处理是否成功,失败就调用错误处理方法。
  3. 在错误处理方法中向用户展现错误。

加入 paging 组件

以前提到过,若是加入了 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>

}
复制代码

这里的 sourceLivaDataBaseItemKeyedDataSource 做为值,而 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 架构的理论与实践都已打通!

相关文章
相关标签/搜索