你可能不知道的ListView Recycler机制

1、问题引出

项目中使用的listview拥有多个不同类型的item,而且有些item数量只有一个,之前以为滑动时view无法复用。

2、分析

说到ListView的View复用,先来一张经典的原理解析图。

这里写图片描述

从图中可以看出,当listview某一item完全滑出屏幕时,内部的一个Recycler容器会缓存这个item。当listview新的一项需要在屏幕上展示时,这项缓存的item会在getView方法中以convertView参数传入,从而达到复用的效果,同时更新Recycler中的缓存item。通常是这么用的:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    MyLayout layout = convertView instanceof MyLayout ? (MyLayout) convertView : null;
    if (layout == null) {
        layout = (layout) res.inflate(getContext(), R.layout.my_rank_layout, parent, false);
    }
    layout.setData(data);
    return layout;
}

但是,上面这种说法并不是完全正确的。这里需要注意一个问题,就是item的viewType。其实仔细看上面的原理图中的Recycler部分也可以发现,Recycler会分别缓存不同viewType的item。查看AbsListView源码中Recycler实现:

// item缓存数组
private ArrayList<View>[] mScrapViews;
public void setViewTypeCount(int viewTypeCount) {
    if (viewTypeCount < 1) {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    //noinspection unchecked
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
        scrapViews[i] = new ArrayList<View>();
    }
    mViewTypeCount = viewTypeCount;
    mCurrentScrap = scrapViews[0];
    mScrapViews = scrapViews;
}

listview的setAdapter方法中会调用该方法,从代码可以看出,mScrapViews数组大小为adapter的viewTypeCount,数组每个元素又是一个list,即可以为相同viewType的item缓存多份。当listview滚动时,会将滑出屏幕的item缓存至Recycler。部分源码如下:

private void scrollListItemsBy(int amount) {
    ...
    final AbsListView.RecycleBin recycleBin = mRecycler;
    if (amount < 0) {
        ...
        // listview上拉时,上面的item需要缓存
        View first = getChildAt(0);
        while (first.getBottom() < listTop) {
            AbsListView.LayoutParams layoutParams = (LayoutParams) first.getLayoutParams();
            if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
                recycleBin.addScrapView(first, mFirstPosition);
            }
            detachViewFromParent(first);
            first = getChildAt(0);
            mFirstPosition++;
        }
    } else {
        ...
        // listview下拉,下面的item需要缓存
        int lastIndex = getChildCount() - 1;
        View last = getChildAt(lastIndex);
        while (last.getTop() > listBottom) {
            AbsListView.LayoutParams layoutParams = (LayoutParams) last.getLayoutParams();
            if (recycleBin.shouldRecycleViewType(layoutParams.viewType)) {
                recycleBin.addScrapView(last, mFirstPosition+lastIndex);
            }
            detachViewFromParent(last);
            last = getChildAt(--lastIndex);
        }
    }
}

当adapter的getView回调时,会从Recycler中去取符合条件的item缓存。Recycler的getScrapView()方法会根据当前item的viewType去寻找合适的缓存,代码如下:

/**
 * @return A view from the ScrapViews collection.
 */
View getScrapView(int position) {
    final int whichScrap = mAdapter.getItemViewType(position);
    if (whichScrap < 0) {
        return null;
    }
    if (mViewTypeCount == 1) {
        return retrieveFromScrap(mCurrentScrap, position);
    } else if (whichScrap < mScrapViews.length) {
        return retrieveFromScrap(mScrapViews[whichScrap], position);
    }
    return null;
}

3、结论

listview内部会分别缓存不同类型的item。

续:

(1)为什么每个viewType都会用一个ArrayList来缓存,也就是说为什么会缓存多份相同viewType的item?
根据上面的原理图,ListView滑动时上方滑出屏幕的item进入缓存,当下方有相同viewType的item滑进屏幕时,应该可以直接去缓存中取到缓存,这不是刚好一进一出,用一个变量去缓存不就可以了?其实不然,考虑ListView第一屏显示的都是相同viewType的item,第二屏都是跟第一屏不一样类型的item,那么当第二屏滑进屏幕时,第一屏的item都会进入缓存,并且无法被消费,这就要求针对每个类型的item都需要一个List来缓存多份。
(2)既然每个viewType的item都会缓存多份,那么问题又来了,最多可以缓存多少份呢?有没有数量限制,或者说可以无限缓存呢?
通过阅读源码,根本没有看到关于缓存数量限制的代码,那么真的是可以无限缓存吗?那内存不是爆了吗?其实不是的,仔细想想就能明白,针对每种类型的item,最多只会缓存一屏能容下该类型item的数量。因为超出屏幕的相同类型item滑进屏幕时,就会消费掉缓存,所以源码中根本无需做缓存数量的限制,RecycleBin机制本身就能够达到一种动态的平衡。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值