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

栏目: IOS · Android · 发布时间: 7年前

内容简介:谈到的使用和理解,我将架构图扩展成下面这样:组件需要多用到的。

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

MVVM 架构解析及 Jetpack 架构组件的使用
在实践中,根据对架构组件 paging

的使用和理解,我将架构图扩展成下面这样:

MVVM 架构解析及 Jetpack 架构组件的使用
有背景颜色的3处是 paging

组件需要多用到的。

MVVM 和 MVP 的区别

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

MVVM 架构解析

整个架构解析如下:

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

Jetpack 架构组件

Jetpack 是 Google 为我们提供的架构组件,对于这些组件,我有以下理解和使用心得:

paging

  • 适用于列表页面,可以配置每页加载的数据量和预加载距离
  • 需要使用 PagedListAdapter 和 PagedList
  • 加载下一页的逻辑就在 PagedListAdapter 调用 getItem() 时,这里会调用 PagedList 的 loadAround() 方法
  • 相关参数要求: mEnablePlaceholders 为 true 或 mPrefetchDistance 大于 0

DataBinding

适用于数据繁杂的页面,可以减少大量 java 代码,在列表页面不必使用。

Navigation

  • 适用于能触发两个明确页面之间跳转的操作
  • 不适用不能确定从哪个页面来或去往哪个页面的操作

ViewModel

  • 管理 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() 方法,我们可以在这个方法中取消订阅,以防内存泄漏。

MVVM 案例实战

下面根据我的开源项目 WanAndroid-MVVM 进一步讲解 MVVM 架构的运用,以下所有代码均来自于该项目。

不同的 UI 状态

首先对于数据加载,一般有【加载中、加载成功、加载失败】这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 组件,架构流程略微不同。

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 架构的理论与实践都已打通!


以上所述就是小编给大家介绍的《MVVM 架构解析及 Jetpack 架构组件的使用》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

密码学概论(中文版)

密码学概论(中文版)

wade trappe、lawrence C.washington / 特拉普 / 人民邮电出版社 / 2004-6-1 / 38.00

本书全面讲解了密码学基本知识以及相关的基础数学理论,介绍了椭圆曲线、AES和量子密码体制等密码学前沿知识,详细地阐述了数字签名、数字现金等应用问题。另外,书中每章均给出了相应的习题,在附录中给出了相关Mathematica、Maple和 MATLAB实例。 本书可供高等院校就用数学、通信和计算机等专业用作密码学、通信安全和网络安全等课程的教材或参考书,也可供信息安全系统设计开发人......一起来看看 《密码学概论(中文版)》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具