本文字数: 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
也许你还想看
如果你有写博客的习惯
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- php如何实现session,自己实现session,laravel如何实现session
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
- Docker实现原理之 - OverlayFS实现原理
- 为什么实现 .NET 的 ICollection 集合时需要实现 SyncRoot 属性?如何正确实现这个属性?
- 自己实现集合框架(十):顺序栈的实现
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。