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