结论:如果一个View作为ListView的Item,并且这个View在OnAttachToWindow与OnDetachFromWindow中进行ViewTreeObersever相关状态的注册与解注册,那么这个ListView所在的Fragment在销毁的时候,将会导致内存泄漏。
原因:
1.ListView的字view进行复用的时候,只会走attachToWindow而不会先走detachFromWindow,再走attachToWindow
ListView 子View进行复用的代码
堆栈
android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3578)
android.view.ViewGroup.addViewInner(ViewGroup.java:5442)
android.view.ViewGroup.addViewInLayout(ViewGroup.java:5375)
android.widget.ListView.setupChild(ListView.java:2172)
android.widget.ListView.makeAndAddView(ListView.java:2093)
android.widget.ListView.fillDown(ListView.java:799)
android.widget.ListView.fillSpecific(ListView.java:1510)
android.widget.ListView.layoutChildren(ListView.java:1808)
android.widget.AbsListView.onLayout(AbsListView.java:2211)
代码ViewGroup.addViewInLayout
protected boolean addViewInLayout(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
child.mParent = null;
addViewInner(child, index, params, preventRequestLayout);
child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
return true;
}
因为ListView的子View复用的过程中没有涉及到View的删除,仅仅只是这个子View的index发生变化,所有并没有RemoveView的过程,没有走到detachFromWindow。复用的过程核心是addViewInLayout,会导致走到attachToWindow。
- ViewTreeObersever的监听所使用的容器都是CopyOnWriteArrayList
public final class ViewTreeObserver {
// Recursive listeners use CopyOnWriteArrayList
private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
private CopyOnWriteArrayList<OnEnterAnimationCompleteListener>
mOnEnterAnimationCompleteListeners;
// Non-recursive listeners use CopyOnWriteArray
// Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
private CopyOnWriteArray<OnGlobalLayoutListener> mOnGlobalLayoutListeners;
private CopyOnWriteArray<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners;
private CopyOnWriteArray<OnScrollChangedListener> mOnScrollChangedListeners;
private CopyOnWriteArray<OnPreDrawListener> mOnPreDrawListeners;
private CopyOnWriteArray<OnWindowShownListener> mOnWindowShownListeners;
CopyOnWriteArrayList与ArrayList存在以下差异是:
1.ArrayList的一个item多次插入之后一次移除,就可以全部移除。
- CopyOnWriteArrayList 插入几次,就需要移除几次,才能全部移除。
ArrayList.remove 一次移除所有都移除
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
CopyOnWriteArrayList.remove,只移除了第一个
public boolean remove(Object o) {
Object[] snapshot = getArray();
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, index);
}
结论:
1.View的onAttachToWindow和onDetachFromWinwo并不是成对出现,大家要在里做注册与解注册监听,以及初始化和反初始化的时候要慎重,最好要用状态值控制。
- ViewTreeObersever的监听的注册与解注册要谨慎,要确保能够完成解注册。
- CopyOnWriteArrayList的remove与ArrayList的remove效果有差距,以后用到要谨慎。
4.对于absListView的子类互相嵌套的情况要慎用。absListView本身在onAttachToWindow时要注册ViewTreeObersever的相关的监听。如上述,它它可能发生重复注册,导致释放不了。