Skip to content

Commit 2ae03d8

Browse files
authored
Merge pull request #629 from square/py/custom
RefWatcher configuration using builder
2 parents eccba7a + 111609e commit 2ae03d8

File tree

13 files changed

+271
-86
lines changed

13 files changed

+271
-86
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.squareup.leakcanary;
2+
3+
import android.app.Application;
4+
import android.content.Context;
5+
import java.util.concurrent.TimeUnit;
6+
7+
import static com.squareup.leakcanary.RefWatcher.DISABLED;
8+
import static java.util.concurrent.TimeUnit.SECONDS;
9+
10+
/** A {@link RefWatcherBuilder} with appropriate Android defaults. */
11+
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
12+
13+
private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);
14+
15+
private final Context context;
16+
17+
AndroidRefWatcherBuilder(Context context) {
18+
this.context = context.getApplicationContext();
19+
}
20+
21+
/**
22+
* Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
23+
* overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
24+
*/
25+
public AndroidRefWatcherBuilder listenerServiceClass(
26+
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
27+
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
28+
}
29+
30+
/**
31+
* Sets a custom delay for how long the {@link RefWatcher} should wait until it checks if a
32+
* tracked object has been garbage collected. This overrides any call to {@link
33+
* #watchExecutor(WatchExecutor)}.
34+
*/
35+
public AndroidRefWatcherBuilder watchDelay(long delay, TimeUnit unit) {
36+
return watchExecutor(new AndroidWatchExecutor(unit.toMillis(delay)));
37+
}
38+
39+
/**
40+
* Sets the maximum number of heap dumps stored. This overrides any call to {@link
41+
* #heapDumper(HeapDumper)} as well as any call to
42+
* {@link LeakCanary#setDisplayLeakActivityDirectoryProvider(LeakDirectoryProvider)})}
43+
*
44+
* @throws IllegalArgumentException if maxStoredHeapDumps < 1.
45+
*/
46+
public AndroidRefWatcherBuilder maxStoredHeapDumps(int maxStoredHeapDumps) {
47+
LeakDirectoryProvider leakDirectoryProvider =
48+
new DefaultLeakDirectoryProvider(context, maxStoredHeapDumps);
49+
LeakCanary.setDisplayLeakActivityDirectoryProvider(leakDirectoryProvider);
50+
return heapDumper(new AndroidHeapDumper(context, leakDirectoryProvider));
51+
}
52+
53+
/**
54+
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
55+
*/
56+
public RefWatcher buildAndInstall() {
57+
RefWatcher refWatcher = build();
58+
if (refWatcher != DISABLED) {
59+
LeakCanary.enableDisplayLeakActivity(context);
60+
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
61+
}
62+
return refWatcher;
63+
}
64+
65+
@Override protected boolean isDisabled() {
66+
return LeakCanary.isInAnalyzerProcess(context);
67+
}
68+
69+
@Override protected HeapDumper defaultHeapDumper() {
70+
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
71+
return new AndroidHeapDumper(context, leakDirectoryProvider);
72+
}
73+
74+
@Override protected DebuggerControl defaultDebuggerControl() {
75+
return new AndroidDebuggerControl();
76+
}
77+
78+
@Override protected HeapDump.Listener defaultHeapDumpListener() {
79+
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
80+
}
81+
82+
@Override protected ExcludedRefs defaultExcludedRefs() {
83+
return AndroidExcludedRefs.createAppDefaults().build();
84+
}
85+
86+
@Override protected WatchExecutor defaultWatchExecutor() {
87+
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
88+
}
89+
}

leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidWatchExecutor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public final class AndroidWatchExecutor implements WatchExecutor {
3535
private final long initialDelayMillis;
3636
private final long maxBackoffFactor;
3737

38-
public AndroidWatchExecutor(int initialDelayMillis) {
38+
public AndroidWatchExecutor(long initialDelayMillis) {
3939
mainHandler = new Handler(Looper.getMainLooper());
4040
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
4141
handlerThread.start();

leakcanary-android/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import android.annotation.TargetApi;
1919
import android.app.PendingIntent;
2020
import android.content.Context;
21-
import android.content.res.Resources;
2221
import android.os.Environment;
2322
import com.squareup.leakcanary.internal.RequestStoragePermissionActivity;
2423
import java.io.File;
@@ -40,19 +39,30 @@
4039

4140
public final class DefaultLeakDirectoryProvider implements LeakDirectoryProvider {
4241

42+
private static final int DEFAULT_MAX_STORED_HEAP_DUMPS = 7;
43+
4344
private static final String HPROF_SUFFIX = ".hprof";
4445
private static final String PENDING_HEAPDUMP_SUFFIX = "_pending" + HPROF_SUFFIX;
4546

4647
/** 10 minutes */
4748
private static final int ANALYSIS_MAX_DURATION_MS = 10 * 60 * 1000;
4849

4950
private final Context context;
51+
private final int maxStoredHeapDumps;
5052

51-
private boolean writeExternalStorageGranted;
52-
private boolean permissionNotificationDisplayed;
53+
private volatile boolean writeExternalStorageGranted;
54+
private volatile boolean permissionNotificationDisplayed;
5355

5456
public DefaultLeakDirectoryProvider(Context context) {
57+
this(context, DEFAULT_MAX_STORED_HEAP_DUMPS);
58+
}
59+
60+
public DefaultLeakDirectoryProvider(Context context, int maxStoredHeapDumps) {
61+
if (maxStoredHeapDumps < 1) {
62+
throw new IllegalArgumentException("maxStoredHeapDumps must be at least 1");
63+
}
5564
this.context = context.getApplicationContext();
65+
this.maxStoredHeapDumps = maxStoredHeapDumps;
5666
}
5767

5868
@Override public List<File> listFiles(FilenameFilter filter) {
@@ -176,9 +186,6 @@ private boolean directoryWritableAfterMkdirs(File directory) {
176186
}
177187

178188
private void cleanupOldHeapDumps() {
179-
Resources resources = context.getResources();
180-
int configStoredHeapDumps = resources.getInteger(R.integer.leak_canary_max_stored_leaks);
181-
int maxStoredHeapDumps = Math.max(configStoredHeapDumps, 1);
182189
List<File> hprofFiles = listFiles(new FilenameFilter() {
183190
@Override public boolean accept(File dir, String filename) {
184191
return filename.endsWith(HPROF_SUFFIX);

leakcanary-android/src/main/java/com/squareup/leakcanary/LeakCanary.java

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import android.content.Context;
2020
import android.content.pm.PackageInfo;
2121
import android.content.pm.PackageManager;
22-
import android.content.res.Resources;
2322
import android.os.Build;
2423
import android.util.Log;
2524
import com.squareup.leakcanary.internal.DisplayLeakActivity;
@@ -38,47 +37,25 @@ public final class LeakCanary {
3837
* references (on ICS+).
3938
*/
4039
public static RefWatcher install(Application application) {
41-
return install(application, DisplayLeakService.class,
42-
AndroidExcludedRefs.createAppDefaults().build());
40+
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
41+
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
42+
.buildAndInstall();
4343
}
4444

45-
/**
46-
* Creates a {@link RefWatcher} that reports results to the provided service, and starts watching
47-
* activity references (on ICS+).
48-
*/
49-
public static RefWatcher install(Application application,
50-
Class<? extends AbstractAnalysisResultService> listenerServiceClass,
51-
ExcludedRefs excludedRefs) {
52-
if (isInAnalyzerProcess(application)) {
53-
return RefWatcher.DISABLED;
54-
}
55-
enableDisplayLeakActivity(application);
56-
HeapDump.Listener heapDumpListener =
57-
new ServiceHeapDumpListener(application, listenerServiceClass);
58-
RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
59-
ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
60-
return refWatcher;
61-
}
62-
63-
/**
64-
* Creates a {@link RefWatcher} with a default configuration suitable for Android.
65-
*/
66-
public static RefWatcher androidWatcher(Context context, HeapDump.Listener heapDumpListener,
67-
ExcludedRefs excludedRefs) {
68-
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
69-
DebuggerControl debuggerControl = new AndroidDebuggerControl();
70-
AndroidHeapDumper heapDumper = new AndroidHeapDumper(context, leakDirectoryProvider);
71-
Resources resources = context.getResources();
72-
int watchDelayMillis = resources.getInteger(R.integer.leak_canary_watch_delay_millis);
73-
AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis);
74-
return new RefWatcher(executor, debuggerControl, GcTrigger.DEFAULT, heapDumper,
75-
heapDumpListener, excludedRefs);
45+
/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
46+
public static AndroidRefWatcherBuilder refWatcher(Context context) {
47+
return new AndroidRefWatcherBuilder(context);
7648
}
7749

7850
public static void enableDisplayLeakActivity(Context context) {
7951
setEnabled(context, DisplayLeakActivity.class, true);
8052
}
8153

54+
/**
55+
* If you build a {@link RefWatcher} with a {@link AndroidHeapDumper} that has a custom {@link
56+
* LeakDirectoryProvider}, then you should also call this method to make sure the activity in
57+
* charge of displaying leaks can find those on the file system.
58+
*/
8259
public static void setDisplayLeakActivityDirectoryProvider(
8360
LeakDirectoryProvider leakDirectoryProvider) {
8461
DisplayLeakActivity.setLeakDirectoryProvider(leakDirectoryProvider);

leakcanary-android/src/main/res/values/leak_canary_int.xml

Lines changed: 0 additions & 20 deletions
This file was deleted.

leakcanary-android/src/main/res/values/leak_canary_public.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,5 @@
1919
<public name="leak_canary_display_activity_label" type="string"/>
2020
<public name="leak_canary_heap_dump_toast" type="layout"/>
2121
<public name="leak_canary_icon" type="drawable"/>
22-
<public name="leak_canary_max_stored_leaks" type="integer"/>
23-
<public name="leak_canary_watch_delay_millis" type="integer"/>
2422

2523
</resources>

leakcanary-watcher/src/main/java/com/squareup/leakcanary/HeapDump.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@
2020

2121
import static com.squareup.leakcanary.Preconditions.checkNotNull;
2222

23+
/** Data structure holding information about a heap dump. */
2324
public final class HeapDump implements Serializable {
2425

26+
/** Receives a heap dump to analyze. */
2527
public interface Listener {
28+
Listener NONE = new Listener() {
29+
@Override public void analyze(HeapDump heapDump) {
30+
}
31+
};
32+
2633
void analyze(HeapDump heapDump);
2734
}
2835

leakcanary-watcher/src/main/java/com/squareup/leakcanary/HeapDumper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717

1818
import java.io.File;
1919

20+
/** Dumps the heap into a file. */
2021
public interface HeapDumper {
22+
HeapDumper NONE = new HeapDumper() {
23+
@Override public File dumpHeap() {
24+
return RETRY_LATER;
25+
}
26+
};
2127

2228
File RETRY_LATER = null;
2329

leakcanary-watcher/src/main/java/com/squareup/leakcanary/RefWatcher.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,7 @@
3535
*/
3636
public final class RefWatcher {
3737

38-
public static final RefWatcher DISABLED = new RefWatcher(new WatchExecutor() {
39-
@Override public void execute(Retryable retryable) {
40-
}
41-
}, new DebuggerControl() {
42-
@Override public boolean isDebuggerAttached() {
43-
// Skips watching.
44-
return true;
45-
}
46-
}, GcTrigger.DEFAULT, new HeapDumper() {
47-
@Override public File dumpHeap() {
48-
return RETRY_LATER;
49-
}
50-
}, new HeapDump.Listener() {
51-
@Override public void analyze(HeapDump heapDump) {
52-
}
53-
}, new ExcludedRefs.BuilderWithParams().build());
38+
public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();
5439

5540
private final WatchExecutor watchExecutor;
5641
private final DebuggerControl debuggerControl;
@@ -61,9 +46,8 @@ public final class RefWatcher {
6146
private final HeapDump.Listener heapdumpListener;
6247
private final ExcludedRefs excludedRefs;
6348

64-
public RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl,
65-
GcTrigger gcTrigger, HeapDumper heapDumper, HeapDump.Listener heapdumpListener,
66-
ExcludedRefs excludedRefs) {
49+
RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
50+
HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) {
6751
this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
6852
this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
6953
this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
@@ -91,6 +75,9 @@ public void watch(Object watchedReference) {
9175
* @param referenceName An logical identifier for the watched object.
9276
*/
9377
public void watch(Object watchedReference, String referenceName) {
78+
if (this == DISABLED) {
79+
return;
80+
}
9481
checkNotNull(watchedReference, "watchedReference");
9582
checkNotNull(referenceName, "referenceName");
9683
final long watchStartNanoTime = System.nanoTime();

0 commit comments

Comments
 (0)