0%

RecyclerView-onViewRecycled-什么时候被调用?

前言

有个需求,要求View离开屏幕后立即上报后台,本来以为这是个非常easy的需求,直接给RecyclerView设置一个监听即可,没想到View离开屏幕后,RecyclerView并没有立即调用onViewRecycled方法,于是,跟了一下代码,发现RecyclerView还真是自己做了缓存。

代码分析

显然是先从接口调用处开始入手

dispatchViewRecycled

1
2
3
4
5
6
7
8
9
10
11
12
void dispatchViewRecycled(ViewHolder holder) {
if (mRecyclerListener != null) {
mRecyclerListener.onViewRecycled(holder);
}
if (mAdapter != null) {
mAdapter.onViewRecycled(holder);
}
if (mState != null) {
mViewInfoStore.removeViewHolder(holder);
}
if (DEBUG) Log.d(TAG, "dispatchViewRecycled: " + holder);
}

addViewHolderToRecycledViewPool

添加ViewHolder到回收池

1
2
3
4
5
6
void addViewHolderToRecycledViewPool(ViewHolder holder) {
ViewCompat.setAccessibilityDelegate(holder.itemView, null);
dispatchViewRecycled(holder);
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}

recycleViewHolderInternal

核心代码在这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
* internal implementation checks if view is scrapped or attached and throws an exception
* if so.
* Public version un-scraps before calling recycle.
*/
void recycleViewHolderInternal(ViewHolder holder) {
if (holder.isScrap() || holder.itemView.getParent() != null) {
throw new IllegalArgumentException(
"Scrapped or attached views may not be recycled. isScrap:"
+ holder.isScrap() + " isAttached:"
+ (holder.itemView.getParent() != null));
}
if (holder.isTmpDetached()) {
throw new IllegalArgumentException("Tmp detached view should be removed "
+ "from RecyclerView before it can be recycled: " + holder);
}
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ " should first call stopIgnoringView(view) before calling recycle.");
}
//noinspection unchecked
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (DEBUG && mCachedViews.contains(holder)) {
throw new IllegalArgumentException("cached view received recycle internal? " +
holder);
}
if (forceRecycle || holder.isRecyclable()) {
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
final int cachedViewSize = mCachedViews.size();
if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}
}
if (!cached) {
addViewHolderToRecycledViewPool(holder);
recycled = true;
}
} else if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists");
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}

其中有一段代码:

1
2
3
4
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
cached = true;
}

RecyclerView在此处使用了内存缓存,通过查看源码,可以发现mViewCacheMax默认为2(使用RecyclerView.setItemViewCacheSize(int size)设置 ),也就是说当某个holder中的View被DetachFromWindow后,不会立即被回收。知道了原因,解决方案也很简单,直接使用RecyclerView.setItemViewCacheSize(0)即可。

多说一句

在review项目代码的时候,发现有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
public BaseItemHolder() {

itemView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
// 此处持有activity引用
}
@Override
public void onViewDetachedFromWindow(View v) {
// 此处并没有把activity引用置空
}
});

这样其实是会导致两个问题:

  1. 首先activity引用一直没法释放,有可能会导致内存泄漏问题
  2. 由于RecyclerView的复用机制,当Holder被回收的时候,该View设置监听状态是不会自动移除的(下图)。因此为了防止状态错乱,最好在View被回收时,还原View的初始状态(清空监听、子View状态设置为View.GONE等)。

参考:

https://stackoverflow.com/questions/33846536/how-can-i-remove-onclicklisteners-from-recyclerviews-viewholders-when-they-are