Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ViewGroup.ViewLocationHolder leak in Android P #1081

Closed
seyedjafariy opened this issue Aug 6, 2018 · 31 comments
Closed

ViewGroup.ViewLocationHolder leak in Android P #1081

seyedjafariy opened this issue Aug 6, 2018 · 31 comments
Milestone

Comments

@seyedjafariy
Copy link

@seyedjafariy seyedjafariy commented Aug 6, 2018

I'm testing my app with the new leak canary and on different devices (as well as virtual devices).
everything looks fine on all devices except one device which is the only one running Android 8.
the analyzing process looks fine but whenever it finishes it shows a few leaks (which other devices won't show!).

every time I check the trace I end up either with ReportFragment or InputMethodManager.

is anything actually leaking? is this an excluded leak?

thanks in advance

photo_2018-08-06_10-16-39

@pyricau
Copy link
Member

@pyricau pyricau commented Aug 7, 2018

Can you share the full text leaktrace (click ... in the top right)?

@seyedjafariy
Copy link
Author

@seyedjafariy seyedjafariy commented Aug 8, 2018

sure thing:

Leak Trace.txt

here is a heap dump file too. but it's not for this particular leak, for a similar one.just in case.

heap dump

@mezpahlan
Copy link

@mezpahlan mezpahlan commented Aug 8, 2018

I'm seeing a similar thing too from ReportFragment. Although in our case I am more inclined to believe we are doing something stupid with our app 😃. Here's our leak trace for comparison My.Leak.Trace.txt.

I'm happy to raise this in a different issue if it's better that way.

@SUPERCILEX
Copy link

@SUPERCILEX SUPERCILEX commented Aug 11, 2018

For me, the AccessibilityManager is holding onto it: leak.txt

@kuriandungu
Copy link

@kuriandungu kuriandungu commented Aug 14, 2018

Similar issue on Oreo(S8+) and Android Pie(Pixel2XL).
I have an app with TabLayout navigating via ViewPager having Fragments. Simply opening the app and swiping to the 3 Tabs and then closing causes a leak MainActivity and ReportFragment Leaks. I've googled but have no idea how to resolve them as yet.
reportfragment leak.txt
Uploading Mainactivityleak.txt…

Note that your work Guys, is really highly appreciated. Thanks.

@linhphan0108
Copy link

@linhphan0108 linhphan0108 commented Aug 15, 2018

hi I got the leak on Pixel Emulator running Android 8.1 too, here is my leak trace

@ZacSweers
Copy link
Contributor

@ZacSweers ZacSweers commented Aug 18, 2018

I've filed a bug with a repro case here for anyone that wants to follow: https://issuetracker.google.com/issues/112792715

@PaulWoitaschek
Copy link
Contributor

@PaulWoitaschek PaulWoitaschek commented Aug 22, 2018

Am I the only one or did they hide the issue from the public? oO

@JakeWharton
Copy link
Member

@JakeWharton JakeWharton commented Aug 22, 2018

@PaulWoitaschek
Copy link
Contributor

@PaulWoitaschek PaulWoitaschek commented Aug 22, 2018

Thanks, found the mail. If someone is wondering:

pw...@google.com added comment #9:
This is in the framework, not in the support library. Thanks for reporting it. I'm going to move it to the correct internal component, which will cut off public access to it (sorry).

@svenjacobs
Copy link

@svenjacobs svenjacobs commented Sep 20, 2018

So this is an Android issue starting with Oreo? It also occurs on a Pixel with Android Pie. Will this be fixed in a monthly security update or do we have to wait for Android 9.1 or even 10?

@ZacSweers
Copy link
Contributor

@ZacSweers ZacSweers commented Sep 20, 2018

You'll have to wait till it's fixed in AOSP. It's such a nuisance that I've actually just disabled leakcanary on P all together :/

@pyricau
Copy link
Member

@pyricau pyricau commented Oct 15, 2018

For late readers, this is the proper leaktrace:

In com.ddsafricaltd.fieldmetrics:6.0:2018081101.
* android.arch.lifecycle.ReportFragment has leaked:
* static ViewGroup$ViewLocationHolder.!(sPool)!
* ↳ Pools$SynchronizedPool.!(mPool)!
* ↳ array Object[].!([3])!
* ↳ ViewGroup$ViewLocationHolder.!(mRoot)!
* ↳ LinearLayout.mContext
* ↳ MainActivity.mFragments
* ↳ FragmentController.mHost
* ↳ Activity$HostCallbacks.mFragmentManager
* ↳ FragmentManagerImpl.mAdded
* ↳ ArrayList.elementData
* ↳ array Object[].[0]
* ↳ ReportFragment

* Reference Key: b114064f-db10-4fab-8166-e08179003461
* Device: Google google Pixel 2 XL taimen
* Android Version: 9 API: 28 LeakCanary: 1.6.1 26145bf
* Durations: watch=75812ms, gc=235ms, heap dump=3866ms, analysis=7645ms

* Details:
* Class android.view.ViewGroup$ViewLocationHolder
|   static $class$dexTypeIndex = 4622
|   static $class$ifTable = java.lang.Object[2]@321728464 (0x132d2fd0)
|   static $class$dexCache = java.lang.DexCache@1893249352 (0x70d8b148)
|   static $class$accessFlags = 524288
|   static $class$name = null
|   static $class$componentType = null
|   static $class$iFields = 535328457232
|   static $class$clinitThreadId = 4551
|   static $class$numReferenceStaticFields = 1
|   static $class$superClass = java.lang.Object
|   static $classOverhead = byte[124]@319405305 (0x1309bcf9)
|   static $class$objectSizeAllocFastPath = 24
|   static $class$dexClassDefIndex = 2017
|   static $class$primitiveType = 131072
|   static $class$classSize = 268
|   static $class$vtable = null
|   static MAX_POOL_SIZE = 32
|   static $class$referenceInstanceOffsets = 7
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 535328457304
|   static COMPARISON_STRATEGY_LOCATION = 2
|   static $class$extData = null
|   static $class$objectSize = 24
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 9
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 0
|   static $class$numReferenceInstanceFields = 3
|   static $class$sFields = 535328457144
|   static COMPARISON_STRATEGY_STRIPE = 1
|   static sComparisonStrategy = 1
|   static sPool = android.util.Pools$SynchronizedPool@321728488 (0x132d2fe8)
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 12
* Instance of android.util.Pools$SynchronizedPool
|   static $class$dexTypeIndex = 4136
|   static $class$ifTable = java.lang.Object[2]@1893372152 (0x70da90f8)
|   static $class$dexCache = java.lang.DexCache@1893249352 (0x70d8b148)
|   static $class$accessFlags = 524289
|   static $class$name = "android.util.Pools$SynchronizedPool"
|   static $class$componentType = null
|   static $class$iFields = 1899522280
|   static $class$clinitThreadId = 0
|   static $class$numReferenceStaticFields = 0
|   static $class$superClass = android.util.Pools$SimplePool
|   static $classOverhead = byte[116]@1896706545 (0x710d71f1)
|   static $class$objectSizeAllocFastPath = 24
|   static $class$dexClassDefIndex = 5340
|   static $class$primitiveType = 131072
|   static $class$classSize = 240
|   static $class$vtable = null
|   static $class$referenceInstanceOffsets = 5
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 1902393744
|   static $class$extData = null
|   static $class$objectSize = 20
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 2
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 536870912
|   static $class$numReferenceInstanceFields = 1
|   static $class$sFields = 0
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 4
|   mLock = java.lang.Object@321728656 (0x132d3090)
|   mPool = java.lang.Object[32]@321728512 (0x132d3000)
|   mPoolSize = 4
|   shadow$_klass_ = android.util.Pools$SynchronizedPool
|   shadow$_monitor_ = 0
* Array of java.lang.Object[]
|   [0] = android.view.ViewGroup$ViewLocationHolder@321728664 (0x132d3098)
|   [1] = android.view.ViewGroup$ViewLocationHolder@321728688 (0x132d30b0)
|   [2] = android.view.ViewGroup$ViewLocationHolder@321728712 (0x132d30c8)
|   [3] = android.view.ViewGroup$ViewLocationHolder@321728736 (0x132d30e0)
|   [4] = null
|   [5] = null
|   [6] = null
|   [7] = null
|   [8] = null
|   [9] = null
|   [10] = null
|   [11] = null
|   [12] = null
|   [13] = null
|   [14] = null
|   [15] = null
|   [16] = null
|   [17] = null
|   [18] = null
|   [19] = null
|   [20] = null
|   [21] = null
|   [22] = null
|   [23] = null
|   [24] = null
|   [25] = null
|   [26] = null
|   [27] = null
|   [28] = null
|   [29] = null
|   [30] = null
|   [31] = null
* Instance of android.view.ViewGroup$ViewLocationHolder
|   static $class$dexTypeIndex = 4622
|   static $class$ifTable = java.lang.Object[2]@321728464 (0x132d2fd0)
|   static $class$dexCache = java.lang.DexCache@1893249352 (0x70d8b148)
|   static $class$accessFlags = 524288
|   static $class$name = null
|   static $class$componentType = null
|   static $class$iFields = 535328457232
|   static $class$clinitThreadId = 4551
|   static $class$numReferenceStaticFields = 1
|   static $class$superClass = java.lang.Object
|   static $classOverhead = byte[124]@319405305 (0x1309bcf9)
|   static $class$objectSizeAllocFastPath = 24
|   static $class$dexClassDefIndex = 2017
|   static $class$primitiveType = 131072
|   static $class$classSize = 268
|   static $class$vtable = null
|   static MAX_POOL_SIZE = 32
|   static $class$referenceInstanceOffsets = 7
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 535328457304
|   static COMPARISON_STRATEGY_LOCATION = 2
|   static $class$extData = null
|   static $class$objectSize = 24
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 9
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 0
|   static $class$numReferenceInstanceFields = 3
|   static $class$sFields = 535328457144
|   static COMPARISON_STRATEGY_STRIPE = 1
|   static sComparisonStrategy = 1
|   static sPool = android.util.Pools$SynchronizedPool@321728488 (0x132d2fe8)
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 12
|   mLayoutDirection = 0
|   mLocation = android.graphics.Rect@321728760 (0x132d30f8)
|   mRoot = android.widget.LinearLayout@321728784 (0x132d3110)
|   mView = null
|   shadow$_klass_ = android.view.ViewGroup$ViewLocationHolder
|   shadow$_monitor_ = 0
* Instance of android.widget.LinearLayout
|   static $class$dexTypeIndex = 5524
|   static $class$ifTable = java.lang.Object[10]@1893739392 (0x70e02b80)
|   static VERTICAL_GRAVITY_COUNT = 4
|   static $class$dexCache = java.lang.DexCache@1893249352 (0x70d8b148)
|   static HORIZONTAL = 0
|   static $class$accessFlags = 524289
|   static $class$name = "android.widget.LinearLayout"
|   static SHOW_DIVIDER_BEGINNING = 1
|   static sCompatibilityDone = true
|   static $class$componentType = null
|   static $class$iFields = 1899166828
|   static INDEX_FILL = 3
|   static $class$clinitThreadId = 0
|   static $class$numReferenceStaticFields = 0
|   static INDEX_BOTTOM = 2
|   static VERTICAL = 1
|   static $class$superClass = android.view.ViewGroup
|   static $classOverhead = byte[8004]@1894502489 (0x70ebd059)
|   static $class$objectSizeAllocFastPath = 680
|   static INDEX_TOP = 1
|   static $class$dexClassDefIndex = 6000
|   static $class$primitiveType = 131072
|   static $class$classSize = 8174
|   static $class$vtable = null
|   static $class$referenceInstanceOffsets = -1073741824
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 1901153336
|   static $class$extData = null
|   static INDEX_CENTER_VERTICAL = 0
|   static $class$objectSize = 676
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 11
|   static sRemeasureWeightedChildren = false
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 536870912
|   static $class$numReferenceInstanceFields = 3
|   static $class$sFields = 1899166616
|   static SHOW_DIVIDER_END = 4
|   static SHOW_DIVIDER_NONE = 0
|   static $class$status = -536870912
|   static SHOW_DIVIDER_MIDDLE = 2
|   static $class$copiedMethodsOffset = 63
|   mAllowInconsistentMeasurement = false
|   mBaselineAligned = true
|   mBaselineAlignedChildIndex = -1
|   mBaselineChildTop = 0
|   mDivider = null
|   mDividerHeight = 0
|   mDividerPadding = 0
|   mDividerWidth = 0
|   mGravity = 8388659
|   mLayoutDirection = 0
|   mMaxAscent = null
|   mMaxDescent = null
|   mOrientation = 1
|   mShowDividers = 0
|   mTotalLength = 172
|   mUseLargestChild = false
|   mWeightSum = -1.0
|   mAnimationListener = null
|   mCachePaint = null
|   mChildCountWithTransientState = 0
|   mChildTransformation = null
|   mChildUnhandledKeyListeners = 0
|   mChildren = android.view.View[12]@321729464 (0x132d33b8)
|   mChildrenCount = 1
|   mChildrenInterestedInDrag = null
|   mCurrentDragChild = null
|   mCurrentDragStartEvent = null
|   mDefaultFocus = null
|   mDisappearingChildren = null
|   mFirstHoverTarget = null
|   mFirstTouchTarget = null
|   mFocused = null
|   mFocusedInCluster = null
|   mGroupFlags = 2244691
|   mHoveredSelf = false
|   mInvalidateRegion = null
|   mInvalidationTransformation = null
|   mIsInterestedInDrag = false
|   mLastTouchDownIndex = -1
|   mLastTouchDownTime = 0
|   mLastTouchDownX = 0.0
|   mLastTouchDownY = 0.0
|   mLayoutAnimationController = null
|   mLayoutCalledWhileSuppressed = false
|   mLayoutMode = -1
|   mLayoutTransitionListener = android.view.ViewGroup$4@321729528 (0x132d33f8)
|   mLocalPoint = null
|   mNestedScrollAxes = 0
|   mOnHierarchyChangeListener = null
|   mPersistentDrawingCache = 2
|   mPreSortedChildren = null
|   mSuppressLayout = false
|   mTempPoint = null
|   mTooltipHoverTarget = null
|   mTooltipHoveredSelf = false
|   mTransientIndices = null
|   mTransientViews = null
|   mTransition = null
|   mTransitioningViews = null
|   mVisibilityChangingChildren = null
|   mAccessibilityCursorPosition = -1
|   mAccessibilityDelegate = null
|   mAccessibilityPaneTitle = null
|   mAccessibilityTraversalAfterId = -1
|   mAccessibilityTraversalBeforeId = -1
|   mAccessibilityViewId = 212
|   mAnimator = null
|   mAttachInfo = null
|   mAttributes = null
|   mAutofillHints = null
|   mAutofillId = null
|   mAutofillViewId = -1
|   mBackground = android.graphics.drawable.GradientDrawable@321729544 (0x132d3408)
|   mBackgroundRenderNode = android.view.RenderNode@321729632 (0x132d3460)
|   mBackgroundResource = 0
|   mBackgroundSizeChanged = false
|   mBackgroundTint = null
|   mBottom = 172
|   mCachingFailed = false
|   mClipBounds = null
|   mContentDescription = null
|   mContext = com.ddsafricaltd.fieldmetrics.activities.MainActivity@321729656 (0x132d3478)
|   mCurrentAnimation = null
|   mDefaultFocusHighlight = null
|   mDefaultFocusHighlightCache = null
|   mDefaultFocusHighlightEnabled = true
|   mDefaultFocusHighlightSizeChanged = true
|   mDrawableState = int[2]@1894217152 (0x70e775c0)
|   mDrawingCache = null
|   mDrawingCacheBackgroundColor = 0
|   mFloatingTreeObserver = null
|   mForegroundInfo = null
|   mFrameMetricsObservers = null
|   mGhostView = null
|   mHasPerformedLongPress = false
|   mID = -1
|   mIgnoreNextUpEvent = false
|   mInContextButtonPress = false
|   mInputEventConsistencyVerifier = null
|   mKeyedTags = null
|   mLabelForId = -1
|   mLastIsOpaque = false
|   mLayerPaint = null
|   mLayerType = 0
|   mLayoutInsets = null
|   mLayoutParams = android.view.WindowManager$LayoutParams@321730272 (0x132d36e0)
|   mLeft = 0
|   mLeftPaddingDefined = false
|   mListenerInfo = null
|   mLongClickX = NaN
|   mLongClickY = NaN
|   mMatchIdPredicate = null
|   mMatchLabelForPredicate = null
|   mMeasureCache = android.util.LongSparseLongArray@321730456 (0x132d3798)
|   mMeasuredHeight = 172
|   mMeasuredWidth = 854
|   mMinHeight = 0
|   mMinWidth = 0
|   mNestedScrollingParent = null
|   mNextClusterForwardId = -1
|   mNextFocusDownId = -1
|   mNextFocusForwardId = -1
|   mNextFocusLeftId = -1
|   mNextFocusRightId = -1
|   mNextFocusUpId = -1
|   mOldHeightMeasureSpec = -2147481037
|   mOldWidthMeasureSpec = -2147482528
|   mOutlineProvider = android.view.ViewOutlineProvider$1@1893475968 (0x70dc2680)
|   mOverScrollMode = 1
|   mOverlay = null
|   mPaddingBottom = 0
|   mPaddingLeft = 0
|   mPaddingRight = 0
|   mPaddingTop = 0
|   mParent = null
|   mPendingCheckForLongPress = null
|   mPendingCheckForTap = null
|   mPerformClick = null
|   mPointerIcon = null
|   mPrivateFlags = -2128607184
|   mPrivateFlags2 = 1611867680
|   mPrivateFlags3 = 0
|   mRecreateDisplayList = false
|   mRenderNode = android.view.RenderNode@321730480 (0x132d37b0)
|   mResources = android.content.res.Resources@321730504 (0x132d37c8)
|   mRight = 854
|   mRightPaddingDefined = false
|   mRoundScrollbarRenderer = null
|   mRunQueue = null
|   mScrollCache = null
|   mScrollIndicatorDrawable = null
|   mScrollX = 0
|   mScrollY = 0
|   mSendViewScrolledAccessibilityEvent = null
|   mSendingHoverAccessibilityEvents = false
|   mStartActivityRequestWho = null
|   mStateListAnimator = null
|   mSystemUiVisibility = 0
|   mTag = null
|   mTempNestedScrollConsumed = null
|   mTooltipInfo = null
|   mTop = 0
|   mTouchDelegate = null
|   mTouchSlop = 28
|   mTransformationInfo = null
|   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 = 402653328
|   mVisibilityChangeForAutofillHandler = null
|   mWindowAttachCount = 1
|   shadow$_klass_ = android.widget.LinearLayout
|   shadow$_monitor_ = -2075687360
* Instance of com.ddsafricaltd.fieldmetrics.activities.MainActivity
|   static $class$dexTypeIndex = 526
|   static $class$ifTable = java.lang.Object[62]@320352488 (0x131830e8)
|   static $class$dexCache = java.lang.DexCache@320064464 (0x1313cbd0)
|   static hasBeenCreated = false
|   static $class$accessFlags = 524289
|   static $class$name = "com.ddsafricaltd.fieldmetrics.activities.MainActivity"
|   static $class$componentType = null
|   static $class$iFields = 532712420584
|   static $class$clinitThreadId = 4551
|   static $class$numReferenceStaticFields = 1
|   static $class$superClass = android.support.v7.app.AppCompatActivity
|   static $classOverhead = byte[4788]@320152585 (0x13152409)
|   static $class$objectSizeAllocFastPath = 616
|   static NUMLOOKUPS = 10
|   static $class$dexClassDefIndex = 1026
|   static REQUEST_CHECK_SETTINGS = 1
|   static $class$primitiveType = 131072
|   static $class$classSize = 4929
|   static $class$vtable = null
|   static PLAY_SERVICES_RESOLUTION_REQUEST = 1000
|   static $class$referenceInstanceOffsets = -1073741824
|   static Spinner_OnKey = com.ddsafricaltd.fieldmetrics.activities.MainActivity$34@320352824 (0x13183238)
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 532712422016
|   static $class$extData = null
|   static $class$objectSize = 616
|   static $class$classLoader = dalvik.system.PathClassLoader@319417192 (0x1309eb68)
|   static $class$virtualMethodsOffset = 108
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = -1960235794
|   static $class$numReferenceInstanceFields = 57
|   static $class$sFields = 532712420496
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 174
|   CURRENT_FRAGMENT = 0
|   CurrentPJP_User_Outlet = null
|   FRAGMENT_ACTIVITY_LOG = 40
|   FRAGMENT_ACTIVITY_OUTLET_MAP = 50
|   FRAGMENT_OUTLETS = 20
|   FRAGMENT_OUTLET_INFO = 21
|   FRAGMENT_PROFILE = 30
|   FRAGMENT_SETTINGS = 10
|   GPSLocationManagerService = null
|   GPS_Listener_Initialized = true
|   INT_PJPMODE = 1
|   SpinnerToolbar = android.support.v7.widget.AppCompatSpinner@321731488 (0x132d3ba0)
|   Spinner_OnTouch = com.ddsafricaltd.fieldmetrics.activities.MainActivity$33@321732328 (0x132d3ee8)
|   alertDialogBuilder = null
|   alertGPS = null
|   alertNetwork = null
|   appbar = null
|   arrQuestionAnswerStats = null
|   blnByPassOutletConfirmation = false
|   blnGPSAlertIsVisible = false
|   blnInstanceOfCuttOffAlreadyRunning = false
|   blnLastSavedOutletListSetting = false
|   blnMissingQuestionsCheckMode = false
|   blnNetworkAlertIsVisible = false
|   blnRealTimeGPSMode = false
|   blnShowSearchVsSpinner = true
|   btnShowLocation = null
|   btnStartLocationUpdates = null
|   bundle = null
|   db = com.ddsafricaltd.fieldmetrics.globalSupportRoutines.DatabaseRoutines@320312952 (0x13179678)
|   df = java.text.DecimalFormat@321732344 (0x132d3ef8)
|   distToHome = 0.0
|   fragmentManager = android.support.v4.app.FragmentManagerImpl@321732408 (0x132d3f38)
|   fragmentMapOutlet = null
|   fragmentMessages = com.ddsafricaltd.fieldmetrics.fragments.fragment_gcm_messages@321732520 (0x132d3fa8)
|   fragmentQuestions = com.ddsafricaltd.fieldmetrics.fragments.fragment_questions@321732720 (0x132d4070)
|   fragment_adapter = com.ddsafricaltd.fieldmetrics.activities.MainActivity$Adapter@321733040 (0x132d41b0)
|   gps = null
|   gpsStateReceiver = com.ddsafricaltd.fieldmetrics.globalSupportRoutines.GPSStateReceiver@321733088 (0x132d41e0)
|   holderOutlets = com.ddsafricaltd.fieldmetrics.pojos.HolderOutlets@321733104 (0x132d41f0)
|   imgMainActivityLogo = android.support.v7.widget.AppCompatImageView@321733240 (0x132d4278)
|   intAllowOutletCreation = 1
|   intDataStarted = 100
|   intEnforceGPSMapping = 1
|   intGPSResultCode = 10
|   intOutletIDToCheckQuestionsFor = 0
|   intTimesToastIsShown = 0
|   kalmanLatLong = null
|   lastPress = 1534252129233
|   lblLocation = null
|   lngNanoStartTime = 0
|   lngstartTime = 0
|   lngstarttime = 0
|   locCounter = 10
|   mFirebaseAnalytics = com.google.firebase.analytics.FirebaseAnalytics@320408624 (0x13190c30)
|   mGoogleApiClient = null
|   mGoogleError = false
|   mInAppBroadcastReceiver = com.ddsafricaltd.fieldmetrics.activities.MainActivity$5@321733808 (0x132d44b0)
|   mIntentFilter = android.content.IntentFilter@321733832 (0x132d44c8)
|   mLastUpdateTime = null
|   mLocalBroadcastManager = android.support.v4.content.LocalBroadcastManager@320812944 (0x131f3790)
|   mLocationRequest = null
|   mRequestingLocationUpdates = false
|   mTracker = com.google.android.gms.analytics.Tracker@321088360 (0x13236b68)
|   networkChangeReceiver = com.ddsafricaltd.fieldmetrics.globalSupportRoutines.NetworkChangeReceiver@321733888 (0x132d4500)
|   notifClickReceiver = null
|   objCheckIfDBIsCorruptAsync = com.ddsafricaltd.fieldmetrics.activities.MainActivity$CheckIfDBIsCorruptAsync@321733904 (0x132d4510)
|   objFetchSettings = null
|   objInsertUserQuestionsToTrans = null
|   objManualAnswerUpload = null
|   objQuestionAnswerStatus = null
|   objQuestionAnswerStatus2 = null
|   objQuestionCountForOutlet = null
|   objRunGenericSqlCommand = null
|   objSaveMessageToDB = null
|   objSaveSettings = null
|   progressBarMainActivity = android.widget.ProgressBar@321733944 (0x132d4538)
|   singleton = null
|   tabLayout = android.support.design.widget.TabLayout@321734512 (0x132d4770)
|   timeChangedReceiver = null
|   toolbar = android.support.v7.widget.Toolbar@321735344 (0x132d4ab0)
|   txtBlueTop = android.support.v7.widget.AppCompatTextView@321736120 (0x132d4db8)
|   txtCancelMain = android.support.v7.widget.AppCompatTextView@321736944 (0x132d50f0)
|   txtGreenBottom = android.support.v7.widget.AppCompatTextView@321737768 (0x132d5428)
|   txtIsDev = android.support.v7.widget.AppCompatTextView@321738592 (0x132d5760)
|   txtMainActivityDisplay = android.support.v7.widget.AppCompatTextView@321739416 (0x132d5a98)
|   txtRetailFieldMetricsUnderlogo = android.support.v7.widget.AppCompatTextView@321740240 (0x132d5dd0)
|   viewPager = android.support.v4.view.ViewPager@321741064 (0x132d6108)
|   wantLocations = true
|   mDelegate = android.support.v7.app.AppCompatDelegateImplN@321741888 (0x132d6440)
|   mResources = null
|   mThemeId = 2131689645
|   mCreated = true
|   mFragments = android.support.v4.app.FragmentController@321742032 (0x132d64d0)
|   mHandler = android.support.v4.app.FragmentActivity$1@321742048 (0x132d64e0)
|   mLoaderManager = null
|   mNextCandidateRequestIndex = 0
|   mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@321742080 (0x132d6500)
|   mReallyStopped = true
|   mRequestedPermissionsFromFragment = false
|   mResumed = false
|   mRetaining = false
|   mStopped = true
|   mViewModelStore = null
|   mStartedActivityFromFragment = false
|   mStartedIntentSenderFromFragment = false
|   mExtraDataMap = android.support.v4.util.SimpleArrayMap@321742104 (0x132d6518)
|   mLifecycleRegistry = android.arch.lifecycle.LifecycleRegistry@321742128 (0x132d6530)
|   mActionBar = null
|   mActionModeTypeStarting = 0
|   mActivityInfo = android.content.pm.ActivityInfo@321742160 (0x132d6550)
|   mActivityTransitionState = android.app.ActivityTransitionState@321742312 (0x132d65e8)
|   mApplication = com.ddsafricaltd.fieldmetrics.base.baseFieldMetrics@319430688 (0x130a2020)
|   mAutoFillIgnoreFirstResumePause = false
|   mAutoFillResetNeeded = false
|   mAutofillManager = null
|   mAutofillPopupWindow = null
|   mCalled = true
|   mCanEnterPictureInPicture = false
|   mChangeCanvasToTranslucent = false
|   mChangingConfigurations = false
|   mComponent = android.content.ComponentName@321742368 (0x132d6620)
|   mConfigChangeFlags = 0
|   mCurrentConfig = android.content.res.Configuration@321742384 (0x132d6630)
|   mDecor = null
|   mDefaultKeyMode = 0
|   mDefaultKeySsb = null
|   mDestroyed = true
|   mDoReportFullyDrawn = false
|   mEmbeddedID = null
|   mEnableDefaultActionBarUp = false
|   mEnterTransitionListener = android.app.SharedElementCallback$1@1893386296 (0x70dac838)
|   mExitTransitionListener = android.app.SharedElementCallback$1@1893386296 (0x70dac838)
|   mFinished = true
|   mFragments = android.app.FragmentController@321742496 (0x132d66a0)
|   mHandler = android.os.Handler@321742512 (0x132d66b0)
|   mHasCurrentPermissionsRequest = false
|   mIdent = 190401265
|   mInstanceTracker = android.os.StrictMode$InstanceTracker@321742544 (0x132d66d0)
|   mInstrumentation = android.app.Instrumentation@320471240 (0x131a00c8)
|   mIntent = android.content.Intent@321742560 (0x132d66e0)
|   mLastAutofillId = 1073741825
|   mLastNonConfigurationInstances = null
|   mMainThread = android.app.ActivityThread@319291816 (0x130801a8)
|   mManagedCursors = java.util.ArrayList@321742624 (0x132d6720)
|   mManagedDialogs = null
|   mMenuInflater = null
|   mParent = null
|   mReferrer = "com.ddsafricaltd.fieldmetrics"
|   mRestoredFromBundle = false
|   mResultCode = 0
|   mResultData = null
|   mResumed = false
|   mSearchEvent = null
|   mSearchManager = null
|   mStartedActivity = false
|   mStopped = true
|   mTaskDescription = android.app.ActivityManager$TaskDescription@321742696 (0x132d6768)
|   mTemporaryPause = false
|   mTitle = "Field Metrics"
|   mTitleColor = 0
|   mTitleReady = true
|   mToken = android.os.BinderProxy@321742768 (0x132d67b0)
|   mTranslucentCallback = null
|   mUiThread = java.lang.Thread@1967573624 (0x7546ca78)
|   mVisibleFromClient = true
|   mVisibleFromServer = false
|   mVoiceInteractor = null
|   mWindow = com.android.internal.policy.PhoneWindow@321742792 (0x132d67c8)
|   mWindowAdded = true
|   mWindowManager = android.view.WindowManagerImpl@321743168 (0x132d6940)
|   mInflater = com.android.internal.policy.PhoneLayoutInflater@321743192 (0x132d6958)
|   mOverrideConfiguration = null
|   mResources = android.content.res.Resources@321730504 (0x132d37c8)
|   mTheme = android.content.res.Resources$Theme@321743240 (0x132d6988)
|   mThemeResource = 2131689645
|   mBase = android.app.ContextImpl@321743256 (0x132d6998)
|   shadow$_klass_ = com.ddsafricaltd.fieldmetrics.activities.MainActivity
|   shadow$_monitor_ = -1882115313
* Instance of android.app.FragmentController
|   static $class$dexTypeIndex = 547
|   static $class$ifTable = java.lang.Object[0]@1891798952 (0x70c28fa8)
|   static $class$dexCache = java.lang.DexCache@1893249256 (0x70d8b0e8)
|   static $class$accessFlags = 524289
|   static $class$name = "android.app.FragmentController"
|   static $class$componentType = null
|   static $class$iFields = 1899316064
|   static $class$clinitThreadId = 0
|   static $class$numReferenceStaticFields = 0
|   static $class$superClass = java.lang.Object
|   static $classOverhead = byte[412]@1896134193 (0x7104b631)
|   static $class$objectSizeAllocFastPath = 16
|   static $class$dexClassDefIndex = 303
|   static $class$primitiveType = 131072
|   static $class$classSize = 536
|   static $class$vtable = null
|   static $class$referenceInstanceOffsets = 1
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 1901690088
|   static $class$extData = null
|   static $class$objectSize = 12
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 2
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 536870912
|   static $class$numReferenceInstanceFields = 1
|   static $class$sFields = 0
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 41
|   mHost = android.app.Activity$HostCallbacks@321776432 (0x132deb30)
|   shadow$_klass_ = android.app.FragmentController
|   shadow$_monitor_ = 0
* Instance of android.app.Activity$HostCallbacks
|   static $class$dexTypeIndex = 355
|   static $class$ifTable = java.lang.Object[0]@1891798952 (0x70c28fa8)
|   static $class$dexCache = java.lang.DexCache@1893249256 (0x70d8b0e8)
|   static $class$accessFlags = 524288
|   static $class$name = "android.app.Activity$HostCallbacks"
|   static $class$componentType = null
|   static $class$iFields = 1898886064
|   static $class$clinitThreadId = 0
|   static $class$numReferenceStaticFields = 0
|   static $class$superClass = android.app.FragmentHostCallback
|   static $classOverhead = byte[364]@1895176241 (0x70f61831)
|   static $class$objectSizeAllocFastPath = 48
|   static $class$dexClassDefIndex = 5011
|   static $class$primitiveType = 131072
|   static $class$classSize = 488
|   static $class$vtable = null
|   static $class$referenceInstanceOffsets = 319
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 1900272992
|   static $class$extData = null
|   static $class$objectSize = 44
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 1
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 536870912
|   static $class$numReferenceInstanceFields = 1
|   static $class$sFields = 0
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 17
|   this$0 = com.ddsafricaltd.fieldmetrics.activities.MainActivity@321729656 (0x132d3478)
|   mActivity = com.ddsafricaltd.fieldmetrics.activities.MainActivity@321729656 (0x132d3478)
|   mAllLoaderManagers = android.util.ArrayMap@321776480 (0x132deb60)
|   mCheckedForLoaderManager = true
|   mContext = com.ddsafricaltd.fieldmetrics.activities.MainActivity@321729656 (0x132d3478)
|   mFragmentManager = android.app.FragmentManagerImpl@321776512 (0x132deb80)
|   mHandler = android.os.Handler@321742512 (0x132d66b0)
|   mLoaderManager = null
|   mLoadersStarted = true
|   mRetainLoaders = false
|   mWindowAnimations = 0
|   shadow$_klass_ = android.app.Activity$HostCallbacks
|   shadow$_monitor_ = 0
* Instance of android.app.FragmentManagerImpl
|   static $class$dexTypeIndex = 560
|   static $class$ifTable = java.lang.Object[4]@1893804904 (0x70e12b68)
|   static $class$dexCache = java.lang.DexCache@1893249256 (0x70d8b0e8)
|   static $class$accessFlags = 524304
|   static $class$name = "android.app.FragmentManagerImpl"
|   static $class$componentType = null
|   static VIEW_STATE_TAG = "android:view_state"
|   static $class$iFields = 1899063816
|   static $class$clinitThreadId = 0
|   static TARGET_STATE_TAG = "android:target_state"
|   static $class$numReferenceStaticFields = 5
|   static TAG = "FragmentManager"
|   static TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state"
|   static $class$superClass = android.app.FragmentManager
|   static $classOverhead = byte[916]@1894490841 (0x70eba2d9)
|   static $class$objectSizeAllocFastPath = 112
|   static $class$dexClassDefIndex = 5028
|   static $class$primitiveType = 131072
|   static USER_VISIBLE_HINT_TAG = "android:user_visible_hint"
|   static $class$classSize = 1061
|   static $class$vtable = null
|   static DEBUG = false
|   static $class$referenceInstanceOffsets = 4194303
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 1900949840
|   static $class$extData = null
|   static $class$objectSize = 110
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 30
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 536870912
|   static $class$numReferenceInstanceFields = 22
|   static $class$sFields = 1899063716
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 132
|   mActive = android.util.SparseArray@321776624 (0x132debf0)
|   mAdded = java.util.ArrayList@321776648 (0x132dec08)
|   mAllowOldReentrantBehavior = false
|   mAvailBackStackIndices = null
|   mBackStack = null
|   mBackStackChangeListeners = null
|   mBackStackIndices = null
|   mContainer = null
|   mCreatedMenus = null
|   mCurState = 0
|   mDestroyed = true
|   mExecCommit = android.app.FragmentManagerImpl$1@321776672 (0x132dec20)
|   mExecutingActions = false
|   mHavePendingDeferredStart = false
|   mHost = null
|   mLifecycleCallbacks = java.util.concurrent.CopyOnWriteArrayList@321776688 (0x132dec30)
|   mNeedMenuInvalidate = false
|   mNextFragmentIndex = 1
|   mNoTransactionsBecause = null
|   mParent = null
|   mPendingActions = java.util.ArrayList@321776704 (0x132dec40)
|   mPostponedTransactions = null
|   mPrimaryNav = null
|   mSavedNonConfig = null
|   mStateArray = null
|   mStateBundle = null
|   mStateSaved = false
|   mTmpAddedFragments = java.util.ArrayList@321776728 (0x132dec58)
|   mTmpIsPop = java.util.ArrayList@321776752 (0x132dec70)
|   mTmpRecords = java.util.ArrayList@321776776 (0x132dec88)
|   shadow$_klass_ = android.app.FragmentManagerImpl
|   shadow$_monitor_ = 0
* Instance of java.util.ArrayList
|   static $class$dexTypeIndex = 1417
|   static $class$ifTable = java.lang.Object[12]@1887487328 (0x7080c560)
|   static $class$dexCache = java.lang.DexCache@1887109360 (0x707b00f0)
|   static $class$accessFlags = 524289
|   static $class$name = "java.util.ArrayList"
|   static $class$componentType = null
|   static EMPTY_ELEMENTDATA = java.lang.Object[0]@1890322656 (0x70ac08e0)
|   static $class$iFields = 1888539960
|   static DEFAULT_CAPACITY = 10
|   static MAX_ARRAY_SIZE = 2147483639
|   static $class$clinitThreadId = 0
|   static serialVersionUID = 8683452581122892189
|   static $class$numReferenceStaticFields = 2
|   static DEFAULTCAPACITY_EMPTY_ELEMENTDATA = java.lang.Object[0]@1893471960 (0x70dc16d8)
|   static $class$superClass = java.util.AbstractList
|   static $classOverhead = byte[364]@1887527113 (0x708160c9)
|   static $class$objectSizeAllocFastPath = 24
|   static $class$dexClassDefIndex = 3077
|   static $class$primitiveType = 131072
|   static $class$classSize = 512
|   static $class$vtable = null
|   static $class$referenceInstanceOffsets = 2
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 1888681872
|   static $class$extData = null
|   static $class$objectSize = 20
|   static $class$classLoader = null
|   static $class$virtualMethodsOffset = 15
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = -1363712915
|   static $class$numReferenceInstanceFields = 1
|   static $class$sFields = 1888539876
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 46
|   elementData = java.lang.Object[10]@321777064 (0x132deda8)
|   size = 1
|   modCount = 1
|   shadow$_klass_ = java.util.ArrayList
|   shadow$_monitor_ = 0
* Array of java.lang.Object[]
|   [0] = android.arch.lifecycle.ReportFragment@321777120 (0x132dede0)
|   [1] = null
|   [2] = null
|   [3] = null
|   [4] = null
|   [5] = null
|   [6] = null
|   [7] = null
|   [8] = null
|   [9] = null
* Instance of android.arch.lifecycle.ReportFragment
|   static $class$dexTypeIndex = 135
|   static $class$ifTable = java.lang.Object[6]@320797536 (0x131efb60)
|   static $class$dexCache = java.lang.DexCache@319666776 (0x130dba58)
|   static $class$accessFlags = 524289
|   static $class$name = "android.arch.lifecycle.ReportFragment"
|   static $class$componentType = null
|   static $class$iFields = 532712444408
|   static $class$clinitThreadId = 4551
|   static $class$numReferenceStaticFields = 1
|   static $class$superClass = android.app.Fragment
|   static $classOverhead = byte[1316]@319776057 (0x130f6539)
|   static $class$objectSizeAllocFastPath = 128
|   static REPORT_FRAGMENT_TAG = "android.arch.lifecycle.LifecycleDispatcher.report_fragment_tag"
|   static $class$dexClassDefIndex = 30
|   static $class$primitiveType = 131072
|   static $class$classSize = 1444
|   static $class$vtable = null
|   static $class$referenceInstanceOffsets = 268500991
|   static $class$shadow$_klass_ = java.lang.Class
|   static $class$methods = 532712444432
|   static $class$extData = null
|   static $class$objectSize = 124
|   static $class$classLoader = dalvik.system.PathClassLoader@319417192 (0x1309eb68)
|   static $class$virtualMethodsOffset = 7
|   static $class$classFlags = 0
|   static $class$shadow$_monitor_ = 0
|   static $class$numReferenceInstanceFields = 1
|   static $class$sFields = 532712444384
|   static $class$status = -536870912
|   static $class$copiedMethodsOffset = 14
|   mProcessListener = null
|   mAdded = false
|   mAnimationInfo = null
|   mArguments = null
|   mBackStackNesting = 0
|   mCalled = true
|   mCheckedForLoaderManager = false
|   mChildFragmentManager = null
|   mChildNonConfig = null
|   mContainer = null
|   mContainerId = 0
|   mDeferStart = false
|   mDetached = false
|   mFragmentId = 0
|   mFragmentManager = null
|   mFromLayout = false
|   mHasMenu = false
|   mHidden = false
|   mHiddenChanged = false
|   mHost = null
|   mInLayout = false
|   mIndex = -1
|   mIsCreated = false
|   mIsNewlyAdded = false
|   mLayoutInflater = null
|   mLoaderManager = null
|   mLoadersStarted = false
|   mMenuVisible = true
|   mParentFragment = null
|   mPerformedCreateView = false
|   mRemoving = false
|   mRestored = false
|   mRetainInstance = false
|   mRetaining = false
|   mSavedFragmentState = null
|   mSavedViewState = null
|   mState = 0
|   mTag = null
|   mTarget = null
|   mTargetIndex = -1
|   mTargetRequestCode = 0
|   mUserVisibleHint = true
|   mView = null
|   mWho = null
|   shadow$_klass_ = android.arch.lifecycle.ReportFragment
|   shadow$_monitor_ = 0
* Excluded Refs:
| Field: android.os.Message.obj
| Field: android.os.Message.next
| Field: android.os.Message.target
| Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)
| Thread:FinalizerWatchdogDaemon (always)
| Thread:main (always)
| Thread:LeakCanary-Heap-Dump (always)
| Class:java.lang.ref.WeakReference (always)
| Class:java.lang.ref.SoftReference (always)
| Class:java.lang.ref.PhantomReference (always)
| Class:java.lang.ref.Finalizer (always)
| Class:java.lang.ref.FinalizerReference (always)

@pyricau
Copy link
Member

@pyricau pyricau commented Oct 15, 2018

Reopening because we can document a gist that uses reflection to fix this:

Latest ViewGroup sources: https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java

This leaks happens whenever ViewGroup.ViewLocationHolder.recycle() is called (a ViewLocationHolder instance is cleared incorrectly then put back in the static pool) , which AFAIK only happens from ViewGroup.ChildListForAccessibility.init() (is that true?), which is called when ViewGroupChildListForAccessibility. obtain is called with sort set to true.

In other words, as far as I can see, ViewGroup.ViewLocationHolder is only ever used as a temporary object to sort the list of children in ViewGroup.ChildListForAccessibility, once per ViewGroup.ChildListForAccessibility.obtain() call.

That happens in ViewGroup.addChildrenForAccessibility and ViewGroup.dispatchPopulateAccessibilityEventInternal.

Here's an idea:

  • when an activity or a fragment is destroyed (ie when LeakCanary would start tracking a reference), look up the ViewGroup static pool and either:
    • a) clear the ViewGroup$ViewLocationHolder.mRoot ref for all instances in the array
    • or b) empty the static pool.

@pyricau pyricau reopened this Oct 15, 2018
@pyricau pyricau changed the title ReportFragment leaked ViewGroup.ViewLocationHolder leak in Android P Oct 16, 2018
NasaGeek added a commit to NasaGeek/utexas-utilities that referenced this issue Nov 3, 2018
wbaumann pushed a commit to SmartReceipts/SmartReceiptsLibrary that referenced this issue Jan 3, 2019
According to square/leakcanary#1081, there's a bug in the Android framework that causes this memory leak for a few scenarios, including apps that support TabLayout navigation via ViewPager (like we do). Rather than deal with this steady stream or leaks, I have disabled LeakCanary for Android O, O_MR1, and P.
wbaumann pushed a commit to SmartReceipts/SmartReceiptsLibrary that referenced this issue Jan 3, 2019
According to square/leakcanary#1081, there's a bug in the Android framework that causes this memory leak for a few scenarios, including apps that support TabLayout navigation via ViewPager (like we do). Rather than deal with this steady stream or leaks, I have disabled LeakCanary for Android O, O_MR1, and P.
@jrodbx
Copy link
Collaborator

@jrodbx jrodbx commented Jan 11, 2019

Reopening because we can document a gist that uses reflection to fix this:

Here's an idea:

  • a) clear the ViewGroup$ViewLocationHolder.mRoot ref for all instances in the array

Took a stab at this, in ActivityRefWatcher:

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          System.out.println("API level = " + Build.VERSION.SDK_INT);
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            try {
              Class<?> holder = Class.forName("android.view.ViewGroup$ViewLocationHolder");

              // class ViewLocationHolder {
              //   SynchronizedPool<ViewLocationHolder> sPool;
              Field sPoolField = holder.getDeclaredField("sPool");
              sPoolField.setAccessible(true);
              Object sPool = sPoolField.get(null);

              // class SynchronizedPool<T> extends SimplePool<T>
              Class<?> simplePoolClass = sPool.getClass().getSuperclass();

              // class SimplePool<T> implements Pool<T> {
              //   Object[] mPool;
              //   int mPoolSize;
              Field poolArrayField = simplePoolClass.getDeclaredField("mPool");
              poolArrayField.setAccessible(true);
              Object[] poolArray = (Object[]) poolArrayField.get(sPool);
              Field poolSizeField = simplePoolClass.getDeclaredField("mPoolSize");
              poolSizeField.setAccessible(true);
              int poolSize = (int) poolSizeField.get(sPool);

              // sanity check, we really care about poolSize
              int poolArrayLength = Array.getLength(poolArray);
              System.out.println("poolArrayLength = " + poolArrayLength);
              for (int i = 0; i < poolArrayLength; i++) {
                Object viewLocationHolder = poolArray[i];
                if (viewLocationHolder == null) {
                  System.out.println("poolArray[" + i + "] == null");
                  continue;
                }

                Field mRootField = viewLocationHolder.getClass().getDeclaredField("mRoot");
                mRootField.setAccessible(true);

                Object mRoot = mRootField.get(viewLocationHolder);
                if (mRoot == null) {
                  System.out.println("poolArray[" + i + "].mRoot == null");
                } else {
                  System.out.println("Found leak!  poolArray[" + i + "].mRoot != null");
                }
              }

              System.out.println("poolSize = " + poolSize);
              for (int i = 0; i < poolSize; i++) {
                Object viewLocationHolder = poolArray[i];

                // class ViewLocationHolder {
                //   ViewGroup mRoot;
                Field mRootField = viewLocationHolder.getClass().getDeclaredField("mRoot");
                mRootField.setAccessible(true);

                // release leaked reference
                mRootField.set(viewLocationHolder, null);
              }
            } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
              // we tried...
              System.out.println(e);
            }
          }

          refWatcher.watch(activity);
        }
      };

On API 28, attempting to access ViewGroup$ViewLocationHolder.sPool throws a NoSuchFieldException.

2019-01-11 12:30:56.723 31998-31998/com.squareup.cash.beta.debug W/cash.beta.debu: Accessing hidden field Landroid/view/ViewGroup$ViewLocationHolder;->sPool:Landroid/util/Pools$SynchronizedPool; (dark greylist, reflection)

More here: https://developer.android.com/about/versions/pie/restrictions-non-sdk-interfaces.

Given this is a leak in AOSP, should we file a feature request to move the pertinent classes to the light greylist?

@emartynov
Copy link

@emartynov emartynov commented Jan 24, 2019

Unfortunate bad luck!

@frist008
Copy link

@frist008 frist008 commented Feb 15, 2019

@AOrobator
Copy link

@AOrobator AOrobator commented Mar 10, 2019

Is there any way to suppress the LeakCanary notification for this individual type of leak? I know it's a real issue, and LeakCanary reporting it means that LeakCanary is doing its job correctly. However, because we have no control over fixing it, being notified every time it happens isn't particularly useful. The other alternative would be to disable LeakCanary entirely for the app which would be throwing out the baby with the bathwater.

Edit: I see there is AndroidExcludedRefs.java. Seems like this leak should be included in the list.

Edit2: This leak is included in AndroidExcludedRefs.java, but it is not alwaysExclude()'d. Using the following code also still triggers memory leaks. Is this a valid way to exclude leaks? If so, I'll unfortunately have to disable LeakCanary on Android P.

package com.squareup.leakcanary

import android.app.Application
import com.squareup.leakcanary.AndroidExcludedRefs.VIEWLOCATIONHOLDER_ROOT

fun buildAndInstallLeakCanary(app: Application) {
  val excludedRefsBuilder: ExcludedRefs.Builder = ExcludedRefs.builder()

  AndroidExcludedRefs
      .values()
      .filter { it.applies }
      .forEach { ref ->
        if (ref == VIEWLOCATIONHOLDER_ROOT) {
          excludedRefsBuilder
              .instanceField("android.view.ViewGroup\$ViewLocationHolder", "mRoot")
              .alwaysExclude()
              .staticField("android.view.ViewGroup\$ViewLocationHolder", "sPool")
              .alwaysExclude()
        } else {
          ref.add(excludedRefsBuilder)
        }
        (excludedRefsBuilder as ExcludedRefs.BuilderWithParams).named(ref.name)
      }

  LeakCanary.refWatcher(app)
      .excludedRefs(excludedRefsBuilder.build())
      .buildAndInstall()
}

@AOrobator
Copy link

@AOrobator AOrobator commented Mar 10, 2019

@jrodbx Have you filed a feature request to move the relevant classes to the light greylist? If so, please link it, as I'd like to follow the status of this feature request.

@pyricau
Copy link
Member

@pyricau pyricau commented Mar 19, 2019

@AOrobator you should rarely if ever have to use "always exclude", that's only for things like weak references.

LeakCanary cannot "ignore" a leak until it knows what leak this is. Then it'll show up as an "excluded" leak and that's as good as it gets.

@pyricau
Copy link
Member

@pyricau pyricau commented May 5, 2019

In the meantime, I believe we can come up with a fix that doesn't involve reflection, if someone is interested in having some fun.

This leak is triggered when ViewGroup#addChildrenForAccessibility is called, the view group ends up leaking in ViewLocationHolder.sPool.

So one way to "clean this up" is to create a dummy view group, keep it detached, add ViewLocationHolder.MAX_POOL_SIZE children (32) to it, then call ViewGroup#addChildrenForAccessibility on it. This will effectively reset the pool so that all instances leaks the dummy view group as their mRoot.

This may be expansive, it's worth measuring it.

@emartynov
Copy link

@emartynov emartynov commented May 6, 2019

I've just quickly tried next code:

class AndroidPHackCleanReference {
    companion object {
        fun execute(activity: AppCompatActivity) {
            val viewGroup = LinearLayout(activity)
            // 32 - ViewLocationHolder.MAX_POOL_SIZE
            for (i in 0..32) {
                viewGroup.addView(TextView(activity))
            }

            viewGroup.addChildrenForAccessibility(ArrayList(viewGroup.childViews))
        }
    }
}

And I still see this leak in the Leak Canary (I need a button now to trigger the analysis to not click app to pass threshold).

My understanding - View has context as a constructor parameter, this still leak activity since I will use current activity to construct views.

@pyricau
Copy link
Member

@pyricau pyricau commented May 7, 2019

@emartynov The button (notification) will be coming in the next alpha :) . In the meantime, press "home" (not back) and that will trigger the heap dump (triggers on app background)

Edit: programmers make mistakes, whether they work at Google or not. Let's not pile on this, although I get the frustration :) .

@pyricau
Copy link
Member

@pyricau pyricau commented May 7, 2019

Also, do you have a short piece of code that reproduces the issue?

@eneim
Copy link

@eneim eneim commented Nov 7, 2019

@pyricau I think I found a pattern to reproduce the related issues: Open a DialogFragment from a Fragment, then press 'Current App' button (the Square button on NavigationBar), then press it again to bring everything to normal, then press 'Back' button until the App is closed.

I can always reproduce this on my LG G7 running Android 9, but not always for AVD with Android 9 or 10. Details below:

TestLeakFragment.kt
class TestFragment : Fragment() {

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View? {
    val frame = FrameLayout(inflater.context)
    frame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)

    val button = AppCompatButton(inflater.context)
    button.layoutParams =
      FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
          .also {
            it.gravity = Gravity.CENTER
          }
    frame.addView(button)

    button.text = "Click here!"
    button.setOnClickListener {
      TestDialogFragment().show(childFragmentManager, "test")
    }
    return frame
  }
}

class TestDialogFragment : AppCompatDialogFragment() {

  override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
  ): View? {
    return Button(inflater.context)
  }
}
activity_debug.xml
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

  <fragment
      android:id="@+id/debugContainer"
      android:name="kohii.v1.sample.ui.debug.TestFragment"
      android:layout_width="0dp"
      android:layout_height="0dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
DevActivity.kt
class DevActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_debug)
  }
}
Logcat
2019-11-07 22:30:35.828 18199-18199/kohii.v1.sample.dev D/LeakCanary: Installing AppWatcher
2019-11-07 22:30:44.265 18199-18199/kohii.v1.sample.dev D/LeakCanary: Watching instance of android.widget.Button with key e568fe83-59e8-4c79-91ae-a018a9e7df4e
2019-11-07 22:30:44.266 18199-18199/kohii.v1.sample.dev D/LeakCanary: Watching instance of kohii.v1.sample.ui.debug.TestDialogFragment with key 7cb87876-cf05-4b1a-b9ee-af12446c16e7
2019-11-07 22:30:45.773 18199-18199/kohii.v1.sample.dev D/LeakCanary: Already scheduled retained check, ignoring (app became invisible)
2019-11-07 22:30:45.777 18199-18199/kohii.v1.sample.dev D/LeakCanary: Watching instance of androidx.lifecycle.ReportFragment with key 7d73525c-097f-4664-aeb0-4fc5cc1390ce
2019-11-07 22:30:45.778 18199-18199/kohii.v1.sample.dev D/LeakCanary: Watching instance of kohii.v1.sample.DevActivity with key dd11f52d-374c-48e8-9c86-649ed295ccf5
2019-11-07 22:30:45.779 18199-18199/kohii.v1.sample.dev D/LeakCanary: Watching instance of android.widget.FrameLayout with key 820ab904-6a6e-4120-9b48-122dd4adf8b8
2019-11-07 22:30:45.781 18199-18199/kohii.v1.sample.dev D/LeakCanary: Watching instance of kohii.v1.sample.ui.debug.TestFragment with key 137ab2cd-5cc6-4d65-849b-c395ee8b6e61
2019-11-07 22:30:46.650 18199-18328/kohii.v1.sample.dev D/LeakCanary: Checking retained object because app became invisible
2019-11-07 22:30:46.651 18199-18328/kohii.v1.sample.dev D/LeakCanary: No retained objects
2019-11-07 22:30:49.294 18199-18199/kohii.v1.sample.dev D/LeakCanary: Already scheduled retained check, ignoring (found new object retained)
2019-11-07 22:30:49.295 18199-18328/kohii.v1.sample.dev D/LeakCanary: Checking retained object because found new object retained
2019-11-07 22:30:49.488 18199-18328/kohii.v1.sample.dev D/LeakCanary: Found 2 retained objects, which is less than the visible threshold of 5
2019-11-07 22:30:50.810 18199-18199/kohii.v1.sample.dev D/LeakCanary: Already scheduled retained check, ignoring (found new object retained)
2019-11-07 22:30:50.811 18199-18199/kohii.v1.sample.dev D/LeakCanary: Already scheduled retained check, ignoring (found new object retained)
2019-11-07 22:30:51.551 18199-18328/kohii.v1.sample.dev D/LeakCanary: Checking retained object because Showing retained objects notification
2019-11-07 22:30:51.759 18199-18328/kohii.v1.sample.dev D/LeakCanary: Found 6 retained references, dumping the heap
2019-11-07 22:30:51.795 18199-18328/kohii.v1.sample.dev D/LeakCanary: Removing 1 heap dumps
2019-11-07 22:30:57.226 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: PARSING_HEAP_DUMP
2019-11-07 22:30:58.484 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: FINDING_LEAKING_INSTANCES
2019-11-07 22:30:59.613 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: FINDING_PATHS_TO_LEAKING_INSTANCES
2019-11-07 22:31:05.526 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: FINDING_DOMINATORS
2019-11-07 22:31:05.575 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: COMPUTING_NATIVE_RETAINED_SIZE
2019-11-07 22:31:05.878 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: COMPUTING_RETAINED_SIZE
2019-11-07 22:31:05.907 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: BUILDING_LEAK_TRACES
2019-11-07 22:31:05.919 18199-18350/kohii.v1.sample.dev D/LeakCanary: Analysis in progress, working on: REPORTING_HEAP_ANALYSIS
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: HeapAnalysisSuccess(heapDumpFile=/storage/emulated/0/Download/leakcanary-kohii.v1.sample.dev/2019-11-07_22-30-51_803.hprof, createdAtTimeMillis=1573133465921, analysisDurationMillis=8696, applicationLeaks=[], libraryLeaks=[LibraryLeak(className=kohii.v1.sample.DevActivity, leakTrace=
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ┬
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.ViewGroup$ViewLocationHolder
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: NO (a class is never leaking)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    GC Root: System class
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ static ViewGroup$ViewLocationHolder.sPool
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                                          ~~~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.util.Pools$SynchronizedPool
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: UNKNOWN
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ Pools$SynchronizedPool.mPool
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                             ~~~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ java.lang.Object[]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: UNKNOWN
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ array Object[].[0]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                     ~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.ViewGroup$ViewLocationHolder
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: UNKNOWN
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ ViewGroup$ViewLocationHolder.mRoot
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                                   ~~~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ androidx.appcompat.widget.FitWindowsFrameLayout
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: YES (View.mContext references a destroyed activity)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    mContext instance of android.view.ContextThemeWrapper, wrapping activity kohii.v1.sample.DevActivity with mDestroyed = true
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View#mParent is set
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View#mAttachInfo is null (view detached)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View.mWindowAttachCount = 1
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ FitWindowsFrameLayout.mContext
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.ContextThemeWrapper
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: YES (FitWindowsFrameLayout↑ is leaking and ContextThemeWrapper wraps an Activity with Activity.mDestroyed true)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ ContextThemeWrapper.mBase
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ╰→ kohii.v1.sample.DevActivity
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     Leaking: YES (ContextThemeWrapper↑ is leaking and Activity#mDestroyed is true and ObjectWatcher was watching this)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     key = dd11f52d-374c-48e8-9c86-649ed295ccf5
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     watchDurationMillis = 5981
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     retainedDurationMillis = 948
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: , retainedHeapByteSize=11812, pattern=instance field android.view.ViewGroup$ViewLocationHolder#mRoot, description=In Android P, ViewLocationHolder has an mRoot field that is not cleared in its clear() method. Introduced in https://github.com/aosp-mirror/platform_frameworks_base/commit/86b326012813f09d8f1de7d6d26c986a909d Bug report: https://issuetracker.google.com/issues/112792715), LibraryLeak(className=android.widget.Button, leakTrace=
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ┬
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.ViewGroup$ViewLocationHolder
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: NO (a class is never leaking)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    GC Root: System class
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ static ViewGroup$ViewLocationHolder.sPool
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                                          ~~~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.util.Pools$SynchronizedPool
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: UNKNOWN
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ Pools$SynchronizedPool.mPool
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                             ~~~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ java.lang.Object[]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: UNKNOWN
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ array Object[].[0]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                     ~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.ViewGroup$ViewLocationHolder
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: UNKNOWN
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ ViewGroup$ViewLocationHolder.mRoot
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │                                   ~~~~~
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ androidx.appcompat.widget.FitWindowsFrameLayout
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: YES (View.mContext references a destroyed activity)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    mContext instance of android.view.ContextThemeWrapper, wrapping activity kohii.v1.sample.DevActivity with mDestroyed = true
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View#mParent is set
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View#mAttachInfo is null (view detached)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View.mWindowAttachCount = 1
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ FitWindowsFrameLayout.mChildren
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.View[]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: YES (FitWindowsFrameLayout↑ is leaking)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ array View[].[0]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ androidx.appcompat.widget.ContentFrameLayout
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: YES (View[]↑ is leaking and View.mContext references a destroyed activity)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    mContext instance of android.view.ContextThemeWrapper, wrapping activity kohii.v1.sample.DevActivity with mDestroyed = true
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View#mParent is set
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View#mAttachInfo is null (view detached)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    View.mWindowAttachCount = 1
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ ContentFrameLayout.mChildren
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ├─ android.view.View[]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    Leaking: YES (ContentFrameLayout↑ is leaking)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: │    ↓ array View[].[0]
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ╰→ android.widget.Button
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     Leaking: YES (View[]↑ is leaking and View.mContext references a destroyed activity and ObjectWatcher was watching this)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     mContext instance of android.view.ContextThemeWrapper, wrapping activity kohii.v1.sample.DevActivity with mDestroyed = true
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     View#mParent is set
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     View#mAttachInfo is null (view detached)
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     View.mWindowAttachCount = 1
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     key = e568fe83-59e8-4c79-91ae-a018a9e7df4e
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     watchDurationMillis = 7494
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: ​     retainedDurationMillis = 2467
2019-11-07 22:31:05.924 18199-18350/kohii.v1.sample.dev D/LeakCanary: , retainedHeapByteSize=17473, pattern=instance field android.view.ViewGroup$ViewLocationHolder#mRoot, description=In Android P, ViewLocationHolder has an mRoot field that is not cleared in its clear() method. Introduced in https://github.com/aosp-mirror/platform_frameworks_base/commit/86b326012813f09d8f1de7d6d26c986a909d Bug report: https://issuetracker.google.com/issues/112792715)])

@anguyenqd
Copy link

@anguyenqd anguyenqd commented Apr 8, 2020

Anyone has any update about this leak. Google has locked the issue tracker thread and leak has been happening a lot on my app. :(

@lixiaogang03
Copy link

@lixiaogang03 lixiaogang03 commented Apr 28, 2020

I found the same problem in Android P

pyricau added a commit that referenced this issue May 22, 2020
Related to #1081

Used the code from @jrodbx ([here](#1081 (comment))) to log whether the leak is happening, which worked after changing this:

```
adb shell settings put global hidden_api_policy_pre_p_apps  1
adb shell settings put global hidden_api_policy_p_apps 1
```

Then I used the code from @eneim [here](#1081 (comment)) to reproduce the leak. The leak was not happening on the P emulator until I turned on talkback, then it immediately started happening.
pyricau added a commit that referenced this issue May 22, 2020
Uses the fix from @emartynov ([here](#1081 (comment))) except we use the application context instead of activity context to avoid reintroducing a leak.

This effectively flushes the pool on activity destroy.

Ideally, we'd do that on fragment as well, and pretty much any time a chunk of the view hierarchy changes.
@pyricau
Copy link
Member

@pyricau pyricau commented May 22, 2020

Good news! I managed to reproduce the leak here: #1842

Then I applied a fix: d3769b1 and it's working!

The next release of LeakCanary will include a separate artifact that can fix leaks for you, automatically, so ideally I can ship this fix as well.

The main thing to figure out is when to apply it. Fragment destroy? Activity destroy? Every time a view is detached?

@pyricau
Copy link
Member

@pyricau pyricau commented May 22, 2020

big thanks to @jrodbx (for the reflection code which I used to quickly detect if there was a leak / if the fix was working), @eneim for the repro code, and @emartynov for the almost working fix that involves no reflection.

@pyricau pyricau added this to the 2.4 milestone May 22, 2020
@emartynov
Copy link

@emartynov emartynov commented May 23, 2020

@pyricau, that was a big surprise for me this morning. I completely forgot about this and super busy with my current job change that I don't participate in the open-source much (unfortunately).

To be honest, the biggest part of this code I took from @swankjesse somewhere online and just a bit improved it later (if I remember it correctly).

Thank you again for such a great library. Looking forward to the new release.

pyricau added a commit that referenced this issue May 29, 2020
Uses the fix from @emartynov ([here](#1081 (comment))) except we use the application context instead of activity context to avoid reintroducing a leak.

This effectively flushes the pool on activity destroy.

Ideally, we'd do that on fragment as well, and pretty much any time a chunk of the view hierarchy changes.
pyricau added a commit that referenced this issue Jun 4, 2020
Uses the fix from @emartynov ([here](#1081 (comment))) except we use the application context instead of activity context to avoid reintroducing a leak.

This effectively flushes the pool on activity destroy.

Ideally, we'd do that on fragment as well, and pretty much any time a chunk of the view hierarchy changes.
@pyricau
Copy link
Member

@pyricau pyricau commented Jun 5, 2020

👋🏻

@pyricau pyricau closed this as completed Jun 5, 2020
Martinvlba pushed a commit to KekHunterOS/KekHunter that referenced this issue Oct 30, 2020
…freeing them would cause null exception when press back button to jump back to previous fragment.

But the app will reports memory leaks when the app is terminated, seems there is still no any good looking solutions to address this issue. See square/leakcanary#1081
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests