Android中在写列表的时候,相信不少时候,咱们都须要进行ListView嵌套ListView编程。好比说:帖子+评论页面的编写。然而这种模型是会出现被嵌套ListView缓存失效的问题。编程
被嵌套的ListView的代码为:缓存
public class NestListView extends ListView { public NestListView(Context context, AttributeSet attrs) { super(context, attrs); } public NestListView(Context context) { super(context); } public NestListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //无限大小的子View空间 int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); } }
最近,在写微信朋友圈的功能中,由于须要处理说说和评论,**因此采用ListView 嵌套ListView的处理模式。**而不幸的是,发生了评论的ListView的View缓存没法使用的状况,从而形成GC比较频繁。微信
CommentAdapter adapter = (CommentAdapter) lv_lc_comment.getAdapter(); if (adapter == null) { adapter = new CommentAdapter(context); lv_lc_comment.setAdapter(adapter); } //从新设置数据 adapter.setData(cmTopicCommentList); adapter.setClsId(cmTopic.getClsId()); adapter.setTag(tag); adapter.setOnClickListener(onTopicListener); adapter.notifyDataSetChanged();
上面这段代码就是评论adapter刷新代码。在实际运行过程当中,会发生View Cache没法使用的问题。ide
调试代码,开始是怀疑不能屡次调用setAdapter的问题**(以前都是没有添加adapter是否存在判断)**,而setAdapter代码为:调试
public void setAdapter(ListAdapter adapter) { ... mRecycler.clear(); ... }
能够发现,setAdapter()会清空缓存。而采用最新的代码编写后,仍是发生了View Cache无效的问题。再次翻阅ListView的代码AbsListView# obtainView :code
View obtainView(int position, boolean[] isScrap) { ... //查看是否存在相应的View缓存 scrapView = mRecycler.getScrapView(position); ... return child; }
可见,在ListView显示的时候,会去查询RecycleBin中是否存在对应的View缓存的,可是每次都是为null。get
可是业务逻辑中,刷新一条说说的时候,会总体刷新它的评论,因此按照正常的逻辑来讲,被嵌套的评论ListView中,应该包含了以前的评论View缓存。这必定是哪里出了问题。it
因此看了一下**RecycleBin回收View的各个时机,**而后发现:io
protected void layoutChildren() { ... //若是数据变化,则回收View if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(getChildAt(i), ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i); } } } else { recycleBin.fillActiveViews(childCount, firstPosition); } .... }
翻看了几个addScrapView几个被调用的地方后,发现layoutChildren这个调用最可疑,由于它利用dataChanged标志来判断是否回收View,而 notifyDataSetChanged就是标记须要回收View。class
而且ListView#onMeasure->ListView#measureHeightOfChildren() 须要进行获取子View高度的时候:
final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition, ... for (i = startPosition; i <= endPosition; ++i) { child = obtainView(i, isScrap); measureScrapChild(child, i, widthMeasureSpec); ... } ... }
**因此应该是NestListView这种无限长度的ListView的锅:**由于在onMeasure的时候obtainView全部的View高度,而RecycleBin是在onLayout的时候才能缓存不使用的View。而View#onMeasure() > View#onLayout(), 因此形成被嵌套的ListView缓存失效的问题。
而最新的RecycleView已经能够支持View强制回收了API为:swapAdapter()。