我正在使用LeakCanary 1.3.1-SNAPSHOT.
我发现有关ViewTreeObserver.OnScrollChangedListener设置的泄漏,我修复了它,如下面的代码:
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);
}
然而,LeakCanary仍然将其报告为泄漏,任何想法为什么?
* 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
| [
解决方法:
在View实际从窗口中分离之前,尝试更改删除侦听器的代码,如下所示:
@Override public void onDetachedFromWindow() {
scrollView.getViewTreeObserver().removeOnScrollChangedListener(scrollViewChangeListener);
super.onDetachedFromWindow();
}
原因是,在从窗口分离后,getViewTreeObserver()返回一个不同的实例(“浮动树观察者”),因此您不会从添加它的同一对象中删除您的侦听器.
UPDATE
由于您正在使用子视图的ViewTreeObserver,因此行为稍微复杂一点,一种可能的解决方案是将onAttachStateChangeListener添加到scrollView并从那里添加/删除OnScrollChangedListener.
无论如何,有关泄漏的原因:getViewTreeObserver()在View从窗口中分离后不会返回相同的实例.调用removeOnScrollChangedListener()可能无效,使原始的OnScrollChangedListener仍然附加到旧的ViewTreeObserver,从而泄露了Context.