Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package shark
import shark.AndroidReferenceMatchers.Companion.appDefaults
import shark.AndroidReferenceMatchers.Companion.buildKnownReferences
import shark.ReferencePattern.InstanceFieldPattern
import shark.ReferencePattern.JavaLocalPattern
import shark.ReferencePattern.NativeGlobalVariablePattern
import shark.ReferencePattern.StaticFieldPattern
import java.lang.ref.PhantomReference
import java.lang.ref.SoftReference
import java.lang.ref.WeakReference
import java.util.EnumSet
/**
* [AndroidReferenceMatchers] values add [ReferenceMatcher] instances to a global list via their
* [add] method. A [ReferenceMatcher] is either a [IgnoredReferenceMatcher] or
* a [LibraryLeakReferenceMatcher].
*
* [AndroidReferenceMatchers] is used to build the list of known references that cannot ever create
* leaks (via [IgnoredReferenceMatcher]) as well as the list of known leaks in the Android Framework
* and in manufacturer specific Android implementations.
*
* This class is a work in progress. You can help by reporting leak traces that seem to be caused
* by the Android SDK, here: https://github.com/square/leakcanary/issues/new
*
* We filter on SDK versions and Manufacturers because many of those leaks are specific to a given
* manufacturer implementation, they usually share their builds across multiple models, and the
* leaks eventually get fixed in newer versions.
*
* Most app developers should use [appDefaults]. However, you can also use a subset of
* [AndroidReferenceMatchers] by creating an [EnumSet] that matches your needs and calling
* [buildKnownReferences].
*/
enum class AndroidReferenceMatchers {
// ######## Android Framework known leaks ########
IREQUEST_FINISH_CALLBACK {
override fun add(
references: MutableList
) {
references += instanceFieldLeak("android.app.Activity\$1", "this\$0",
description = "Android Q added a new android.app.IRequestFinishCallback\$Stub " +
"class. android.app.Activity creates an implementation of that interface as an " +
"anonymous subclass. That anonymous subclass has a reference to the activity. " +
"Another process is keeping the android.app.IRequestFinishCallback\$Stub " +
"reference alive long after Activity.onDestroyed() has been called, " +
"causing the activity to leak."
) {
sdkInt == 29
}
}
},
ACTIVITY_CLIENT_RECORD__NEXT_IDLE {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.app.ActivityThread\$ActivityClientRecord", "nextIdle",
description = "Android AOSP sometimes keeps a reference to a destroyed activity as a"
+ " nextIdle client record in the android.app.ActivityThread.mActivities map."
+ " Not sure what's going on there, input welcome."
) {
sdkInt in 19..27
}
}
},
SPAN_CONTROLLER {
override fun add(
references: MutableList
) {
val description =
("Editor inserts a special span, which has a reference to the EditText. That span is a"
+ " NoCopySpan, which makes sure it gets dropped when creating a new"
+ " SpannableStringBuilder from a given CharSequence."
+ " TextView.onSaveInstanceState() does a copy of its mText before saving it in the"
+ " bundle. Prior to KitKat, that copy was done using the SpannableString"
+ " constructor, instead of SpannableStringBuilder. The SpannableString constructor"
+ " does not drop NoCopySpan spans. So we end up with a saved state that holds a"
+ " reference to the textview and therefore the entire view hierarchy & activity"
+ " context. Fix: https://github.com/android/platform_frameworks_base/commit"
+ "/af7dcdf35a37d7a7dbaad7d9869c1c91bce2272b ."
+ " To fix this, you could override TextView.onSaveInstanceState(), and then use"
+ " reflection to access TextView.SavedState.mText and clear the NoCopySpan spans.")
references += instanceFieldLeak(
"android.widget.Editor\$SpanController", "this$0", description
) {
sdkInt <= 19
}
references += instanceFieldLeak(
"android.widget.Editor\$EasyEditSpanController", "this$0", description
) {
sdkInt <= 19
}
}
},
MEDIA_SESSION_LEGACY_HELPER__SINSTANCE {
override fun add(
references: MutableList
) {
references +=
staticFieldLeak(
"android.media.session.MediaSessionLegacyHelper", "sInstance",
description = "MediaSessionLegacyHelper is a static singleton that is lazily instantiated and"
+ " keeps a reference to the context it's given the first time"
+ " MediaSessionLegacyHelper.getHelper() is called."
+ " This leak was introduced in android-5.0.1_r1 and fixed in Android 5.1.0_r1 by"
+ " calling context.getApplicationContext()."
+ " Fix: https://github.com/android/platform_frameworks_base/commit"
+ "/9b5257c9c99c4cb541d8e8e78fb04f008b1a9091"
+ " To fix this, you could call MediaSessionLegacyHelper.getHelper() early"
+ " in Application.onCreate() and pass it the application context."
) {
sdkInt == 21
}
}
},
TEXT_LINE__SCACHED {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.text.TextLine", "sCached",
description = "TextLine.sCached is a pool of 3 TextLine instances. TextLine.recycle() has had"
+ " at least two bugs that created memory leaks by not correctly clearing the"
+ " recycled TextLine instances. The first was fixed in android-5.1.0_r1:"
+ " https://github.com/android/platform_frameworks_base/commit"
+ "/893d6fe48d37f71e683f722457bea646994a10"
+ " The second was fixed, not released yet:"
+ " https://github.com/android/platform_frameworks_base/commit"
+ "/b3a9bc038d3a218b1dbdf7b5668e3d6c12be5e"
+ " To fix this, you could access TextLine.sCached and clear the pool every now"
+ " and then (e.g. on activity destroy)."
) {
sdkInt <= 22
}
}
},
BLOCKING_QUEUE {
override fun add(
references: MutableList
) {
val description = ("A thread waiting on a blocking queue will leak the last"
+ " dequeued object as a stack local reference. So when a HandlerThread becomes idle, it"
+ " keeps a local reference to the last message it received. That message then gets"
+ " recycled and can be used again. As long as all messages are recycled after being"
+ " used, this won't be a problem, because these references are cleared when being"
+ " recycled. However, dialogs create template Message instances to be copied when a"
+ " message needs to be sent. These Message templates holds references to the dialog"
+ " listeners, which most likely leads to holding a reference onto the activity in some"
+ " way. Dialogs never recycle their template Message, assuming these Message instances"
+ " will get GCed when the dialog is GCed."
+ " The combination of these two things creates a high potential for memory leaks as soon"
+ " as you use dialogs. These memory leaks might be temporary, but some handler threads"
+ " sleep for a long time."
+ " To fix this, you could post empty messages to the idle handler threads from time to"
+ " time. This won't be easy because you cannot access all handler threads, but a library"
+ " that is widely used should consider doing this for its own handler threads. This leaks"
+ " has been shown to happen in both Dalvik and ART.")
references += instanceFieldLeak("android.os.Message", "obj", description)
references += instanceFieldLeak("android.os.Message", "next", description)
references += instanceFieldLeak("android.os.Message", "target", description)
}
},
INPUT_METHOD_MANAGER_IS_TERRIBLE {
override fun add(
references: MutableList
) {
val description =
("When we detach a view that receives keyboard input, the InputMethodManager"
+ " leaks a reference to it until a new view asks for keyboard input."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=171190"
+ " Hack: https://gist.github.com/pyricau/4df64341cc978a7de414")
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mNextServedView", description
) {
sdkInt in 15..27
}
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mServedView", description
) {
sdkInt in 15..27
}
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mServedInputConnection", description
) {
sdkInt in 15..27
}
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mLastSrvView"
,
description =
"HUAWEI added a mLastSrvView field to InputMethodManager" + " that leaks a reference to the last served view."
) {
manufacturer == HUAWEI && sdkInt in 23..28
}
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mCurRootView",
description = "The singleton InputMethodManager is holding a reference to mCurRootView long"
+ " after the activity has been destroyed."
+ " Observed on ICS MR1: https://github.com/square/leakcanary/issues/1"
+ "#issuecomment-100579429"
+ " Hack: https://gist.github.com/pyricau/4df64341cc978a7de414"
) {
sdkInt in 15..28
}
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mImeInsetsConsumer",
description = """
Android Q Beta has a leak where InputMethodManager.mImeInsetsConsumer isn't set to
null when the activity is destroyed.
""".trimIndent()
) {
sdkInt == 28
}
references += instanceFieldLeak(
"android.view.inputmethod.InputMethodManager", "mCurrentInputConnection",
description = """
In Android Q Beta InputMethodManager keeps its EditableInputConnection after the
activity has been destroyed.
""".trimIndent()
) {
sdkInt == 28
}
}
},
LAYOUT_TRANSITION {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.animation.LayoutTransition$1", "val\$parent",
description = "LayoutTransition leaks parent ViewGroup through"
+ " ViewTreeObserver.OnPreDrawListener When triggered, this leaks stays until the"
+ " window is destroyed. Tracked here:"
+ " https://code.google.com/p/android/issues/detail?id=171830"
) {
sdkInt in 14..22
}
}
},
SPELL_CHECKER_SESSION {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.view.textservice.SpellCheckerSession$1", "this$0",
description = "SpellCheckerSessionListenerImpl.mHandler is leaking destroyed Activity when the"
+ " SpellCheckerSession is closed before the service is connected."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=172542"
) {
sdkInt in 16..24
}
}
},
SPELL_CHECKER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.widget.SpellChecker$1", "this$0",
description = "SpellChecker holds on to a detached view that points to a destroyed activity."
+ " mSpellRunnable is being enqueued, and that callback should be removed when "
+ " closeSession() is called. Maybe closeSession() wasn't called, or maybe it was "
+ " called after the view was detached."
) {
sdkInt == 22
}
}
},
ACTIVITY_CHOOSE_MODEL {
override fun add(
references: MutableList
) {
val description = ("ActivityChooserModel holds a static reference to the last set"
+ " ActivityChooserModelPolicy which can be an activity context."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=172659"
+ " Hack: https://gist.github.com/andaag/b05ab66ed0f06167d6e0")
references += instanceFieldLeak(
"android.support.v7.internal.widget.ActivityChooserModel",
"mActivityChoserModelPolicy",
description = description
) {
sdkInt in 15..22
}
references += instanceFieldLeak(
"android.widget.ActivityChooserModel", "mActivityChoserModelPolicy",
description = description
)
}
},
MEDIA_PROJECTION_CALLBACK {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.media.projection.MediaProjection\$MediaProjectionCallback",
"this$0", description = """
MediaProjectionCallback is held by another process, and holds on to MediaProjection
which has an activity as its context.
""".trimIndent()
) {
sdkInt in 22..28
}
}
},
SPEECH_RECOGNIZER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.speech.SpeechRecognizer\$InternalListener", "this$0"
,
description = "Prior to Android 5, SpeechRecognizer.InternalListener was a non static inner"
+ " class and leaked the SpeechRecognizer which leaked an activity context."
+ " Fixed in AOSP: https://github.com/android/platform_frameworks_base/commit"
+ " /b37866db469e81aca534ff6186bdafd44352329b"
) {
sdkInt < 21
}
}
},
ACCOUNT_MANAGER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.accounts.AccountManager\$AmsTask\$Response", "this$1"
,
description =
"AccountManager\$AmsTask\$Response is a stub and is held in memory by native code,"
+ " probably because the reference to the response in the other process hasn't been"
+ " cleared."
+ " AccountManager\$AmsTask is holding on to the activity reference to use for"
+ " launching a new sub- Activity."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=173689"
+ " Fix: Pass a null activity reference to the AccountManager methods and then deal"
+ " with the returned future to to get the result and correctly start an activity"
+ " when it's available."
) {
sdkInt <= 27
}
}
},
MEDIA_SCANNER_CONNECTION {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.media.MediaScannerConnection", "mContext",
description =
"The static method MediaScannerConnection.scanFile() takes an activity context"
+ " but the service might not disconnect after the activity has been destroyed."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=173788"
+ " Fix: Create an instance of MediaScannerConnection yourself and pass in the"
+ " application context. Call connect() and disconnect() manually."
) {
sdkInt <= 22
}
}
},
USER_MANAGER__SINSTANCE {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.os.UserManager", "mContext",
description =
"UserManager has a static sInstance field that creates an instance and caches it"
+ " the first time UserManager.get() is called. This instance is created with the"
+ " outer context (which is an activity base context)."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=173789"
+ " Introduced by: https://github.com/android/platform_frameworks_base/commit"
+ "/27db46850b708070452c0ce49daf5f79503fbde6"
+ " Fix: trigger a call to UserManager.get() in Application.onCreate(), so that the"
+ " UserManager instance gets cached with a reference to the application context."
) {
sdkInt in 18..25
}
}
},
APP_WIDGET_HOST_CALLBACKS {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.appwidget.AppWidgetHost\$Callbacks", "this$0"
,
description =
"android.appwidget.AppWidgetHost\$Callbacks is a stub and is held in memory native"
+ " code. The reference to the `mContext` was not being cleared, which caused the"
+ " Callbacks instance to retain this reference"
+ " Fixed in AOSP: https://github.com/android/platform_frameworks_base/commit"
+ "/7a96f3c917e0001ee739b65da37b2fadec7d7765"
) {
sdkInt < 22
}
}
},
AUDIO_MANAGER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.media.AudioManager$1", "this$0",
description =
"Prior to Android M, VideoView required audio focus from AudioManager and"
+ " never abandoned it, which leaks the Activity context through the AudioManager."
+ " The root of the problem is that AudioManager uses whichever"
+ " context it receives, which in the case of the VideoView example is an Activity,"
+ " even though it only needs the application's context. The issue is fixed in"
+ " Android M, and the AudioManager now uses the application's context."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=152173"
+ " Fix: https://gist.github.com/jankovd/891d96f476f7a9ce24e2"
) {
sdkInt <= 22
}
}
},
EDITTEXT_BLINK_MESSAGEQUEUE {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.widget.Editor\$Blink", "this$0",
description =
"The EditText Blink of the Cursor is implemented using a callback and Messages,"
+ " which trigger the display of the Cursor. If an AlertDialog or DialogFragment that"
+ " contains a blinking cursor is detached, a message is posted with a delay after the"
+ " dialog has been closed and as a result leaks the Activity."
+ " This can be fixed manually by calling TextView.setCursorVisible(false) in the"
+ " dismiss() method of the dialog."
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=188551"
+ " Fixed in AOSP: https://android.googlesource.com/platform/frameworks/base/+"
+ "/5b734f2430e9f26c769d6af8ea5645e390fcf5af%5E%21/"
) {
sdkInt <= 23
}
}
},
CONNECTIVITY_MANAGER__SINSTANCE {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.net.ConnectivityManager", "sInstance",
description =
"ConnectivityManager has a sInstance field that is set when the first"
+ " ConnectivityManager instance is created. ConnectivityManager has a mContext field."
+ " When calling activity.getSystemService(Context.CONNECTIVITY_SERVICE) , the first"
+ " ConnectivityManager instance is created with the activity context and stored in"
+ " sInstance. That activity context then leaks forever."
+ " Until this is fixed, app developers can prevent this leak by making sure the"
+ " ConnectivityManager is first created with an App Context. E.g. in some static"
+ " init do: context.getApplicationContext()"
+ ".getSystemService(Context.CONNECTIVITY_SERVICE)"
+ " Tracked here: https://code.google.com/p/android/issues/detail?id=198852"
+ " Introduced here: https://github.com/android/platform_frameworks_base/commit/"
+ "e0bef71662d81caaaa0d7214fb0bef5d39996a69"
) {
sdkInt <= 23
}
}
},
ACCESSIBILITY_NODE_INFO__MORIGINALTEXT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.view.accessibility.AccessibilityNodeInfo", "mOriginalText"
,
description =
"AccessibilityNodeInfo has a static sPool of AccessibilityNodeInfo. When"
+ " AccessibilityNodeInfo instances are released back in the pool,"
+ " AccessibilityNodeInfo.clear() does not clear the mOriginalText field, which"
+ " causes spans to leak which in turns causes TextView.ChangeWatcher to leak and the"
+ " whole view hierarchy. Introduced here: https://android.googlesource.com/platform/"
+ "frameworks/base/+/193520e3dff5248ddcf8435203bf99d2ba667219%5E%21/core/java/"
+ "android/view/accessibility/AccessibilityNodeInfo.java"
) {
sdkInt in 26..27
}
}
},
ASSIST_STRUCTURE {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.app.assist.AssistStructure\$ViewNodeText", "mText"
,
description = "AssistStructure (google assistant / autofill) holds on to text spannables" +
" on the screen. TextView.ChangeWatcher and android.widget.Editor end up in spans and" +
" typically hold on to the view hierarchy"
) {
sdkInt in 24..28
}
}
},
ACCESSIBILITY_ITERATORS {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.widget.AccessibilityIterators\$LineTextSegmentIterator", "mLayout"
,
description = "AccessibilityIterators holds on to text layouts which can hold on to spans" +
" TextView.ChangeWatcher and android.widget.Editor end up in spans and" +
" typically hold on to the view hierarchy"
) {
sdkInt == 27
}
}
},
BIOMETRIC_PROMPT {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.hardware.biometrics.BiometricPrompt", "mFingerprintManager"
,
description = "BiometricPrompt holds on to a FingerprintManager which holds on to a " +
"destroyed activity."
) {
sdkInt == 28
}
}
},
MAGNIFIER {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.widget.Magnifier\$InternalPopupWindow", "mCallback"
,
description = "android.widget.Magnifier.InternalPopupWindow registers a frame callback" +
" on android.view.ThreadedRenderer.SimpleRenderer which holds it as a native" +
" reference. android.widget.Editor\$InsertionHandleView registers an" +
" OnOperationCompleteCallback on Magnifier.InternalPopupWindow. These references are" +
" held after the activity has been destroyed."
) {
sdkInt == 28
}
}
},
BACKDROP_FRAME_RENDERER__MDECORVIEW {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"com.android.internal.policy.BackdropFrameRenderer", "mDecorView"
,
description =
"When BackdropFrameRenderer.releaseRenderer() is called, there's an unknown case"
+ " where mRenderer becomes null but mChoreographer doesn't and the thread doesn't"
+ " stop and ends up leaking mDecorView which itself holds on to a destroyed"
+ " activity"
) {
sdkInt in 24..26
}
}
},
VIEWLOCATIONHOLDER_ROOT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"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"
) {
sdkInt == 28
}
}
},
ACCESSIBILITY_NODE_ID_MANAGER {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.view.accessibility.AccessibilityNodeIdManager", "mIdsToViews"
,
description = """
Android Q Beta added AccessibilityNodeIdManager which stores all views from their
onAttachedToWindow() call, until detached. Unfortunately it's possible to trigger
the view framework to call detach before attach (by having a view removing itself
from its parent in onAttach, which then causes AccessibilityNodeIdManager to keep
children view forever. Future releases of Q will hold weak references.
""".trimIndent()
) {
sdkInt in 28..29
}
}
},
TEXT_TO_SPEECH {
override fun add(
references: MutableList
) {
val description =
("TextToSpeech.shutdown() does not release its references to context objects." +
" Furthermore, TextToSpeech instances cannot be garbage collected due to other process" +
" keeping the references, resulting the context objects leaked." +
" Developers might be able to mitigate the issue by passing application context" +
" to TextToSpeech constructor." +
" Tracked at: https://github.com/square/leakcanary/issues/1210 and" +
" https://issuetracker.google.com/issues/129250419")
references += instanceFieldLeak(
"android.speech.tts.TextToSpeech", "mContext",
description = description
) {
sdkInt == 24
}
references += instanceFieldLeak(
"android.speech.tts.TtsEngines", "mContext",
description = description
) {
sdkInt == 24
}
}
},
WINDOW_MANAGER_GLOBAL {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.view.WindowManagerGlobal", "mRoots"
,
description = """
ViewRootImpl references a destroyed activity yet it's not detached (still has a view)
and WindowManagerGlobal still references it.
""".trimIndent()
) {
sdkInt == 27
}
}
},
CONTROLLED_INPUT_CONNECTION_WRAPPER {
override fun add(references: MutableList) {
references += nativeGlobalVariableLeak(
"android.view.inputmethod.InputMethodManager\$ControlledInputConnectionWrapper",
description = """
ControlledInputConnectionWrapper is held by a global variable in native code.
""".trimIndent()
)
}
},
TOAST_TN {
override fun add(references: MutableList) {
references += nativeGlobalVariableLeak(
"android.widget.Toast\$TN",
description = """
Toast.TN is held by a global variable in native code due to an IPC call to show the toast.
""".trimIndent()
)
}
},
// ######## Manufacturer specific known leaks ########
// SAMSUNG
SPEN_GESTURE_MANAGER {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"com.samsung.android.smartclip.SpenGestureManager", "mContext"
,
description =
"SpenGestureManager has a static mContext field that leaks a reference to the" + " activity. Yes, a STATIC mContext field."
) {
manufacturer == SAMSUNG && sdkInt == 19
}
}
},
CLIPBOARD_UI_MANAGER__SINSTANCE {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.sec.clipboard.ClipboardUIManager", "mContext"
,
description =
"ClipboardUIManager is a static singleton that leaks an activity context."
+ " Fix: trigger a call to ClipboardUIManager.getInstance() in Application.onCreate()"
+ " , so that the ClipboardUIManager instance gets cached with a reference to the"
+ " application context. Example: https://gist.github.com/cypressious/"
+ "91c4fb1455470d803a602838dfcd5774"
) {
manufacturer == SAMSUNG && sdkInt in 19..21
}
}
},
SEM_CLIPBOARD_MANAGER__MCONTEXT {
override fun add(
references: MutableList
) {
val description = """
SemClipboardManager inner classes are held by native references due to IPC calls
""".trimIndent()
references += nativeGlobalVariableLeak(
"com.samsung.android.content.clipboard.SemClipboardManager$1", description
) {
manufacturer == SAMSUNG && sdkInt in 19..28
}
references += nativeGlobalVariableLeak(
"com.samsung.android.content.clipboard.SemClipboardManager$3", description
) {
manufacturer == SAMSUNG && sdkInt in 19..28
}
}
},
CLIPBOARD_EX_MANAGER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.sec.clipboard.ClipboardExManager", "mContext",
description = "android.sec.clipboard.ClipboardExManager\$IClipboardDataPasteEventImpl\$1" +
" is a native callback that holds IClipboardDataPasteEventImpl which holds" +
" ClipboardExManager which has a destroyed activity as mContext"
) {
manufacturer == SAMSUNG && sdkInt == 23
}
references += instanceFieldLeak(
"android.sec.clipboard.ClipboardExManager", "mPersonaManager",
description = "android.sec.clipboard.ClipboardExManager\$IClipboardDataPasteEventImpl\$1" +
" is a native callback that holds IClipboardDataPasteEventImpl which holds" +
" ClipboardExManager which holds PersonaManager which has a destroyed activity as" +
" mContext"
) {
manufacturer == SAMSUNG && sdkInt == 23
}
references += instanceFieldLeak(
"android.widget.TextView\$IClipboardDataPasteEventImpl", "this\$0",
description = "TextView\$IClipboardDataPasteEventImpl\$1 is held by a native ref, and" +
" IClipboardDataPasteEventImpl ends up leaking a detached textview"
) {
manufacturer == SAMSUNG && sdkInt == 22
}
}
},
SEM_EMERGENCY_MANAGER__MCONTEXT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"com.samsung.android.emergencymode.SemEmergencyManager", "mContext"
,
description =
"SemEmergencyManager is a static singleton that leaks a DecorContext." +
" Fix: https://gist.github.com/jankovd/a210460b814c04d500eb12025902d60d"
) {
manufacturer == SAMSUNG && sdkInt in 19..24
}
}
},
SEM_PERSONA_MANAGER {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"com.samsung.android.knox.SemPersonaManager", "mContext"
) {
manufacturer == SAMSUNG && sdkInt == 24
}
}
},
SEM_APP_ICON_SOLUTION {
override fun add(references: MutableList) {
references += instanceFieldLeak(
"android.app.SemAppIconSolution", "mContext"
) {
manufacturer == SAMSUNG && sdkInt == 28
}
}
},
AW_RESOURCE__SRESOURCES {
override fun add(
references: MutableList
) {
// AwResource#setResources() is called with resources that hold a reference to the
// activity context (instead of the application context) and doesn't clear it.
// Not sure what's going on there, input welcome.
references += staticFieldLeak(
"com.android.org.chromium.android_webview.AwResource", "sResources"
) {
manufacturer == SAMSUNG && sdkInt == 19
}
}
},
TEXT_VIEW__MLAST_HOVERED_VIEW {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.widget.TextView", "mLastHoveredView",
description =
"mLastHoveredView is a static field in TextView that leaks the last hovered" + " view."
) {
manufacturer == SAMSUNG && sdkInt in 19..28
}
}
},
PERSONA_MANAGER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.os.PersonaManager", "mContext",
description =
"android.app.LoadedApk.mResources has a reference to"
+ " android.content.res.Resources.mPersonaManager which has a reference to"
+ " android.os.PersonaManager.mContext which is an activity."
) {
manufacturer == SAMSUNG && sdkInt == 19
}
}
},
RESOURCES__MCONTEXT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.content.res.Resources", "mContext",
description =
"In AOSP the Resources class does not have a context."
+ " Here we have ZygoteInit.mResources (static field) holding on to a Resources"
+ " instance that has a context that is the activity."
+ " Observed here: https://github.com/square/leakcanary/issues/1#issue-74450184"
) {
manufacturer == SAMSUNG && sdkInt == 19
}
}
},
VIEW_CONFIGURATION__MCONTEXT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.view.ViewConfiguration", "mContext",
description =
"In AOSP the ViewConfiguration class does not have a context."
+ " Here we have ViewConfiguration.sConfigurations (static field) holding on to a"
+ " ViewConfiguration instance that has a context that is the activity."
+ " Observed here: https://github.com/square/leakcanary/issues"
+ "/1#issuecomment-100324683"
) {
manufacturer == SAMSUNG && sdkInt == 19
}
}
},
AUDIO_MANAGER__MCONTEXT_STATIC {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.media.AudioManager", "mContext_static",
description =
"Samsung added a static mContext_static field to AudioManager, holds a reference"
+ " to the activity."
+ " Observed here: https://github.com/square/leakcanary/issues/32"
) {
manufacturer == SAMSUNG && sdkInt == 19
}
}
},
ACTIVITY_MANAGER_MCONTEXT {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.app.ActivityManager", "mContext",
description =
"Samsung added a static mContext field to ActivityManager, holds a reference"
+ " to the activity."
+ " Observed here: https://github.com/square/leakcanary/issues/177 Fix in comment:"
+ " https://github.com/square/leakcanary/issues/177#issuecomment-222724283"
) {
manufacturer == SAMSUNG && sdkInt in 22..23
}
}
},
STATIC_MTARGET_VIEW {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.widget.TextView", "mTargetView",
description =
"Samsung added a static mTargetView field to TextView which holds on to detached views."
) {
manufacturer == SAMSUNG && sdkInt == 27
}
}
},
// OTHER MANUFACTURERS
GESTURE_BOOST_MANAGER {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.gestureboost.GestureBoostManager", "mContext"
,
description =
"GestureBoostManager is a static singleton that leaks an activity context." +
" Fix: https://github.com/square/leakcanary/issues/696#issuecomment-296420756"
) {
manufacturer == HUAWEI && sdkInt in 24..25
}
}
},
BUBBLE_POPUP_HELPER__SHELPER {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.widget.BubblePopupHelper", "sHelper",
description =
"A static helper for EditText bubble popups leaks a reference to the latest" + " focused view."
) {
manufacturer == LG && sdkInt in 19..22
}
}
},
LGCONTEXT__MCONTEXT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"com.lge.systemservice.core.LGContext", "mContext",
description = "LGContext is a static singleton that leaks an activity context."
) {
manufacturer == LG && sdkInt == 21
}
}
},
SMART_COVER_MANAGER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"com.lge.systemservice.core.SmartCoverManager", "mContext",
description = "SmartCoverManager\$CallbackRegister is a callback held by a native ref," +
" and SmartCoverManager ends up leaking an activity context."
) {
manufacturer == LG && sdkInt == 27
}
}
},
MAPPER_CLIENT {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"com.nvidia.ControllerMapper.MapperClient\$ServiceClient", "this$0"
,
description =
"Not sure exactly what ControllerMapper is about, but there is an anonymous"
+ " Handler in ControllerMapper.MapperClient.ServiceClient, which leaks"
+ " ControllerMapper.MapperClient which leaks the activity context."
) {
manufacturer == NVIDIA && sdkInt == 19
}
}
},
SYSTEM_SENSOR_MANAGER__MAPPCONTEXTIMPL {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.hardware.SystemSensorManager", "mAppContextImpl"
,
description =
"SystemSensorManager stores a reference to context"
+ " in a static field in its constructor."
+ " Fix: use application context to get SensorManager"
) {
(manufacturer == LENOVO && sdkInt == 19) || (manufacturer == VIVO && sdkInt == 22)
}
}
},
INSTRUMENTATION_RECOMMEND_ACTIVITY {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.app.Instrumentation", "mRecommendActivity",
description =
"Instrumentation would leak com.android.internal.app.RecommendActivity (in"
+ " framework.jar) in Meizu FlymeOS 4.5 and above, which is based on Android 5.0 and "
+ " above"
) {
manufacturer == MEIZU && sdkInt in 21..22
}
}
},
DEVICE_POLICY_MANAGER__SETTINGS_OBSERVER {
override fun add(
references: MutableList
) {
references += instanceFieldLeak(
"android.app.admin.DevicePolicyManager\$SettingsObserver", "this$0"
,
description =
"DevicePolicyManager keeps a reference to the context it has been created with"
+ " instead of extracting the application context. In this Motorola build,"
+ " DevicePolicyManager has an inner SettingsObserver class that is a content"
+ " observer, which is held into memory by a binder transport object."
) {
manufacturer == MOTOROLA && sdkInt in 19..22
}
}
},
EXTENDED_STATUS_BAR_MANAGER {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"android.app.ExtendedStatusBarManager", "sInstance"
,
description =
"""
ExtendedStatusBarManager is held in a static sInstance field and has a mContext
field which references a decor context which references a destroyed activity.
""".trimIndent()
) {
manufacturer == SHARP && sdkInt == 28
}
}
},
OEM_SCENE_CALL_BLOCKER {
override fun add(
references: MutableList
) {
references += staticFieldLeak(
"com.oneplus.util.OemSceneCallBlocker", "sContext",
description =
"""
OemSceneCallBlocker has a sContext static field which holds on to an activity instance.
""".trimIndent()
) {
manufacturer == ONE_PLUS && sdkInt == 28
}
}
},
// ######## Ignored references (not leaks) ########
REFERENCES {
override fun add(
references: MutableList
) {
references += ignoredInstanceField(WeakReference::class.java.name, "referent")
references += ignoredInstanceField("leakcanary.KeyedWeakReference", "referent")
references += ignoredInstanceField(SoftReference::class.java.name, "referent")
references += ignoredInstanceField(PhantomReference::class.java.name, "referent")
references += ignoredInstanceField("java.lang.ref.Finalizer", "prev")
references += ignoredInstanceField("java.lang.ref.Finalizer", "element")
references += ignoredInstanceField("java.lang.ref.Finalizer", "next")
references += ignoredInstanceField("java.lang.ref.FinalizerReference", "prev")
references += ignoredInstanceField("java.lang.ref.FinalizerReference", "element")
references += ignoredInstanceField("java.lang.ref.FinalizerReference", "next")
references += ignoredInstanceField("sun.misc.Cleaner", "prev")
references += ignoredInstanceField("sun.misc.Cleaner", "next")
}
},
FINALIZER_WATCHDOG_DAEMON {
override fun add(
references: MutableList
) {
// If the FinalizerWatchdogDaemon thread is on the shortest path, then there was no other
// reference to the object and it was about to be GCed.
references += ignoredJavaLocal("FinalizerWatchdogDaemon")
}
},
MAIN {
override fun add(
references: MutableList
) {
// The main thread stack is ever changing so local variables aren't likely to hold references
// for long. If this is on the shortest path, it's probably that there's a longer path with
// a real leak.
references += ignoredJavaLocal("main")
}
},
LEAK_CANARY_THREAD {
override fun add(
references: MutableList
) {
references += ignoredJavaLocal(LEAK_CANARY_THREAD_NAME)
}
},
LEAK_CANARY_HEAP_DUMPER {
override fun add(references: MutableList) {
// Holds on to the resumed activity (which is never destroyed), so this will not cause leaks
// but may surface on the path when a resumed activity holds on to destroyed objects.
// Would have a path that doesn't include LeakCanary instead.
references += ignoredInstanceField("leakcanary.internal.AndroidHeapDumper", "resumedActivity")
}
},
LEAK_CANARY_INTERNAL {
override fun add(references: MutableList) {
references += ignoredInstanceField("leakcanary.internal.InternalLeakCanary", "application")
}
},
EVENT_RECEIVER__MMESSAGE_QUEUE {
override fun add(
references: MutableList
) {
// DisplayEventReceiver keeps a reference message queue object so that it is not GC'd while
// the native peer of the receiver is using them.
// The main thread message queue is held on by the main Looper, but that might be a longer
// path. Let's not confuse people with a shorter path that is less meaningful.
references += ignoredInstanceField(
"android.view.Choreographer\$FrameDisplayEventReceiver", "mMessageQueue"
)
}
},
;
internal abstract fun add(references: MutableList)
companion object {
private const val LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump"
const val SAMSUNG = "samsung"
const val MOTOROLA = "motorola"
const val LENOVO = "LENOVO"
const val LG = "LGE"
const val NVIDIA = "NVIDIA"
const val MEIZU = "Meizu"
const val ONE_PLUS = "OnePlus"
const val HUAWEI = "HUAWEI"
const val VIVO = "vivo"
const val SHARP = "SHARP"
/**
* Returns a list of [ReferenceMatcher] that only contains [IgnoredReferenceMatcher] and no
* [LibraryLeakReferenceMatcher].
*/
val ignoredReferencesOnly: List
get() = buildKnownReferences(
EnumSet.of(
REFERENCES,
FINALIZER_WATCHDOG_DAEMON,
MAIN,
LEAK_CANARY_THREAD,
EVENT_RECEIVER__MMESSAGE_QUEUE
)
)
/**
* @see [AndroidReferenceMatchers]
*/
val appDefaults: List
get() = buildKnownReferences(EnumSet.allOf(AndroidReferenceMatchers::class.java))
/**
* Builds a list of [ReferenceMatcher] from the [referenceMatchers] set of
* [AndroidReferenceMatchers].
*/
fun buildKnownReferences(referenceMatchers: Set): List {
val resultSet = mutableListOf()
referenceMatchers.forEach {
it.add(resultSet)
}
return resultSet
}
private val ALWAYS: AndroidBuildMirror.() -> Boolean = {
true
}
/**
* Creates a [LibraryLeakReferenceMatcher] that matches a [StaticFieldPattern].
* [description] should convey what we know about this library leak.
*/
fun staticFieldLeak(
className: String,
fieldName: String,
description: String = "",
patternApplies: AndroidBuildMirror.() -> Boolean = ALWAYS
): LibraryLeakReferenceMatcher {
return libraryLeak(StaticFieldPattern(className, fieldName), description, patternApplies)
}
/**
* Creates a [LibraryLeakReferenceMatcher] that matches a [InstanceFieldPattern].
* [description] should convey what we know about this library leak.
*/
fun instanceFieldLeak(
className: String,
fieldName: String,
description: String = "",
patternApplies: AndroidBuildMirror.() -> Boolean = ALWAYS
): LibraryLeakReferenceMatcher {
return libraryLeak(InstanceFieldPattern(className, fieldName), description, patternApplies)
}
fun nativeGlobalVariableLeak(
className: String,
description: String = "",
patternApplies: AndroidBuildMirror.() -> Boolean = ALWAYS
): LibraryLeakReferenceMatcher {
return libraryLeak(NativeGlobalVariablePattern(className), description, patternApplies)
}
private fun libraryLeak(
referencePattern: ReferencePattern,
description: String,
patternApplies: AndroidBuildMirror.() -> Boolean
): LibraryLeakReferenceMatcher {
return LibraryLeakReferenceMatcher(
pattern = referencePattern,
description = description,
patternApplies = { graph ->
AndroidBuildMirror.fromHeapGraph(graph)
.patternApplies()
}
)
}
/**
* Creates a [IgnoredReferenceMatcher] that matches a [InstanceFieldPattern].
*/
fun ignoredInstanceField(
className: String,
fieldName: String
): IgnoredReferenceMatcher {
return IgnoredReferenceMatcher(pattern = InstanceFieldPattern(className, fieldName))
}
/**
* Creates a [IgnoredReferenceMatcher] that matches a [JavaLocalPattern].
*/
fun ignoredJavaLocal(
threadName: String
): IgnoredReferenceMatcher {
return IgnoredReferenceMatcher(pattern = JavaLocalPattern(threadName))
}
}
}