Descripción de la pérdida de ViewTreeObserver

Estoy utilizando LeakCanary 1.3.1-SNAPSHOT. Encontré una fuga referente a la instalación de ViewTreeObserver.OnScrollChangedListener y ViewTreeObserver.OnScrollChangedListener como en el código siguiente:

  private ViewTreeObserver.OnScrollChangedListener scrollViewChangeListener; @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.inject(this); scrollViewChangeListener = new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { EventDetailsView.this.onScrollChanged(scrollView.getScrollY()); } }; scrollView.getViewTreeObserver() .addOnScrollChangedListener(scrollViewChangeListener); } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); scrollView.getViewTreeObserver().removeOnScrollChangedListener(scrollViewChangeListener); } 

Sin embargo, LeakCanary todavía lo denuncia como una fuga, ¿alguna idea de por qué?

 * com.couchsurfing.mobile.ui.events.detail.EventDetailsScreen$Presenter has leaked: * GC ROOT android.view.inputmethod.InputMethodManager$1.this$0 (anonymous class extends com.android.internal.view.IInputMethodClient$Stub) * references android.view.inputmethod.InputMethodManager.mCurRootView * references com.android.internal.policy.impl.PhoneWindow$DecorView.mAttachInfo * references android.view.View$AttachInfo.mTreeObserver * references android.view.ViewTreeObserver.mOnScrollChangedListeners * references android.view.ViewTreeObserver$CopyOnWriteArray.mData * references java.util.ArrayList.array * references array java.lang.Object[].[0] * references com.couchsurfing.mobile.ui.events.detail.EventDetailsView$1.this$0 (anonymous class implements android.view.ViewTreeObserver$OnScrollChangedListener) * references com.couchsurfing.mobile.ui.events.detail.EventDetailsView.presenter * leaks com.couchsurfing.mobile.ui.events.detail.EventDetailsScreen$Presenter instance* Reference Key: 69d0a429-ae27-48fc-a8e0-033c920dd07c * Device: LGE google Nexus 5 hammerhead * Android Version: 5.1 API: 22 LeakCanary: 1.3.1-SNAPSHOT * Durations: watch=5032ms, gc=165ms, heap dump=2932ms, analysis=29907ms* Details: * Instance of android.view.inputmethod.InputMethodManager$1 | this$0 = android.view.inputmethod.InputMethodManager [id=0x130239c0] | mDescriptor = java.lang.String [id=0x6f5e3f38] | mObject = -1601862176 | mOwner = android.view.inputmethod.InputMethodManager$1 [id=0x13112da0] * Instance of android.view.inputmethod.InputMethodManager | static $staticOverhead = byte[] [id=0x6fe25d29;length=240;size=256] | static CONTROL_START_INITIAL = 256 | static CONTROL_WINDOW_FIRST = 4 | static CONTROL_WINDOW_IS_TEXT_EDITOR = 2 | static CONTROL_WINDOW_VIEW_HAS_FOCUS = 1 | static DEBUG = false | static DISPATCH_HANDLED = 1 | static DISPATCH_IN_PROGRESS = -1 | static DISPATCH_NOT_HANDLED = 0 | static HIDE_IMPLICIT_ONLY = 1 | static HIDE_NOT_ALWAYS = 2 | static INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500 | static MSG_BIND = 2 | static MSG_DUMP = 1 | static MSG_FLUSH_INPUT_EVENT = 7 | static MSG_SEND_INPUT_EVENT = 5 | static MSG_SET_ACTIVE = 4 | static MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9 | static MSG_TIMEOUT_INPUT_EVENT = 6 | static MSG_UNBIND = 3 | static NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1 | static PENDING_EVENT_COUNTER = java.lang.String [id=0x6f5bb948] | static REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE = 0 | static RESULT_HIDDEN = 3 | static RESULT_SHOWN = 2 | static RESULT_UNCHANGED_HIDDEN = 1 | static RESULT_UNCHANGED_SHOWN = 0 | static SHOW_FORCED = 2 | static SHOW_IMPLICIT = 1 | static TAG = java.lang.String [id=0x6f5a76e0] | static sInstance = android.view.inputmethod.InputMethodManager [id=0x130239c0] | mActive = true | mBindSequence = 1523 | mClient = android.view.inputmethod.InputMethodManager$1 [id=0x13112da0] | mCompletions = null | mCurChannel = android.view.InputChannel [id=0x1304a850] | mCurId = java.lang.String [id=0x1325dd80] | mCurMethod = com.android.internal.view.IInputMethodSession$Stub$Proxy [id=0x1304a840] | mCurRootView = com.android.internal.policy.impl.PhoneWindow$DecorView [id=0x12eac000] | mCurSender = android.view.inputmethod.InputMethodManager$ImeInputEventSender [id=0x12c72850] | mCurrentTextBoxAttribute = android.view.inputmethod.EditorInfo [id=0x133036c0] | mCursorAnchorInfo = null | mCursorCandEnd = 0 | mCursorCandStart = 0 | mCursorRect = android.graphics.Rect [id=0x13112d40] | mCursorSelEnd = 0 | mCursorSelStart = 0 | mDummyInputConnection = android.view.inputmethod.BaseInputConnection [id=0x13112dc0] | mFullscreenMode = false | mH = android.view.inputmethod.InputMethodManager$H [id=0x13112de0] | mHasBeenInactive = false | mIInputContext = android.view.inputmethod.InputMethodManager$ControlledInputConnectionWrapper [id=0x13113310] | mLastSentUserActionNotificationSequenceNumber = -1 | mMainLooper = android.os.Looper [id=0x12c76be0] | mNextServedView = com.couchsurfing.mobile.ui.drawer.DrawerView [id=0x131f8c00] | mNextUserActionNotificationSequenceNumber = 1 | mPendingEventPool = android.util.Pools$SimplePool [id=0x13110fe0] | mPendingEvents = android.util.SparseArray [id=0x13112d80] | mRequestUpdateCursorAnchorInfoMonitorMode = 0 | mServedConnecting = false | mServedInputConnection = null | mServedInputConnectionWrapper = null | mServedView = com.couchsurfing.mobile.ui.drawer.DrawerView [id=0x131f8c00] | mService = com.android.internal.view.IInputMethodManager$Stub$Proxy [id=0x13110fc0] | mTmpCursorRect = android.graphics.Rect [id=0x13112d20] | mViewToScreenMatrix = android.graphics.Matrix [id=0x13110fd0] | mViewTopLeft = int[] [id=0x13112d60;length=2;size=24] * Instance of com.android.internal.policy.impl.PhoneWindow$DecorView | mActionMode = null | mActionModePopup = null | mActionModeView = null | mBackgroundFallback = com.android.internal.widget.BackgroundFallback [id=0x12fdd8e0] | mBackgroundPadding = android.graphics.Rect [id=0x12ffd9a0] | mBarEnterExitDuration = 250 | mChanging = false | mDefaultOpacity = -1 | mDownY = 0 | mDrawingBounds = android.graphics.Rect [id=0x12ffd980] | mFeatureId = -1 | mFrameOffsets = android.graphics.Rect [id=0x12ffd9e0] | mFramePadding = android.graphics.Rect [id=0x12ffd9c0] | mHideInterpolator = android.view.animation.PathInterpolator [id=0x12ffdb00] | mLastBottomInset = 144 | mLastHasBottomStableInset = true | mLastHasTopStableInset = true | mLastRightInset = 0 | mLastTopInset = 75 | mLastWindowFlags = -2122252032 | mMenuBackground = null | mNavigationColorViewState = com.android.internal.policy.impl.PhoneWindow$ColorViewState [id=0x12ff2c70] | mNavigationGuard = null | mRootScrollY = 0 | mShowActionModePopup = null | mShowInterpolator = android.view.animation.PathInterpolator [id=0x12ffda60] | mStatusColorViewState = com.android.internal.policy.impl.PhoneWindow$ColorViewState [id=0x12ff2c40] | mStatusGuard = null | mWatchingForMenu = false | this$0 = com.android.internal.policy.impl.PhoneWindow [id=0x12db9e00] | mForeground = null | mForegroundBoundsChanged = true | mForegroundGravity = 119 | mForegroundInPadding = true | mForegroundPaddingBottom = 0 | mForegroundPaddingLeft = 0 | mForegroundPaddingRight = 0 | mForegroundPaddingTop = 0 | mForegroundTintList = null | mForegroundTintMode = null | mHasForegroundTint = false | mHasForegroundTintMode = false | mMatchParentChildren = java.util.ArrayList [id=0x12ffd960] | mMeasureAllChildren = false | mOverlayBounds = android.graphics.Rect [id=0x12ffd940] | mSelfBounds = android.graphics.Rect [id=0x12ffd920] | mAnimationListener = null | mCachePaint = null | mChildAcceptsDrag = false | mChildCountWithTransientState = 0 | mChildTransformation = null | mChildren = android.view.View[] [id=0x130064c0;length=12] | mChildrenCount = 3 | mCurrentDrag = null | mCurrentDragView = null | mDisappearingChildren = null | mDragNotifiedChildren = null | mFirstHoverTarget = null | mFirstTouchTarget = null | mFocused = android.widget.LinearLayout [id=0x12eac800] | mGroupFlags = 2375763 | mHoveredSelf = false | mInvalidateRegion = null | mInvalidationTransformation = null | mLastTouchDownIndex = 0 | mLastTouchDownTime = 137539724 | mLastTouchDownX = 605.0 | mLastTouchDownY = 1177.0 | mLayoutAnimationController = null | mLayoutCalledWhileSuppressed = false | mLayoutMode = 0 | mLayoutTransitionListener = android.view.ViewGroup$3 [id=0x12fdd850] | mLocalPoint = null | mNestedScrollAxes = 0 | mOnHierarchyChangeListener = null | mPersistentDrawingCache = 2 | mPreSortedChildren = null | mSuppressLayout = false | mTempPoint = float[] [id=0x12c0a220;length=2;size=24] | mTransition = null | mTransitioningViews = null | mVisibilityChangingChildren = null | mAccessibilityCursorPosition = -1 | mAccessibilityDelegate = null | mAccessibilityTraversalAfterId = -1 | mAccessibilityTraversalBeforeId = -1 | mAccessibilityViewId = -1 | mAnimator = null | mAttachInfo = android.view.View$AttachInfo [id=0x12c4fcc0] | mAttributes = null | mBackground = android.graphics.drawable.ColorDrawable [id=0x13014f80] | mBackgroundRenderNode = android.view.RenderNode [id=0x12c73740] | mBackgroundResource = 0 | mBackgroundSizeChanged = false | mBackgroundTint = null | mBottom = 1920 | mCachingFailed = false | mClipBounds = null | mContentDescription = null | mContext = com.couchsurfing.mobile.ui.MainActivity [id=0x12db9c80] | mCurrentAnimation = null | mDrawableState = null | mDrawingCache = null | mDrawingCacheBackgroundColor = 0 | mFloatingTreeObserver = null | mGhostView = null | mHasPerformedLongPress = false | mID = -1 | mInputEventConsistencyVerifier = null | mKeyedTags = null | mLabelForId = -1 | mLastIsOpaque = true | mLayerPaint = null | mLayerType = 0 | mLayoutInsets = null | mLayoutParams = android.view.WindowManager$LayoutParams [id=0x12f1f7e0] | mLeft = 0 | mLeftPaddingDefined = true | mListenerInfo = android.view.View$ListenerInfo [id=0x13109940] | mMatchIdPredicate = null | mMatchLabelForPredicate = null | mMeasureCache = android.util.LongSparseLongArray [id=0x13400120] | mMeasuredHeight = 1920 | mMeasuredWidth = 1080 | mMinHeight = 0 | mMinWidth = 0 | mNestedScrollingParent = null | mNextFocusDownId = -1 | mNextFocusForwardId = -1 | mNextFocusLeftId = -1 | mNextFocusRightId = -1 | mNextFocusUpId = -1 | mOldHeightMeasureSpec = 1073743744 | mOldWidthMeasureSpec = 1073742904 | mOutlineProvider = android.view.ViewOutlineProvider$1 [id=0x6fcd7240] | mOverScrollMode = 1 | mOverlay = null | mPaddingBottom = 0 | mPaddingLeft = 0 | mPaddingRight = 0 | mPaddingTop = 0 | mParent = android.view.ViewRootImpl [id=0x13313400] | mPendingCheckForLongPress = null | mPendingCheckForTap = null | mPerformClick = null | mPrivateFlags = 25201976 | mPrivateFlags2 = 1611867680 | mPrivateFlags3 = 4 | mRecreateDisplayList = false | mRenderNode = android.view.RenderNode [id=0x12ffd880] | mResources = android.content.res.Resources [id=0x12c078e0] | mRight = 1080 | mRightPaddingDefined = true | mScrollCache = null | mScrollX = 0 | mScrollY = 0 | mSendViewScrolledAccessibilityEvent = null | mSendViewStateChangedAccessibilityEvent = null | mSendingHoverAccessibilityEvents = false | mStateListAnimator = null | mSystemUiVisibility = 0 | mTag = null | mTempNestedScrollConsumed = null | mTop = 0 | mTouchDelegate = null | mTouchSlop = 24 | mTransformationInfo = android.view.View$TransformationInfo [id=0x1349e7c0] | mTransientStateCount = 0 | mTransitionName = null | mUnscaledDrawingCache = null | mUnsetPressedState = null | mUserPaddingBottom = 0 | mUserPaddingEnd = -2147483648 | mUserPaddingLeft = 0 | mUserPaddingLeftInitial = 0 | mUserPaddingRight = 0 | mUserPaddingRightInitial = 0 | mUserPaddingStart = -2147483648 | mVerticalScrollFactor = 0.0 | mVerticalScrollbarPosition = 0 | mViewFlags = 402655360 | mWindowAttachCount = 1 * Instance of android.view.View$AttachInfo | mAccessibilityFetchFlags = 0 | mAccessibilityFocusDrawable = null | mAccessibilityWindowId = 2147483647 | mApplicationScale = 1.0 | mCanvas = null | mContentInsets = android.graphics.Rect [id=0x13364ee0] | mDebugLayout = false | mDisabledSystemUiVisibility = 0 | mDisplay = android.view.Display [id=0x12f75b50] | mDisplayState = 2 | mDrawingTime = 137551407 | mForceReportNewAttributes = false | mGivenInternalInsets = android.view.ViewTreeObserver$InternalInsetsInfo [id=0x13364f40] | mGlobalSystemUiVisibility = 0 | mHandler = android.view.ViewRootImpl$ViewRootHandler [id=0x13364d00] | mHardwareAccelerated = true | mHardwareAccelerationRequested = true | mHardwareRenderer = android.view.ThreadedRenderer [id=0x13323dc0] | mHasNonEmptyGivenInternalInsets = false | mHasSystemUiListeners = true | mHasWindowFocus = true | mHighContrastText = false | mIWindowId = null | mIgnoreDirtyState = false | mInTouchMode = true | mInvalidateChildLocation = int[] [id=0x13370060;length=2;size=24] | mKeepScreenOn = false | mKeyDispatchState = android.view.KeyEvent$DispatcherState [id=0x13364fc0] | mOverscanInsets = android.graphics.Rect [id=0x13364ec0] | mOverscanRequested = false | mPanelParentWindowToken = null | mPendingAnimatingRenderNodes = null | mPoint = android.graphics.Point [id=0x133582f0] | mRecomputeGlobalAttributes = false | mRootCallbacks = android.view.ViewRootImpl [id=0x13313400] | mRootView = com.android.internal.policy.impl.PhoneWindow$DecorView [id=0x12eac000] | mScalingRequired = false | mScrollContainers = java.util.ArrayList [id=0x13364fa0] | mSession = android.view.IWindowSession$Stub$Proxy [id=0x13358290] | mSetIgnoreDirtyState = true | mStableInsets = android.graphics.Rect [id=0x13364f20] | mSystemUiVisibility = 0 | mTempArrayList = java.util.ArrayList [id=0x133701a0] | mTmpInvalRect = android.graphics.Rect [id=0x133700c0] | mTmpLocation = int[] [id=0x13370080;length=2;size=24] | mTmpMatrix = android.graphics.Matrix [id=0x133582d0] | mTmpOutline = android.graphics.Outline [id=0x13370180] | mTmpRectList = java.util.ArrayList [id=0x13370120] | mTmpTransformLocation = float[] [id=0x133700a0;length=2;size=24] | mTmpTransformRect = android.graphics.RectF [id=0x133700e0] | mTmpTransformRect1 = android.graphics.RectF [id=0x13370100] | mTmpTransformation = android.view.animation.Transformation [id=0x13370140] | mTransparentLocation = int[] [id=0x13370040;length=2;size=24] | mTreeObserver = android.view.ViewTreeObserver [id=0x133656c0] | mTurnOffWindowResizeAnim = false | mUnbufferedDispatchRequested = false | mUse32BitDrawingCache = true | mViewRequestingLayout = null | mViewRootImpl = android.view.ViewRootImpl [id=0x13313400] | mViewScrollChanged = false | mViewVisibilityChanged = false | mVisibleInsets = android.graphics.Rect [id=0x13364f00] | mWindow = android.view.ViewRootImpl$W [id=0x13364e80] | mWindowId = null | mWindowLeft = 0 | mWindowToken = android.view.ViewRootImpl$W [id=0x13364e80] | mWindowTop = 0 | mWindowVisibility = 0 * Instance of android.view.ViewTreeObserver | mAlive = true | mOnComputeInternalInsetsListeners = null | mOnDrawListeners = null | mOnEnterAnimationCompleteListeners = null | mOnGlobalFocusListeners = null | mOnGlobalLayoutListeners = android.view.ViewTreeObserver$CopyOnWriteArray [id=0x1315a300] | mOnPreDrawListeners = android.view.ViewTreeObserver$CopyOnWriteArray [id=0x13345760] | mOnScrollChangedListeners = android.view.ViewTreeObserver$CopyOnWriteArray [id=0x12fd3220] | mOnTouchModeChangeListeners = java.util.concurrent.CopyOnWriteArrayList [id=0x133a1420] | mOnWindowAttachListeners = null | mOnWindowFocusListeners = null | mOnWindowShownListeners = null | mWindowShown = false * Instance of android.view.ViewTreeObserver$CopyOnWriteArray | mAccess = android.view.ViewTreeObserver$CopyOnWriteArray$Access [id=0x12fa1960] | mData = java.util.ArrayList [id=0x12fd3240] | mDataCopy = null | mStart = false * Instance of java.util.ArrayList | static $staticOverhead = byte[] [id=0x6fcffb29;length=16;size=32] | static MIN_CAPACITY_INCREMENT = 12 | static serialVersionUID = 8683452581122892189 | array = java.lang.Object[] [id=0x13094a40;length=12] | size = 1 | modCount = 1 * Array of java.lang.Object[] | [0] = com.couchsurfing.mobile.ui.events.detail.EventDetailsView$1 [id=0x12fa1950] | [1] = null | [2] = null | [3] = null | [4] = null | [5] = null | [6] = null | [7] = null | [8] = null | [9] = null | [10] = null | [ 

Intente cambiar el código que elimina el oyente para que se ejecute antes de que la vista se separe de la ventana, de la siguiente manera:

 @Override public void onDetachedFromWindow() { scrollView.getViewTreeObserver().removeOnScrollChangedListener(scrollViewChangeListener); super.onDetachedFromWindow(); } 

La razón es que después de ser separado de una ventana, getViewTreeObserver() devuelve una instancia diferente (el "observador de árbol flotante"), por lo que no va a eliminar su oyente del mismo objeto en el que lo agregó.


ACTUALIZAR

Dado que está utilizando un ViewTreeObserver de una vista secundaria, el comportamiento es un poco más complejo y una solución posible implicaría agregar un OnAttachStateChangeListener a su scrollView y añadir / quitar su OnScrollChangedListener de allí.

De todos modos en lo que respecta a la razón por la que había una fuga: getViewTreeObserver() no va a devolver la misma instancia después de la View se ha desvinculado de la ventana. Llamar removeOnScrollChangedListener() puede no tener ningún efecto, manteniendo su OnScrollChangedListener original todavía conectado a la antigua ViewTreeObserver , y por lo tanto la filtración de su Context .

ajuste el acceso variable por OnScrollChangedListener con WeakReference, o puede llamar a removeOnScrollChanged () de onDestroyView. eliminar el oyente en onDetach tal vez demasiado tarde, su vista tal vez eliminar por el sistema de árbol de vista

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.