MVVM深入理解

 

 

还是基于上面的MVVM结构来谈一下对该结构深入一点的理解

 

  • 仓库层的理解

刚开始看MVVM的时候ViewModel层比较好理解一些,就是作为View和Mode的桥接,将视图和数据解耦,但是对仓库层的理解就没那么好了

第一次写仓库层的时候是直接在ViewModel层调用数据加载,比如下面这段代码:

class LiveInfoViewMode : ViewModel() {

    private var liveData: MutableLiveData<Result<LiveInfo>>? = null

    fun getLiveInfo(anchorId: String): MutableLiveData<Result<LiveInfo>> {

        if (liveData == null) {
            liveData = MutableLiveData()
        }

        liveData?.postValue(Result.loading())
        val liveApi = LiveApi()
        liveApi.getLiveData(anchorId, object : IHttpClient.ICallback {

            override fun onSuccess(bytes: ByteArray) {
               val liveInfo = …//解析数据
               liveData?.postValue(Result.success(liveInfo))
            }

            override fun onError(e: IHttpClient.RequestException) {
                e.printStackTrace()
                liveData?.postValue(Result.error("${e.message}", -10001))
            }
        })

        return liveData!!
    }
}

有没有发现一个问题?

我们知道ViewModel层是主要处理业务相关逻辑的,因此这块要保证后面底层数据实现改变时不会影响到这边,不然业务的修改会很麻烦,而且容易出问题。

但是上面的写法却违背了这一条,试想一下,如果后面getLiveInfo的实现不再是从http获取数据,而是改成了从本地数据库获取呢,这时候需要修改的点有:

  1. liveApi获取数据的实现方式以及返回数据结构的重新构建
  2. 所有用到liveApi的ViewModel实现都需要更换

缺点就是我们只是要改个数据获取方式就需要动到所有的ViewModel,与我们的初衷解耦相背离

基于上面的例子,对仓库层的设计有了新的理解:

仓库层应该是对外提供统一的接口,具体的实现由具体注册的实现类来实现,这样即使你的数据获取方式一直在变,仓库层对外接口不变,那么每次只需要更改注册的实现类就行了,不会影响到业务层的逻辑

下面是新的设计:

class LiveRoomViewModelFactory(private val repo: ILiveRoomRepo = LiveRoomRepo()) :
    ViewModelProvider.NewInstanceFactory() {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return LiveRoomViewMode(repo) as T
    }

}

class LiveRoomViewMode(private val repo: ILiveRoomRepo) : ViewModel() {  
    fun getLiveInfo(anchorId: String, gameId: String): MutableLiveData<Result<List<LiveInfo>>> {
        return repo.getRelatedLiveAnchor(anchorId, gameId, pageNum, 1).getLiveData()
    } 

}

interface ILiveRoomRepo {
    fun getRelatedLiveAnchor(anchorId: String, gameId: String, pageNum: Int, direction: Int): Deferred<List<LiveInfo>>  

}

viewModel = ViewModelProviders.of(this, LiveRoomViewModelFactory()).get(LiveRoomViewMode::class.java)
/**
 * repo层返回的数据结构,之所以用Deferred是考虑到一个task含有多个async协程,然后要等
 * 所有async结束后再调用complete来通知
*/
fun <T> asyncDataCall(type: Int = TYPE_NETWORK, dataCall: (() -> T?)): Deferred<T> {
    val deferred = CompletableDeferred<T>()

    deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
            // ...
        }
    }

    val executor =
        if (type == TYPE_NETWORK) AppExecutors.getInstance().networkIO() else AppExecutors.getInstance().diskIO()
    executor.execute {
        try {
            // 请求成功
            val res = dataCall.invoke()
            if (res == null) {
                deferred.completeExceptionally(Throwable("data is null"))
            } else {
                deferred.complete(res)
            }
        } catch (e: Throwable) {
            // 请求失败
            deferred.completeExceptionally(e)
        }
    }

    return deferred
}

/**
 * Deferred拓展,返回UI层使用的LiveData<Result<T>>
 */
fun <T> Deferred<T>.getLiveData(): MutableLiveData<Result<T>> {

    val result = MutableLiveData<Result<T>>()

    // 数据loading状态
    result.value = Result.loading()

    GlobalScope.launch {
        try {

            // 协程发起异步数据请求,挂起协程,等待deferred的complete调用
            val response = await()

            // 请求成功
            withContext(Dispatchers.Main) {
                // 数据success状态
                result.value = Result.success(response)
            }
        } catch (e: Throwable) {
            // 请求失败
            withContext(Dispatchers.Main) {
                // 请求数据过程出错,数据error状态
                result.value = Result.error("${e.message}", -10001)
            }
        }
    }

    return result
}

新的仓库层设计是每个ViewModel有一个repo接口,ViewModel只认repo提供的接口,具体的实现由创建ViewModel时选择的工厂来决定

再回到之前的例子,假如这时候我需要修改接口的实现,我只要修改LiveRoomRepo里面的实现就行了,返回的数据格式不变,数据结构解析都在LiveRoomRepo里面,ViewModel层都不需要更改,是不是就做到解耦了,而且测试起来也比较方便,只需要测试接口实现就行了,不需要再测试ViewModel层的逻辑

 

  • ViewModel层的优化

MVVM里面使用ViewModel的好处是做到数据响应式编程,将UI和数据解耦,同时ViewModel还和Activity的生命周期绑定到一起,还能做到数据共享,但是ViewModel和Activity生命周期绑定这点也诞生了一个问题:

如果我的数据获取频率非常高,那岂不是每次要不断注册到Activity生命周期以及取消注册,这样性能肯定不会好,那怎么办呢?

官方也考虑到这个问题,所以提供了一套Transform API来解决这个问题,那这个又是什么东西呢,简单来说就是加了层代理,UI层只要注册这个代理一次就行了,数据获取时会通过另一个LiveData来通知这个代理,说了这么多,来看一下具体实现吧:

class LiveRoomViewMode(private val repo: ILiveRoomRepo) : ViewModel() { 

    /**
     * Transformations输入LiveData
     */
    private val heartTransInput = MutableLiveData<HeartObject>()

    /**
     * Transformations转换后的LiveData
     */
    private var heartTransData: LiveData<Result<HeartPollRsp>>? = null

    /**
     * 频繁调用可通过Transformations来转换数据层的LiveData,
     * 这样UI层只需要注册转换后的LiveData就行了,不需要每次更新注册的LiveData
     */
    fun getHeartOfTransform(): LiveData<Result<HeartPollRsp>> {
        heartTransData ?: let {
            heartTransData =
                Transformations.switchMap<HeartObject, Result<HeartPollRsp>>(heartTransInput) { heartObject ->
                    repo.heartBeatPoll(
                        heartObject.anchorId,
                        heartObject.playBillId,
                        heartObject.moduleId,
                        heartObject.lastSucTime
                    ).getLiveData()
                }
        }
        return heartTransData!!
    }

    /**
     * 更新转换后的LiveData的mSource,UI层在注册之后每次更新参数调用该函数就行了
     */
    fun setHeartTransformValue(heartObject: HeartObject) {
        heartTransInput.value = heartObject
    }

    class HeartObject(var anchorId: String, var playBillId: String, var moduleId: Int, var lastSucTime: Long)

}

业务层调用注册ViewModel:

abstract class UiObserver<T> : Observer<Result<T>> {
    override fun onChanged(t: Result<T>) {
        when (t.status) {
            Result.LOADING -> loading()
            Result.SUCCESS -> {
                if (t.data == null) {
                    error(10001, "null data")
                } else {
                    success(t.data!!)
                }
            }

            Result.ERROR -> error(t.errorCode, t.message ?: "default error msg")
            else -> throw IllegalStateException("lack of handle logic for status:${t.status}")
        }
    }

    open fun loading() {}

    open fun success(data: T) {}

    open fun error(code: Int, msg: String) {}
}
val observer = object : UiObserver<HeartPollRsp>() {
            override fun loading() {
                viewModel.loading = true
                Log.d(TAG, "loading page:${viewModel.pageNum}")
            }

            override fun success(data: HeartPollRsp) {
                Log.d(TAG, "$data")               
            }

            override fun error(code: Int, msg: String) {
                viewModel.loading = false
                Log.e(TAG, "error msg:$msg code:$code")
            }
        }
viewModel.getHeartOfTransform().observe(fragmentActivity as LifecycleOwner, observer)

可以看到我们返回给UI层的不再是简单的LiveData了,而是通过Transformations.switchMap变换后的LiveData,那这个变换做了什么呢,继续看一下: 

@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> source,
            @NonNull final Function<X, LiveData<Y>> switchMapFunction) {

        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            LiveData<Y> mSource;
            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = switchMapFunction.apply(x);
                if (mSource == newLiveData) {
                    return;
                }

                if (mSource != null) {
                    result.removeSource(mSource);
                }

                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
}

简单介绍一下,result是我们最终返回的LiveData,UI层只要注册一次就行了,然后result会监听source的变化,source其实就是我们原先的参数,每次心跳请求都会触发source的onChanged,在source的onChanged里会调用仓库层的接口获取新的LiveData,这个LiveData数据变化时会通知result,这样result就能同步UI层了

说了这么多,其实你也发现了,Transform其实是用两个LiveData来为原先的仓库层数据做代理,所以也不是那么复杂的。

上面那样做的好处就是UI层只要注册一次LiveData就行了,后面数据变化会间接通知这个LiveData,这样不会触发Activity生命周期的反复注册与取消,减少不必要的开销

 

  • 总结

MVVM仓库层的设计不仅是针对这个模式的,像MVP、MVC等模式也是同样原理,目的都是为了解耦,小型项目可能没有太大感触,但是项目越到后期,这种体会就会越深,没有解耦的话一个小的修改就会触发很多改动以及测试,浪费了很大人力资源,因此每个项目在开始阶段就要设计好整体架构才行!

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值