背景
在阅读ListView的测绘流程的过程中,发现ListView很多地方都用到了缓存技术,这主要是由它的父类AbsListView的内部类RecycleBin实现的,我整理了一下测绘过程中里面常用的方法,以备日后查看
fillActiveViews
这个方法是用来把当前ListView显示的全部内容缓存到mActiveViews中,代码如下
void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition; // listView中第一个完整显示的子view的位置
//noinspection MismatchedReadAndWriteOfArray
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
// header和footer不缓存到这里
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
// However, we will NOT place them into scrap views.
activeViews[i] = child; // 把子view添加进mActiveViews中
// Remember the position so that setupChild() doesn't reset state.
// 更新lp.scrappedFromPosition
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
removeSkippedScrap
这个方法在adapter.getItemViewType = 1时是个摆设,代码如下
void removeSkippedScrap() {
// adapter.getItemViewType() == 1 的话,这个方法就是摆设
if (mSkippedScrap == null) {
return;
}
final int count = mSkippedScrap.size();
for (int i = 0; i < count; i++) {
removeDetachedView(mSkippedScrap.get(i), false);
}
mSkippedScrap.clear();
}
getActiveView
用来从mActiveViews缓存中获取view,代码如下
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
getScrapView
用来从mCurrentScrap或mScrapView中获取view,代码如下
View getScrapView(int position) {
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) { // 最简单的自定义adapter走的是这一步
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
addScrapView
把view缓存到mCurrentScrap或mScrapViews中,代码如下
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
// Can't recycle, but we don't know anything about the view.
// Ignore it completely.
return;
}
lp.scrappedFromPosition = position;
// Remove but don't scrap header or footer views, or views that
// should otherwise not be recycled.
final int viewType = lp.viewType;
if (!shouldRecycleViewType(viewType)) { // 判断viewType < 0,一般viewType就是0,进不来
// Can't recycle. If it's not a header or footer, which have
// special handling and should be ignored, then skip the scrap
// heap and we'll fully detach the view later.
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // header和footer要直接和list分离,而不是进入回收站
getSkippedScrap().add(scrap); // 加入另一个数组列表mScrapViews中
}
return;
}
scrap.dispatchStartTemporaryDetach(); // 设置标志位,清空view的回调
// The the accessibility state of the view may change while temporary
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) { // 如果要回收的view是短暂状态的view(有标志位标记,一般都不是)
.. // 忽略
} else {
clearScrapForRebind(scrap);
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap); // 一般就这种情况,直接add进数组列表中去
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap); // 通知回调
}
}
}
getSkippedScrap
获取mSkippedScrap,代码如下
private ArrayList<View> getSkippedScrap() {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<>();
}
return mSkippedScrap;
}
scrapActiveViews
把mActiveViews里的数据移到mCurrentScrap中去,代码如下
void scrapActiveViews() {
final View[] activeViews = mActiveViews;
final boolean hasListener = mRecyclerListener != null;
final boolean multipleScraps = mViewTypeCount > 1;
ArrayList<View> scrapViews = mCurrentScrap;
final int count = activeViews.length;
for (int i = count - 1; i >= 0; i--) {
final View victim = activeViews[i];
if (victim != null) {
final AbsListView.LayoutParams lp
= (AbsListView.LayoutParams) victim.getLayoutParams();
final int whichScrap = lp.viewType;
activeViews[i] = null; // 把第i个置为空
if (victim.hasTransientState()) {
.. // transient_view处理,忽略
} else if (!shouldRecycleViewType(whichScrap)) {
.. // view_type < 0,忽略
} else { // 一般就是这里的逻辑
// Store everything else on the appropriate scrap heap.
if (multipleScraps) { // 不覆写adapter里的getViewType的话,这里走不到
scrapViews = mScrapViews[whichScrap];
}
lp.scrappedFromPosition = mFirstActivePosition + i;
removeDetachedView(victim, false); // 移除子view的一些处理
scrapViews.add(victim); // 给mCurrent这个回收站添加子项
if (hasListener) { // 必要的话,通知回调
mRecyclerListener.onMovedToScrapHeap(victim);
}
}
}
}
pruneScrapViews(); // 处理多种viewType和transient的情况,忽略
}
shouldRecycleViewType
判断viewType是否有效,有效的才能被回收缓存,代码如下
public boolean shouldRecycleViewType(int viewType) {
return viewType >= 0;
}
removeDetachedView
对分离出去的view进行一些清除工作,代码如下
private void removeDetachedView(View child, boolean animate) {
child.setAccessibilityDelegate(null);
AbsListView.this.removeDetachedView(child, animate);
}
调用了AbsListView.removeDetachedView(),实则是ViewGroup的同名方法,代码如下
protected void removeDetachedView(View child, boolean animate) {
if (mTransition != null) {
mTransition.removeChild(this, child);
}
// 清除焦点
if (child == mFocused) {
child.clearFocus();
}
if (child == mDefaultFocus) {
clearDefaultFocus(child);
}
if (child == mFocusedInCluster) {
clearFocusedInCluster(child);
}
child.clearAccessibilityFocus();
cancelTouchTarget(child); // 取消触摸事件,如果能dispatchTochEvent,就分发下去
cancelHoverTarget(child); // 鼠标移动事件,忽略
if ((animate && child.getAnimation() != null) ||
(mTransitioningViews != null && mTransitioningViews.contains(child))) {
addDisappearingView(child);
} else if (child.mAttachInfo != null) { // 走这里
child.dispatchDetachedFromWindow(); // 清理或触发一些回调
}
if (child.hasTransientState()) {
childHasTransientStateChanged(child, false);
}
dispatchViewRemoved(child); // 必要的话,触发mOnHierarchyChangeListener.onChildViewRemoved方法
}
retrieveFromScrap
从回收站中读取view,代码如下
private View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
final int size = scrapViews.size();
if (size > 0) {
// See if we still have a view for this position or ID.
// Traverse backwards to find the most recently used scrap view
for (int i = size - 1; i >= 0; i--) {
final View view = scrapViews.get(i);
final AbsListView.LayoutParams params =
(AbsListView.LayoutParams) view.getLayoutParams();
if (mAdapterHasStableIds) { // 适配器继承自BaseAdapter的话,默认为false
final long id = mAdapter.getItemId(position);
if (id == params.itemId) {
return scrapViews.remove(i);
}
} else if (params.scrappedFromPosition == position) { // 可以看到,view在回收站中的位置是保存在自己的LayoutParams里的
final View scrap = scrapViews.remove(i); // 这里返回的是最后一个位置信息符合要求的view,但不一定是正确的view
clearScrapForRebind(scrap); // 无障碍处理
return scrap;
}
}
final View scrap = scrapViews.remove(size - 1); // 找不到对应的view,就把回收站中最后一个view返回出去
clearScrapForRebind(scrap);
return scrap;
} else {
return null;
}
}
markChildrenDirty
强制所有缓存的子view进行forceLayout,代码如下
public void markChildrenDirty() {
if (mViewTypeCount == 1) {
final ArrayList<View> scrap = mCurrentScrap;
final int scrapCount = scrap.size();
for (int i = 0; i < scrapCount; i++) {
scrap.get(i).forceLayout();
}
} else {
.. // 多种viewType处理
}
.. // transient处理
}