RecycleBin缓存机制

在学习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;
    }

猜你喜欢

转载自blog.csdn.net/qq_38256015/article/details/83270008