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<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多次插入之后一次移除,就可以全部移除。

  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的相关的监听。如上述,它它可能发生重复注册,导致释放不了。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容