ViewHolder的MVVM实现

2024-01-18 23:32
文章标签 实现 mvvm viewholder

本文主要是介绍ViewHolder的MVVM实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文字数:1569

预计阅读时间:5分钟

1.前言

在App的开发中,列表,流式布局是最常用的UI元素。通常RecyclerView的ViewHolder会根据业务的需要,异步处理一些耗时操作,再根据处理后的结果进行UI的更新。

这种情况下,有可能出现问题:由于RecyclerView针对ViewHolder有回收复用机制,所以当数据回来后,如果这个ViewHolder已经被复用则可能导致数据更新错误。

通常我们会通过打TAG或判断唯一标识来确保数据更新的准确性。为此我们开始考虑,有没有更好的处理办法呢?

每一个ViewHolder不需要进行其他处理,即可保证与异步数据是对应关系,不会导致复用错误。另外通过使用MVVM模式对View和数据层进行解耦。

MVVM模式在Android中的使用已经非常广泛了,V层(Activity或Fragment)与VM层(ViewModel)通过LiveData来进行数据交换。其中V层实现了LifecycleOwner接口从而持有了生命周期(LifeCycle对象),并观察VM层的LiveData的变化。

LiveData在接收到M层的数据变化后根据LifecycleOwner当前所处的生命周期,来决定是否通知给Observer,即V层去更新UI。

因此我们想到ViewHolder能不能像Activity或Fragment一样,根据自己的生命周期变化,来处理VM层返回的数据呢?

在这个思维模式的前提下,我们开始考虑V层的拓展。通常我们在处理业务逻辑时可以认为View的生命周期会跟随Activity或Fragment的生命周期,即只要让View感知LifecycleOwner的生命周期变化即可。

但由于RecyclerView的回收复用机制,我们认为每一个ViewHolder应根据回收复用策略,拥有自己的生命周期。

这样就可以像Activity或Fragment一样,利用MVVM模式,来实现UI层与数据层的交互,并通过对LiveData与ViewHolder的改造来保证ViewHolder与数据对应的准确性。

2.目的

使ViewHolder可以像Activity或Fragment那样使用MVVM模式。让ViewHolder拥有生命周期,通过ViewModel与LiveData对数据变化进行监听,在被复用后与原LiveData解绑,解决复用后数据错乱的问题。

3.解决方案

(1)创建抽象类BaseLifecycleViewHolder继承ViewHolder,实现LifecycleOwner接口,使之拥有生命周期。

(2) 创建BaseLifeCycleAdapter继承Adapter,在onBindViewHolder()中注册ViewHolder的生命周期。

(3)创建VHLiveData继承MutableLiveData,保证ViewHolder与数据对应的准确性。

(4)配合使 ViewModel 完成 MVVM 模式。

4.技术实现说明

4.1. BaseLifecycleViewHolder

(1)使BaseLifecycleViewHolder实现LifecycleOwner接口:

private var mLifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this@BaseLifeCycleViewHolder)  override fun getLifecycle(): Lifecycle {  return mLifecycleRegistry  } 

(2)为BaseLifecycleViewHolder添加生命周期:

我们认为ViewHolder在创建和被复用的时候应该算作一个新的生命周期的开始,而这两个时机都会走到onBindViewHolder(),所以我们在onBindViewHolder()中注册onCreate和onStart事件。

onStop和onDestroy在itemView的onViewDetachedFromWindow()中注册。但是由于itemView从window中detach后,有可能只是从屏幕中移除但并没有被真正回收,下次滑动移回来将不会走onBindViewHolder(),而是直接走onViewAttachedToWindow(),所以在onViewAttachedToWindow()将会判断ViewHolder的state,如果不处于Start状态,onCreate和onStart会在此时注册。BaseLifeCycleViewHolder的生命周期如下图所示:

代码如下所示:

BaseLifeCycleAdapter:

@Override  public void onBindViewHolder(@NonNull VH holder, int position) {  holder.registerLifecycle(true);  super.onBindViewHolder(holder, position);  } 

BaseLifeCycleViewHolder:

itemView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {  override fun onViewDetachedFromWindow(v: View?) {  mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)  mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)  }  override fun onViewAttachedToWindow(v: View?) {  if (mLifecycleRegistry.currentState != Lifecycle.State.STARTED) {  registerLifecycle(false)  }  }  })

registerLifecycle():

fun registerLifecycle(resetVersion: Boolean) {  mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)  if (resetVersion) {  mViewHolderVersion++ //被复用后ViewHolder的version加1  }  val bindList = bindLiveData(ArrayList<Pair<VHLiveData<Any>, Observer<Any>>>())  bindList?.forEach {it.first?.bindLifecycleOwner(this, it.second!!, resetVersion)}mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)  }

其中bindLiveData()是个抽象方法,需要子类去实现,为list添加数据

abstract fun bindLiveData(list: ArrayList<Pair<VHLiveData<Any>, Observer<Any>>>): ArrayList<Pair<VHLiveData<Any>, Observer<Any>>>?  

(3) bindLiveData()的实现:

在registerLifecycle()方法中,我们提供了抽象方法bindLiveData()拿到LiveData与Observer,并调用VHLiveData的bindLifecycleOwner()方法进行绑定。在registerLifecycle()中有个bool型的resetVersion变量,这个变量的作用将在之后进行说明。bindLiveData()的实现如以下示例代码:

@org.jetbrains.annotations.Nullable  @Override  public ArrayList<Pair<VHLiveData<Object>, Observer<Object>>> bindLiveData(@NotNull ArrayList<Pair<VHLiveData<Object>, Observer<Object>>> list) {  list.add(new Pair(mViewModel.getMRoomStatus(), new Observer<Integer>() {  @Override  public void onChanged(@Nullable Integer status) {  setRoomText(status);  }  }));  return list;  } 

4.2.原生LiveData源码解析:

在介绍VHLiveData的实现之前,我们先对原生的LiveData源码进行解析,以便更好的理解改造的目的。我们从observe()方法开始:

@MainThread  public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {  if (owner.getLifecycle().getCurrentState() == DESTROYED) {  // ignore  return;  }  LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);  ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);  if (existing != null && !existing.isAttachedTo(owner)) {  throw new IllegalArgumentException("Cannot add the same observer"  + " with different lifecycles");  }  if (existing != null) {  return;  }  owner.getLifecycle().addObserver(wrapper);  } 

每一次observe()时会将LifecycleOwner和Observer对象封装成一个LifecycleBoundObserver()对象,并放入mObservers这个Map中:

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {  @NonNull final LifecycleOwner mOwner;  LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {  super(observer);  mOwner = owner;  }  @Override  boolean shouldBeActive() {  return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);  }  @Override  public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {  if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {  removeObserver(mObserver);  return;  }  activeStateChanged(shouldBeActive());  }  @Override  boolean isAttachedTo(LifecycleOwner owner) {  return mOwner == owner;  }  @Override  void detachObserver() {  mOwner.getLifecycle().removeObserver(this);  }  }

如果LifecycleOwner状态发生了变化,会执行activeStateChanged():

void activeStateChanged(boolean newActive) {  if (newActive == mActive) {  return;  }  // immediately set active state, so we'd never dispatch anything to inactive  // owner  mActive = newActive;  boolean wasInactive = LiveData.this.mActiveCount == 0;  LiveData.this.mActiveCount += mActive ? 1 : -1;  if (wasInactive && mActive) {  onActive();  }  if (LiveData.this.mActiveCount == 0 && !mActive) {  onInactive();  }  if (mActive) {  dispatchingValue(this);  }  }  private 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<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;  }  // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.  //  // we still first check observer.active to keep it as the entrance for events. So even if  // the observer moved to an active state, if we've not received that event, we better not  // notify for a more predictable notification order.  if (!observer.shouldBeActive()) {  observer.activeStateChanged(false);  return;  }  if (observer.mLastVersion >= mVersion) {  return;  }  observer.mLastVersion = mVersion;  //noinspection unchecked  observer.mObserver.onChanged((T) mData);  
} 

在considerNotify()中,有两个变量需要注意,LiveData持有的mVersion和LifecycleBoundObserver父类ObserverWrapper持有的mLastVersion,这两个变量的默认值都是-1,代码中会判断mLastVersion和mVersion的大小,如果mLastVersion小于mVersion就会走到onChanged()。这个mVersion是在setValue()中赋值的(postValue方法最后也会执行到setValue中):

@MainThread  protected void setValue(T value) {  assertMainThread("setValue");  mVersion++;  mData = value;  dispatchingValue(null);  }

4.3. VHLiveData

如果我们使用原生的LiveData,由于LiveData在ViewHolder复用后还是之前的LiveData对象,所以mVersion的值会根据LiveData之前的setValue的次数增加。mLastVersion的值是在初始化LifecycleBoundObserver()时,在其父类ObserverWrapper中会被赋值为-1,每次setValue后会将mVersion的值赋予mLastVersion。

在上面的代码中我们可知,每一次LiveData和Observer进行绑定时都会新创建一个LifecycleBoundObserver对象,mLastVersion的值为-1。这就导致在ViewHolder复用的时候,mLastVersion是-1,mVersion的值若>-1就会走到onChanged()中。从而导致复用问题。

因此VHLiveData增加了bindLifecycleOwner()方法,用来代替原生的observe()方法,考虑到修改mVersion的值可能会引起多个Observer与LiveData绑定时数据接收的隐患,我们决定在复用时修改mObservers中相应ObserverWrapper持有的mLastVersion变量。

通过反射从mObservers中拿到该Observer对应的LifecycleBoundObserver对象,再将mVersion的值赋予给其父类ObserverWrapper持有的mLastVersion。VHLiveData的代码如下:

public class VHLiveData<T> extends MutableLiveData<T> {  public void bindLifecycleOwner(@NonNull LifecycleOwner owner, @NonNull Observer observer, boolean resetVersion) {  super.observe(owner, observer);  if (resetVersion) {  try {  Class hySuperClass = LiveData.class;  Field observers = hySuperClass.getDeclaredField("mObservers");  observers.setAccessible(true);  Object objectObservers = observers.get(this);  Class<?> classObservers = objectObservers.getClass();  Method methodGet = classObservers.getDeclaredMethod("get", Object.class);  methodGet.setAccessible(true);  Object objectWrapperEntry = methodGet.invoke(objectObservers, observer);  Object objectWrapper = null;  if (objectWrapperEntry instanceof Map.Entry) {  objectWrapper = ((Map.Entry) objectWrapperEntry).getValue();  }  if (objectWrapper != null) {  Class<?> classObserverWrapper = objectWrapper.getClass().getSuperclass();  Field lastVersion = classObserverWrapper.getDeclaredField("mLastVersion");  lastVersion.setAccessible(true);  Field version = hySuperClass.getDeclaredField("mVersion");  version.setAccessible(true);  Object objectVersion = version.get(this); //set wrapper's version  lastVersion.set(objectWrapper, objectVersion);  LogUtil.d("bigcatduan1", "set mLastVersion: " + objectVersion);  }  } catch (Exception e) {  LogUtil.e("bigcatduan1", "set mLastVersion failed");  e.printStackTrace();  }  }  }  }

4.4. resetVersion

最后我们再来说说之前遗留的resetVersion这个变量。这个resetVersion是在BaseLifeCycleViewHolder调用bindLiveData()传过来的。

这是因为,如果bindViewHolder()之后,view的生命周期在onViewAttachedToWindow和onViewDetachedFromWindow之间来回切换而并没有被系统回收,这个时候并不会导致复用问题,所以在这种情况下,mLastVersion不用重新赋值。

5.总结

以上方案使各个模块Owner可使用类似Activity或Fragment的方式实现ViewHolder的MVVM模式,并解决ViewHolder复用与异步数据绑定错乱的问题。但是目前我们无法通过ViewModel,实现类似LiveData在Activity和Fragment的数据共享功能,未来会慢慢补充。

参考资料:

[1]https://developer.android.google.cn/reference/androidx/lifecycle/LiveData.html?

[2]https://www.jianshu.com/p/d0ac108b7698

[3]https://www.jianshu.com/p/84f5c9ed0c59

也许你还想看

(▼点击文章标题或封面查看)

AndroidQ强制黑暗(ForceDark)模式实践

2020-01-02

干货!混合式架构App当中的通信安全

2019-10-17

Android消息机制Handler原理解析

2019-09-26

加入搜狐技术作者天团

千元稿费等你来!

戳这里!☛

   

  您对本文有什么疑问吗?

     点我写留言

  ▼▼▼

这篇关于ViewHolder的MVVM实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/620608

相关文章

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机