LeakCanary is a memory leak detection tool for and by Square .Android
Java
use
LeakCanary
The integration process is very simple, first add the dependencies in the build.gradle
file :
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
debug
and release
versions are using different libraries. LeakCanary
The runtime performs GC
operations , release
which affects efficiency in the version. android-no-op
There is basically no logic implementation in the version, which is used for the release
version .
Then implement your own Application
class :
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
This completes the integration. LeakCanary
When a memory leak is detected, it will automatically pop up Notification
to notify the developer of the memory leak Activity
and reference chain for repair.
Source code analysis
LeakCanary.install(this)
Start the analysis from the entry function :
LeakCanary.install
LeakCanary.java
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
LeakCanary.refWatcher
LeakCanary.java
/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
refWatcher()
The method creates a new AndroidRefWatcherBuilder
object , which inherits from the RefWatcherBuilder
class, configures some default parameters, and uses the builder to build an RefWatcher
object .
AndroidRefWatcherBuilder.listenerServiceClass
AndroidRefWatcherBuilder.java
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
RefWatcherBuilder.java
/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
DisplayLeakService.java
/**
* Logs leak analysis results, and then shows a notification which will start {@link
* DisplayLeakActivity}.
*
* You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,
* String)} to add custom behavior, e.g. uploading the heap dump.
*/
public class DisplayLeakService extends AbstractAnalysisResultService {}
listenerServiceClass()
The method is bound to a background service DisplayLeakService
, which is mainly used to analyze the results of memory leaks and send notifications. You can inherit and override this class to perform some custom operations, such as uploading analysis results.
RefWatcherBuilder.excludedRefs
RefWatcherBuilder.java
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}
AndroidExcludedRefs.java
/**
* This returns the references in the leak path that can be ignored for app developers. This
* doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs
* in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app
* developers except by resorting to serious hacks, so we remove the noise caused by those leaks.
*/
public static ExcludedRefs.Builder createAppDefaults() {
return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}
public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
ExcludedRefs.Builder excluded = ExcludedRefs.builder();
for (AndroidExcludedRefs ref : refs) {
if (ref.applies) {
ref.add(excluded);
((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
}
}
return excluded;
}
excludedRefs()
The method defines some paths that can be ignored by the developer, which means that even if there is a memory leak here, the LeakCanary
notification will not pop up. Most of these are caused by system bugs and do not need to be dealt with by the user.
AndroidRefWatcherBuilder.buildAndInstall
Finally call the buildAndInstall()
method to build RefWatcher
the instance and start listening Activity
for references:
AndroidRefWatcherBuilder.java
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
Take a look at the main build()
and install()
methods:
RefWatcherBuilder.build
RefWatcherBuilder.java
/** Creates a {@link RefWatcher}. */
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
build()
The method uses the builder pattern to build an RefWatcher
instance , and take a look at the main parameters:
watchExecutor
: thread controller, performs memory leak detectiononDestroy()
after and when the main thread is idledebuggerControl
: Determine whether it is in debug mode, memory leak detection will not be performed in debug modegcTrigger
: Used toGC
detectwatchExecutor
a possible memory leak for the first time, it will take the initiative to detect itGC
, andGC
then it will be detected again. If it is still leaking, it will be determined as a memory leak, and follow-up operations will be performed.heapDumper
: infodump
on memory leak , write to fileheap
hprof
heapDumpListener
: After parsing thehprof
file and notifyingDisplayLeakService
a pop-up reminderexcludedRefs
: exclude ignorable leak paths
LeakCanary.enableDisplayLeakActivity
The next step is the core install()
method , and here we start to observe Activity
the reference. There is one more step before this, LeakCanary.enableDisplayLeakActivity(context);
:
public static void enableDisplayLeakActivity(Context context) {
setEnabled(context, DisplayLeakActivity.class, true);
}
Finally executed to LeakCanaryInternals#setEnabledBlocking
:
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
Here is enabled DisplayLeakActivity
and app icon is displayed. Note that this does not refer to your own application icon, but a LeakCanary
separate application to display the history of memory leaks. The entry function is DisplayLeakActivity
, you can see it in AndroidManifest.xml by default android:enabled="false"
:
<activity
android:theme="@style/leak_canary_LeakCanary.Base"
android:name=".internal.DisplayLeakActivity"
android:process=":leakcanary"
android:enabled="false"
android:label="@string/leak_canary_display_activity_label"
android:icon="@mipmap/leak_canary_icon"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
ActivityRefWatcher.install
ActivityRefWatcher.java
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
watchActivities()
In the method, the lifecycle callback registration is unbound first lifecycleCallbacks
, and then re-bound to avoid repeated binding. lifecycleCallbacks
Monitored Activity
the various life cycles of , onDestroy()
and began to detect Activity
the reference in .
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
The following focuses on the analysis RefWatcher
of how to detect Activity
.
RefWatcher.watch
Call RefWatcher#watch
detection Activity
.RefWatcher.java
/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
watch()
The parameters of the method are Object
, LeakCanary
not just Android
for , it can detect memory leaks of any object, the principle is the same.
Here are a few new faces, let's take a look at what each is:
retainedKeys
: aSet<String>
collection , each detected object corresponds to a uniquekey
, storedretainedKeys
inKeyedWeakReference
: A custom weak reference that holds the detection object and thekey
value
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
queue
:ReferenceQueue
objectKeyedWeakReference
, used with
Here is a little knowledge point. When weak references and reference queues are used in ReferenceQueue
combination , if the object held by the weak reference is garbage collected, the Java virtual machine will add the weak reference to the reference queue associated with it. That is, if the KeyedWeakReference
held Activity
object is garbage collected, the object will be added to the reference queue
queue .
Then look at the specific memory leak judgment process:
RefWatcher.ensureGoneAsync
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
watchExecutor
Perform the detection operation by , where watchExecutor
is the AndroidWatchExecutor
object .
@Override protected WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
DEFAULT_WATCH_DELAY_MILLIS
is 5 s.
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
Take a look at a few objects used in it:
mainHandler
: Main thread message queuehandlerThread
: background thread,HandlerThread
object, thread nameLeakCanary-Heap-Dump
backgroundHandler
: The message queue of the background thread aboveinitialDelayMillis
: 5 s, i.e. beforeDEFAULT_WATCH_DELAY_MILLIS
@Override public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
In the specific execute()
process , whether it is waitForIdle
or postWaitForIdle
, it will eventually switch to the main thread for execution. It should be noted that when is the implementation IdleHandler
here ?
We all know Handler
that it processes messages in a loop MessageQueue
. When there are no more messages to be processed in the message queue, and the IdleHandler
interface , this is the operation that will be processed here. That is, specify some operations to process when the thread is idle. When the main thread is idle, it will notify the background thread to perform memory leak detection with a delay of 5 seconds.
void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
Here's the actual detection process, AndroidWatchExecutor
which calls the ensureGone()
method :
RefWatcher.ensureGone
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
Repeat the meaning of several variables again, it retainedKeys
is a Set
collection , which stores the unique key
value corresponding to the detection object, and it queue
is a reference queue, which stores the objects that are garbage collected.
The main process has the following steps:
RefWatcher.emoveWeaklyReachableReferences()
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
Traverse the reference queue queue
to determine whether there is a Activity
current weak reference in the queue, and delete retainedKeys
the key
value .
RefWatcher.gone()
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
retainedKeys
Determines whether contains Activity
the key
value .
If it is not included, it means that the value of the reference was retainedKeys
removed in the key
previous operation, that is to say, the reference queue
was the reference was GC
processed, no memory leak occurred, return DONE
, and no further execution.
If it is included, it will not immediately determine that a memory leak occurs. There may be an object that is unreachable but has not yet entered the reference queue queue
. At this time, it will take the initiative to perform an GC
operation and then make a judgment again.
gcTrigger.runGc()
/**
* Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued
* in the reference queue yet. This gives the application a hook to run the GC before the {@link
* RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible.
*/
public interface GcTrigger {
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perfom a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
void runGc();
}
Note GC
the , not the use System.gc
. System.gc
Just notifying the system to perform a garbage collection operation at the right time does not actually guarantee that it will be executed.
GC
After taking the initiative, it will be judged again, and the process is the same as above. First removeWeaklyReachableReferences()
call retainedKeys
the key
value clear, and then judge whether to remove it. If it is still not removed, it is determined to be a memory leak.
Memory leak result processing
AndroidHeapDumper.dumpHeap
After determining the memory leak, call heapDumper.dumpHeap()
to handle:
AndroidHeapDumper.java
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@Override public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
leakDirectoryProvider.newHeapDumpFile()
Create a new hprof
file , then call the Debug.dumpHprofData()
method dump
current heap memory and write to the file just created.
Back in the RefWatcher.ensureGone()
method , heapDumpFile
after generating the file, pass the heapdumpListener
analysis .
ServiceHeapDumpListener.analyze
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
Here heapdumpListener
is the ServiceHeapDumpListener
object, then go to the ServiceHeapDumpListener.runAnalysis()
method .
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
Here listenerServiceClass
refers to the configuration DisplayLeakService.class
mentioned AndroidRefWatcherBuilder
at .
@Override protected HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
HeapAnalyzerService.runAnalysis
HeapAnalyzerService.runAnalysis()
The method starts itself, passing two parameters, the DisplayLeakService
class name and the one to analyze heapDump
. After launching itself, process it onHandleIntent
in .
/**
* This service runs in a separate process to avoid slowing down the app process or making it run
* out of memory.
*/
public final class HeapAnalyzerService extends IntentService {
private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
private static final String HEAPDUMP_EXTRA = "heapdump_extra";
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
public HeapAnalyzerService() {
super(HeapAnalyzerService.class.getSimpleName());
}
@Override protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}
heapAnalyzer.checkForLeak
checkForLeak
The method mainly Square
uses another library haha of the company to analyze Android heap dump
, and calls back after getting the result DisplayLeakService
.
AbstractAnalysisResultService.sendResultToListener
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}
onHandleIntent
Also processed in .
DisplayLeakService.onHandleIntent
@Override protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
DisplayLeakService.onHeapAnalyzed
onHeapAnalyzed()
After calling , the hprof
file will be deleted.
DisplayLeakService.java
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
if (!shouldSaveResult) {
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}
According to the analysis results, calling the showNotification()
method constructs a Notification
notification to the developer of the memory leak.
public static void showNotification(Context context, CharSequence contentTitle,
CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification;
Notification.Builder builder = new Notification.Builder(context) //
.setSmallIcon(R.drawable.leak_canary_notification)
.setWhen(System.currentTimeMillis())
.setContentTitle(contentTitle)
.setContentText(contentText)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
if (SDK_INT >= O) {
String channelName = context.getString(R.string.leak_canary_notification_channel);
setupNotificationChannel(channelName, notificationManager, builder);
}
if (SDK_INT < JELLY_BEAN) {
notification = builder.getNotification();
} else {
notification = builder.build();
}
notificationManager.notify(notificationId, notification);
}
DisplayLeakService.afterDefaultHandling
Finally, a method with an empty implementation is also executed afterDefaultHandling
:
/**
* You can override this method and do a blocking call to a server to upload the leak trace and
* the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
* AnalysisResult#excludedLeak} first.
*/
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
}
You can override this method to perform some custom operations, such as uploading leaked stack information to the server.
In this way, LeakCanary
the whole process of memory leak detection is completed. It can be seen that LeakCanary
the design idea of is very ingenious and clear, and there are many interesting knowledge points, such as the use of weak references ReferenceQueue
and , IdleHandler
the use of , the opening and closing of the four major components, etc., which are all worth exploring. .