ViewHolder的MVVM实现

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的生命周期如下图所示:

image.png

代码如下所示:
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的数据共享功能,未来会慢慢补充。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容