ListView与ViewTreeObersever结合引起的内存泄漏

结论:如果一个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。

  1. ViewTreeObersever的监听所使用的容器都是CopyOnWriteArrayList
public final class ViewTreeObserver {
    // Recursive listeners use CopyOnWriteArrayList
    private CopyOnWriteArrayList mOnWindowFocusListeners;
    private CopyOnWriteArrayList mOnWindowAttachListeners;
    private CopyOnWriteArrayList mOnGlobalFocusListeners;
    private CopyOnWriteArrayList mOnTouchModeChangeListeners;
    private CopyOnWriteArrayList
            mOnEnterAnimationCompleteListeners;

    // Non-recursive listeners use CopyOnWriteArray
    // Any listener invoked from ViewRootImpl.performTraversals() should not be recursive
    private CopyOnWriteArray mOnGlobalLayoutListeners;
    private CopyOnWriteArray mOnComputeInternalInsetsListeners;
    private CopyOnWriteArray mOnScrollChangedListeners;
    private CopyOnWriteArray mOnPreDrawListeners;
    private CopyOnWriteArray mOnWindowShownListeners;

CopyOnWriteArrayList与ArrayList存在以下差异是:
1.ArrayList的一个item多次插入之后一次移除,就可以全部移除。

  1. 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并不是成对出现,大家要在里做注册与解注册监听,以及初始化和反初始化的时候要慎重,最好要用状态值控制。

  1. ViewTreeObersever的监听的注册与解注册要谨慎,要确保能够完成解注册。
  2. CopyOnWriteArrayList的remove与ArrayList的remove效果有差距,以后用到要谨慎。
    4.对于absListView的子类互相嵌套的情况要慎用。absListView本身在onAttachToWindow时要注册ViewTreeObersever的相关的监听。如上述,它它可能发生重复注册,导致释放不了。

你可能感兴趣的:(ListView与ViewTreeObersever结合引起的内存泄漏)