Jetpack using MVVM architecture analysis and architectural components

Disclaimer: This article is a blogger original article, shall not be reproduced without the bloggers allowed. https://blog.csdn.net/recordGrowth/article/details/90377318

MVVM Chart

It comes to MVVMarchitecture, have resorted to the official organization chart, architecture diagram can help us better understand, as follows:
Here Insert Picture Description
In practice, according to the architectural components pagingof use and understand, I'll Chart extended to the following:

Here Insert Picture Description
At the background color of 3 pagingcomponents it needs more used.

MVVM and MVP of difference

MVPIn Vlayers and Players holding each other's references in the Vlayer calls Pthe logic layer, Player callback Vcorresponding method layer of updates UI.

In MVVMthe upper depends only on the immediately lower, can not hold a reference to cross-layer, that Viewlayer calls ViewModelthe post-processing data, and how to update yourself?

The answer lies in ViewModelthe LiveData, which is a type of observable data, the Viewobserver layers Observerof data needed to subscribe, when the data changes, the observer Observercallback method onChanged()will receive new data, which can be updated UI.

LiveData Related code is as follows:

//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 framework to resolve

The whole structure interpreted as follows:

  1. ViewLayer calls ViewModelto get data
  2. ViewModelCalls Repositoryto get data
  3. RepositoryIt is a data warehouse, based on actual business, and through Daoaccess to local databases or Retrofitaccess the server.
  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()
    }
}

BasePagingViewModelLogic is well understood that, repoResultin accordance with pageSizethe change, and other data according to repoResultchanges in the last Viewlayer of these data it is observed.

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>
}

BasePagingRepositoryOn the PagedListconfiguration page the amount of data, parameters such as initial loading, and finally packed into a data structure Listingto return, this structure is as follows:

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
)

And the data source from 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>

}

Here sourceLivaDatawill BaseItemKeyedDataSourceas a value, and BaseItemKeyedDataSourceis the place to get the real data:

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>)
}

Subclass only need to rewrite the parent class onLoadInitial()and onLoadAfter()method can perform a refresh logic and a load more.

Here achieved retry] [[Loading and logic, is loaded, loading fails] of these three states, the three states using data structures mentioned previously RequestState, but the data is not successfully loaded here RequestStatein , where RequestStateonly said loading state. How that data to update it?

Let's look at a BaseItemKeyedDataSourcesubclass of it:

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)
            })
    }
}

It can be seen before and WeChatRepositorythe similar data is retrieved from the server, but the data is acquired by the callback.onResult()method returns to the Viewlayer.

In Viewlayer side, the list [Retry] button is encapsulated BasePagingAdapterin accordance with the observed networkStatedisplay, with hidden dynamic setting button, the relevant code is as follows:

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)
    }
}

According to these wrapper classes, subclasses implement them in business, you can easily use the pagingassembly it! ! !

Here, MVVMthe theory and practice of architecture have been opened!

Guess you like

Origin blog.csdn.net/recordGrowth/article/details/90377318