@Before public void setUp() { excludedRefs = new ExcludedRefs.BuilderWithParams() // .clazz(WeakReference.class.getName()) .alwaysExclude() .clazz("java.lang.ref.FinalizerReference") .alwaysExclude() .clazz(PhantomReference.class.getName()) .alwaysExclude(); }
@Test public void excludeStatic() { excludedRefs.thread(ASYNC_TASK_THREAD).named(ASYNC_TASK_THREAD); excludedRefs.staticField(ASYNC_TASK_CLASS, EXECUTOR_FIELD_1).named(EXECUTOR_FIELD_1); excludedRefs.staticField(ASYNC_TASK_CLASS, EXECUTOR_FIELD_2).named(EXECUTOR_FIELD_2); AnalysisResult result = analyze(heapDumpFile, excludedRefs); assertTrue(result.leakFound); assertTrue(result.excludedLeak); LeakTrace leakTrace = result.leakTrace; List<LeakTraceElement> elements = leakTrace.elements; Exclusion exclusion = elements.get(0).exclusion; List<String> expectedExclusions = asList(ASYNC_TASK_THREAD, EXECUTOR_FIELD_1, EXECUTOR_FIELD_2); assertTrue(expectedExclusions.contains(exclusion.name)); } }
static AnalysisResult analyze(HeapDumpFile heapDumpFile, ExcludedRefs.BuilderWithParams excludedRefs) { File file = fileFromName(heapDumpFile.filename); String referenceKey = heapDumpFile.referenceKey; HeapAnalyzer heapAnalyzer = new HeapAnalyzer(excludedRefs.build(), AnalyzerProgressListener.NONE, Collections.<Class<? extends Reachability.Inspector>>emptyList()); AnalysisResult result = heapAnalyzer.checkForLeak(file, referenceKey, true); if (result.failure != null) { result.failure.printStackTrace(); } if (result.leakTrace != null) { System.out.println(result.leakTrace); } return result; }
public static Builder builder() { return new BuilderWithParams(); }
@Override void add(ExcludedRefs.Builder excluded) { String reason = "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."; excluded.instanceField("android.os.Message", "obj").reason(reason); excluded.instanceField("android.os.Message", "next").reason(reason); excluded.instanceField("android.os.Message", "target").reason(reason); } },
@Override void add(ExcludedRefs.Builder excluded) { String reason = "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."; excluded.instanceField("android.widget.Editor$EasyEditSpanController", "this$0") .reason(reason); excluded.instanceField("android.widget.Editor$SpanController", "this$0").reason(reason); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.net.ConnectivityManager", "sInstance") .reason("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"); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.staticField("android.text.TextLine", "sCached") .reason("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)."); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.widget.Editor$Blink", "this$0") .reason("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/"); } },
@Override void add(ExcludedRefs.Builder excluded) { String reason = "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"; excluded.instanceField("android.view.inputmethod.InputMethodManager", "mNextServedView") .reason(reason); excluded.instanceField("android.view.inputmethod.InputMethodManager", "mServedView") .reason(reason); excluded.instanceField("android.view.inputmethod.InputMethodManager", "mServedInputConnection").reason(reason); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.staticField("android.media.session.MediaSessionLegacyHelper", "sInstance") .reason("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."); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.thread(LEAK_CANARY_THREAD_NAME).alwaysExclude(); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("com.nvidia.ControllerMapper.MapperClient$ServiceClient", "this$0") .reason("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."); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("com.android.internal.policy.BackdropFrameRenderer", "mDecorView") .reason("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"); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.view.accessibility.AccessibilityNodeInfo", "mOriginalText") .reason("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"); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.os.UserManager", "mContext") .reason("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."); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.view.textservice.SpellCheckerSession$1", "this$0") .reason("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"); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.animation.LayoutTransition$1", "val$parent") .reason("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"); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.staticField("android.media.AudioManager", "mContext_static") .reason("Samsung added a static mContext_static field to AudioManager, holds a reference" + " to the activity." + " Observed here: https://github.com/square/leakcanary/issues/32"); } },
@Override void add(ExcludedRefs.Builder excluded) { excluded.instanceField("android.view.ViewConfiguration", "mContext") .reason("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"); } },