RecycleBin缓存机制

本文探讨了Android开发中ListView的缓存机制,重点分析了RecycleBin的工作原理。RecycleBin包含mActiveViews和mScrapViews两个数组,用于存储可复用的视图。当ListView滚动时,顶部视图被回收到mScrapViews,底部视图则从缓存中获取。布局过程涉及measure、layout和draw方法,当视图不可见时,会被移至RecycleBin。fillDown等方法根据mLayoutMode填充视图,makeAndAddView负责从缓存中获取或创建新的视图。通过对RecycleBin的理解,开发者可以更高效地利用资源,提升ListView的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习Android开发的过程中,大家应该或多或少的会接触到ListView这个控件。那么,大家有没有思考过,一个屏幕只能显示一定的item,每次下滑后,消失的item去哪里了?当再次回到原来的位置的时候,加载出来的item是重新获取的还是从缓存空间里提取的呢?接下来,我们通过分析RecycleBin机制来探寻ListView的缓存机制。

RecycleBin的基本原理

RecycleBin中有两个数组,mActiveViews和mScrapViews分别存储可以直接复用的view(处于可见状态的view)和间接复用的view(处于不可见状态的view)。当屏幕向下滑动的时候,顶部view不可见时,会将其回收至RecycleBin中的mScrapViews数组中进行保存,底部需要显示一个新的View时,会从mScrapViews中取出一个View传至convertView进行复用。

在ListView中,重写addView方法,当调用时,会抛出异常。ListView是一帧一帧绘制的,会经历measure->layout->draw方法。在ListView布局的时候,会调用layoutChildren方法绘制子View。当刚开始执行layout的时候,ListView的children是上一帧中需要绘制的view的集合,当layout执行完毕时,children变成当前帧需要绘制的子View的集合。

当一个view不可见时,首先会将该view移至RecycleBin。会根据数据是否发生变化调用不同的方法。如果数据发生变化,会将所有字View移至RecycleBin的mScrapView数组中进行保存,数据未发生改变在将当前View放入mActiveViews数组中。

final int firstPosition = mFirstPosition;
        final RecycleBin recycleBin = mRecycler;
        if (dataChanged) {
          
            for (int i = 0; i < childCount; i++) {
               
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
           
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

然后会将不可见的View清楚,调用detachAllViewFromParent()方法,将该view设置为null。

protected void detachAllViewsFromParent() {
    final int count = mChildrenCount;
    if (count <= 0) {
        return;
    }

    final View[] children = mChildren;
    mChildrenCount = 0;

    for (int i = count - 1; i >= 0; i--) {
        children[i].mParent = null;
        children[i] = null;
    }

当需要将一个view从RecycleBin中传至ListView时,会根据mLayoutMode的不同情况调用fillUp,fillDown等方法。

switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
        case LAYOUT_SYNC:
            sel = fillSpecific(mSyncPosition, mSpecificTop);
            break;
        case LAYOUT_FORCE_BOTTOM:
            sel = fillUp(mItemCount - 1, childrenBottom);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_FORCE_TOP:
            mFirstPosition = 0;
            sel = fillFromTop(childrenTop);
            adjustViewsUpOrDown();
            break;
        case LAYOUT_SPECIFIC:
            sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
            break;
        case LAYOUT_MOVE_SELECTION:
            sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
            break;
        default:
            if (childCount == 0) {
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
 

我们以fillDown方法为例子,该方法传入两个参数,一个是view对应adapter数据源中的位置,和下一个view在ListView中的起点位置。当nextTop小于ListView的高度并且pos小于数据源的数据总数时,将当前view添加进ListView并改变nextTop和pos的数据,实现pos的自增和下一个view的起点。

private View fillDown(int pos, int nextTop) {
    View selectedView = null;
    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }
    
    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
       
        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
       
        pos++;
    }

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

接下来我们看一下makeAndAddView方法是怎么将view添加进ListView的
在makeAndAddView中,如果数据源没有发生改变,我们会首先通过getActiveView方法在mActiveViews中查找是否存在该View,如果不存在,通过调用obtainView方法从mScrapViews数组中查询是否存在。最后调用setupChild对viwe进行定位和量算。

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;
        if (!mDataChanged) {
            child = mRecycler.getActiveView(position);
            if (child != null) {
                setupChild(child, position, y, flow, childrenLeft, selected, true);
                return child;
            }
        }   
        child = obtainView(position, mIsScrap);      
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
        return child;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值