diff --git a/CHANGELOG.md b/CHANGELOG.md index f3835164ac..48a5663200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ You can watch releases [on Bintray](https://bintray.com/pyricau/maven/com.square * Added CanaryLog API to replace the logger: [#201](https://github.com/square/leakcanary/issues/201). * Renamed all resources to begin with `leak_canary_` instead of `__leak_canary`[#161](https://github.com/square/leakcanary/pull/161) * No crash when heap dump fails [#226](https://github.com/square/leakcanary/issues/226). +* Add retained size to leak reports [#162](https://github.com/square/leakcanary/issues/162). ### Public API changes diff --git a/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/AnalysisResult.java b/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/AnalysisResult.java index 8376943af6..4c8270c13a 100644 --- a/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/AnalysisResult.java +++ b/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/AnalysisResult.java @@ -20,16 +20,17 @@ public final class AnalysisResult implements Serializable { public static AnalysisResult noLeak(long analysisDurationMs) { - return new AnalysisResult(false, false, null, null, null, analysisDurationMs); + return new AnalysisResult(false, false, null, null, null, 0, analysisDurationMs); } public static AnalysisResult leakDetected(boolean excludedLeak, String className, - LeakTrace leakTrace, long analysisDurationMs) { - return new AnalysisResult(true, excludedLeak, className, leakTrace, null, analysisDurationMs); + LeakTrace leakTrace, long retainedHeapSize, long analysisDurationMs) { + return new AnalysisResult(true, excludedLeak, className, leakTrace, null, retainedHeapSize, + analysisDurationMs); } public static AnalysisResult failure(Throwable failure, long analysisDurationMs) { - return new AnalysisResult(false, false, null, null, failure, analysisDurationMs); + return new AnalysisResult(false, false, null, null, failure, 0, analysisDurationMs); } /** True if a leak was found in the heap dump. */ @@ -56,16 +57,23 @@ public static AnalysisResult failure(Throwable failure, long analysisDurationMs) /** Null unless the analysis failed. */ public final Throwable failure; + /** + * The number of bytes which would be freed if all references to the leaking object were + * released. 0 if {@link #leakFound} is false. + */ + public final long retainedHeapSize; + /** Total time spent analyzing the heap. */ public final long analysisDurationMs; private AnalysisResult(boolean leakFound, boolean excludedLeak, String className, - LeakTrace leakTrace, Throwable failure, long analysisDurationMs) { + LeakTrace leakTrace, Throwable failure, long retainedHeapSize, long analysisDurationMs) { this.leakFound = leakFound; this.excludedLeak = excludedLeak; this.className = className; this.leakTrace = leakTrace; this.failure = failure; + this.retainedHeapSize = retainedHeapSize; this.analysisDurationMs = analysisDurationMs; } } \ No newline at end of file diff --git a/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/HeapAnalyzer.java b/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/HeapAnalyzer.java index 232975fd15..a972ef206a 100644 --- a/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/HeapAnalyzer.java +++ b/leakcanary-analyzer/src/main/java/com/squareup/leakcanary/HeapAnalyzer.java @@ -22,6 +22,7 @@ import com.squareup.haha.perflib.HprofParser; import com.squareup.haha.perflib.Instance; import com.squareup.haha.perflib.RootObj; +import com.squareup.haha.perflib.RootType; import com.squareup.haha.perflib.Snapshot; import com.squareup.haha.perflib.Type; import com.squareup.haha.perflib.io.HprofBuffer; @@ -119,10 +120,73 @@ private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapsh String className = leakingRef.getClassObj().getClassName(); - return leakDetected(result.excludingKnownLeaks, className, leakTrace, + // Side effect: computes retained size. + snapshot.computeDominators(); + + Instance leakingInstance = result.leakingNode.instance; + + long retainedSize = leakingInstance.getTotalRetainedSize(); + + retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance); + + return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, since(analysisStartNanoTime)); } + /** + * Bitmaps and bitmap byte arrays are sometimes held by native gc roots, so they aren't included + * in the retained size because their root dominator is a native gc root. + * To fix this, we check if the leaking instance is a dominator for each bitmap instance and then + * add the bitmap size. + * + * From experience, we've found that bitmap created in code (Bitmap.createBitmap()) are correctly + * accounted for, however bitmaps set in layouts are not. + */ + private int computeIgnoredBitmapRetainedSize(Snapshot snapshot, Instance leakingInstance) { + int bitmapRetainedSize = 0; + ClassObj bitmapClass = snapshot.findClass("android.graphics.Bitmap"); + + for (Instance bitmapInstance : bitmapClass.getInstancesList()) { + if (isIgnoredDominator(leakingInstance, bitmapInstance)) { + ArrayInstance mBufferInstance = fieldValue(classInstanceValues(bitmapInstance), "mBuffer"); + // Native bitmaps have mBuffer set to null. We sadly can't account for them. + if (mBufferInstance == null) { + continue; + } + long bufferSize = mBufferInstance.getTotalRetainedSize(); + long bitmapSize = bitmapInstance.getTotalRetainedSize(); + // Sometimes the size of the buffer isn't accounted for in the bitmap retained size. Since + // the buffer is large, it's easy to detect by checking for bitmap size < buffer size. + if (bitmapSize < bufferSize) { + bitmapSize += bufferSize; + } + bitmapRetainedSize += bitmapSize; + } + } + return bitmapRetainedSize; + } + + private boolean isIgnoredDominator(Instance dominator, Instance instance) { + boolean foundNativeRoot = false; + while (true) { + Instance immediateDominator = instance.getImmediateDominator(); + if (immediateDominator instanceof RootObj + && ((RootObj) immediateDominator).getRootType() == RootType.UNKNOWN) { + // Ignore native roots + instance = instance.getNextInstanceToGcRoot(); + foundNativeRoot = true; + } else { + instance = immediateDominator; + } + if (instance == null) { + return false; + } + if (instance == dominator) { + return foundNativeRoot; + } + } + } + private LeakTrace buildLeakTrace(LeakNode leakingNode) { List elements = new ArrayList<>(); // We iterate from the leak to the GC root diff --git a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/AsyncTaskLeakTest.java b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/AsyncTaskLeakTest.java index 3545c1eed5..df8a9fd2f5 100644 --- a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/AsyncTaskLeakTest.java +++ b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/AsyncTaskLeakTest.java @@ -15,7 +15,6 @@ */ package com.squareup.leakcanary; -import java.io.File; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; @@ -24,10 +23,12 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import static com.squareup.leakcanary.TestUtil.fileFromName; -import static com.squareup.leakcanary.TestUtil.analyze; import static com.squareup.leakcanary.LeakTraceElement.Holder.THREAD; import static com.squareup.leakcanary.LeakTraceElement.Type.STATIC_FIELD; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK_MPREVIEW2; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK_M_POSTPREVIEW2; +import static com.squareup.leakcanary.TestUtil.analyze; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -44,20 +45,17 @@ public class AsyncTaskLeakTest { @Parameterized.Parameters public static Collection data() { return Arrays.asList(new Object[][] { - { fileFromName("leak_asynctask.hprof"), "dc983a12-d029-4003-8890-7dd644c664c5" }, - { fileFromName("leak_asynctask_mpreview2.hprof"), "1114018e-e154-435f-9a3d-da63ae9b47fa" }, - { fileFromName("leak_asynctask_m_postpreview2.hprof"), "25ae1778-7c1d-4ec7-ac50-5cce55424069" } + { ASYNC_TASK }, // + { ASYNC_TASK_MPREVIEW2 }, // + { ASYNC_TASK_M_POSTPREVIEW2 } // }); } - final File heapDumpFile; - final String referenceKey; - + private final TestUtil.HeapDumpFile heapDumpFile; ExcludedRefs.Builder excludedRefs; - public AsyncTaskLeakTest(File heapDumpFile, String referenceKey) { + public AsyncTaskLeakTest(TestUtil.HeapDumpFile heapDumpFile) { this.heapDumpFile = heapDumpFile; - this.referenceKey = referenceKey; } @Before public void setUp() { @@ -66,7 +64,7 @@ public AsyncTaskLeakTest(File heapDumpFile, String referenceKey) { } @Test public void leakFound() { - AnalysisResult result = analyze(heapDumpFile, referenceKey, excludedRefs); + AnalysisResult result = analyze(heapDumpFile, excludedRefs); assertTrue(result.leakFound); assertFalse(result.excludedLeak); LeakTraceElement gcRoot = result.leakTrace.elements.get(0); @@ -77,7 +75,7 @@ public AsyncTaskLeakTest(File heapDumpFile, String referenceKey) { @Test public void excludeThread() { excludedRefs.thread(ASYNC_TASK_THREAD); - AnalysisResult result = analyze(heapDumpFile, referenceKey, excludedRefs); + AnalysisResult result = analyze(heapDumpFile, excludedRefs); assertTrue(result.leakFound); assertFalse(result.excludedLeak); LeakTraceElement gcRoot = result.leakTrace.elements.get(0); @@ -91,9 +89,8 @@ public AsyncTaskLeakTest(File heapDumpFile, String referenceKey) { excludedRefs.thread(ASYNC_TASK_THREAD); excludedRefs.staticField(ASYNC_TASK_CLASS, EXECUTOR_FIELD_1); excludedRefs.staticField(ASYNC_TASK_CLASS, EXECUTOR_FIELD_2); - AnalysisResult result = analyze(heapDumpFile, referenceKey, excludedRefs); + AnalysisResult result = analyze(heapDumpFile, excludedRefs); assertTrue(result.leakFound); assertTrue(result.excludedLeak); } - } diff --git a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/RetainedSizeTest.java b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/RetainedSizeTest.java new file mode 100644 index 0000000000..d3f2cd4cea --- /dev/null +++ b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/RetainedSizeTest.java @@ -0,0 +1,53 @@ +package com.squareup.leakcanary; + +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.Collection; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK_MPREVIEW2; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.ASYNC_TASK_M_POSTPREVIEW2; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.SERVICE_BINDER; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.SERVICE_BINDER_IGNORED; +import static com.squareup.leakcanary.TestUtil.analyze; +import static org.junit.Assert.assertEquals; + +/** + * This test makes sure there is no regression on the retained size calculation. + */ +@RunWith(Parameterized.class) // +public class RetainedSizeTest { + + @Parameterized.Parameters public static Collection data() { + return Arrays.asList(new Object[][] { + { ASYNC_TASK, 207_407 }, // + { ASYNC_TASK_MPREVIEW2, 1_604 }, // + { ASYNC_TASK_M_POSTPREVIEW2, 1_870 }, // + { SERVICE_BINDER, 378 }, // + { SERVICE_BINDER_IGNORED, 378 }, // + }); + } + + private final TestUtil.HeapDumpFile heapDumpFile; + private final long expectedRetainedHeapSize; + ExcludedRefs.Builder excludedRefs; + + public RetainedSizeTest(TestUtil.HeapDumpFile heapDumpFile, long expectedRetainedHeapSize) { + this.heapDumpFile = heapDumpFile; + this.expectedRetainedHeapSize = expectedRetainedHeapSize; + } + + @Before public void setUp() { + excludedRefs = new ExcludedRefs.Builder().clazz(WeakReference.class.getName(), true) + .clazz("java.lang.ref.FinalizerReference", true); + } + + @Test public void leakFound() { + AnalysisResult result = analyze(heapDumpFile, excludedRefs); + assertEquals(expectedRetainedHeapSize, result.retainedHeapSize); + } +} diff --git a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/ServiceBinderLeakTest.java b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/ServiceBinderLeakTest.java index d270647afd..1851ce755d 100644 --- a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/ServiceBinderLeakTest.java +++ b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/ServiceBinderLeakTest.java @@ -15,15 +15,16 @@ */ package com.squareup.leakcanary; +import java.lang.ref.WeakReference; import org.junit.Before; import org.junit.Test; -import java.lang.ref.WeakReference; - import static com.squareup.leakcanary.LeakTraceElement.Holder.CLASS; import static com.squareup.leakcanary.LeakTraceElement.Holder.OBJECT; import static com.squareup.leakcanary.LeakTraceElement.Type.INSTANCE_FIELD; import static com.squareup.leakcanary.LeakTraceElement.Type.STATIC_FIELD; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.SERVICE_BINDER; +import static com.squareup.leakcanary.TestUtil.HeapDumpFile.SERVICE_BINDER_IGNORED; import static com.squareup.leakcanary.TestUtil.analyze; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -38,19 +39,14 @@ public class ServiceBinderLeakTest { ExcludedRefs.Builder excludedRefs; @Before public void setUp() { - excludedRefs = new ExcludedRefs.Builder() - .clazz(WeakReference.class.getName(), true) + excludedRefs = new ExcludedRefs.Builder().clazz(WeakReference.class.getName(), true) .clazz("java.lang.ref.FinalizerReference", true); } @Test public void realBinderLeak() { excludedRefs.rootSuperClass("android.os.Binder", true); - AnalysisResult result = analyze( - TestUtil.fileFromName("leak_service_binder.hprof"), - "b3abfae6-2c53-42e1-b8c1-96b0558dbeae", - excludedRefs - ); + AnalysisResult result = analyze(SERVICE_BINDER, excludedRefs); assertTrue(result.leakFound); assertFalse(result.excludedLeak); @@ -63,11 +59,7 @@ public class ServiceBinderLeakTest { @Test public void ignorableBinderLeak() { excludedRefs.rootSuperClass("android.os.Binder", false); - AnalysisResult result = analyze( - TestUtil.fileFromName("leak_service_binder_ignored.hprof"), - "6e524414-9581-4ce7-8690-e8ddf8b82454", - excludedRefs - ); + AnalysisResult result = analyze(SERVICE_BINDER_IGNORED, excludedRefs); assertTrue(result.leakFound); assertTrue(result.excludedLeak); @@ -80,13 +72,8 @@ public class ServiceBinderLeakTest { @Test public void alwaysIgnorableBinderLeak() { excludedRefs.rootSuperClass("android.os.Binder", true); - AnalysisResult result = analyze( - TestUtil.fileFromName("leak_service_binder_ignored.hprof"), - "6e524414-9581-4ce7-8690-e8ddf8b82454", - excludedRefs - ); + AnalysisResult result = analyze(SERVICE_BINDER_IGNORED, excludedRefs); assertFalse(result.leakFound); } - } diff --git a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/TestUtil.java b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/TestUtil.java index da3d5cd79a..d58d0aa332 100644 --- a/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/TestUtil.java +++ b/leakcanary-analyzer/src/test/java/com/squareup/leakcanary/TestUtil.java @@ -19,15 +19,38 @@ import java.net.URL; final class TestUtil { + + enum HeapDumpFile { + ASYNC_TASK("leak_asynctask.hprof", "dc983a12-d029-4003-8890-7dd644c664c5"), + ASYNC_TASK_MPREVIEW2("leak_asynctask_mpreview2.hprof", "1114018e-e154-435f-9a3d-da63ae9b47fa"), + ASYNC_TASK_M_POSTPREVIEW2("leak_asynctask_m_postpreview2.hprof", + "25ae1778-7c1d-4ec7-ac50-5cce55424069"), + + SERVICE_BINDER("leak_service_binder.hprof", "b3abfae6-2c53-42e1-b8c1-96b0558dbeae"), + SERVICE_BINDER_IGNORED("leak_service_binder_ignored.hprof", + "6e524414-9581-4ce7-8690-e8ddf8b82454"),; + + private final String filename; + private final String referenceKey; + + HeapDumpFile(String filename, String referenceKey) { + this.filename = filename; + this.referenceKey = referenceKey; + } + + } + static File fileFromName(String filename) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); URL url = classLoader.getResource(filename); return new File(url.getPath()); } - static AnalysisResult analyze(File heapDumpFile, String referenceKey, ExcludedRefs.Builder excludedRefs) { + static AnalysisResult analyze(HeapDumpFile heapDumpFile, ExcludedRefs.Builder excludedRefs) { + File file = fileFromName(heapDumpFile.filename); + String referenceKey = heapDumpFile.referenceKey; HeapAnalyzer heapAnalyzer = new HeapAnalyzer(excludedRefs.build()); - AnalysisResult result = heapAnalyzer.checkForLeak(heapDumpFile, referenceKey); + AnalysisResult result = heapAnalyzer.checkForLeak(file, referenceKey); if (result.failure != null) { result.failure.printStackTrace(); } diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/DisplayLeakService.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/DisplayLeakService.java index fc14a294a1..98699ca969 100644 --- a/leakcanary-android/src/main/java/com/squareup/leakcanary/DisplayLeakService.java +++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/DisplayLeakService.java @@ -36,6 +36,7 @@ import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.JELLY_BEAN; +import static android.text.format.Formatter.formatShortFileSize; import static com.squareup.leakcanary.LeakCanary.leakInfo; import static com.squareup.leakcanary.internal.LeakCanaryInternals.classSimpleName; @@ -73,10 +74,12 @@ public class DisplayLeakService extends AbstractAnalysisResultService { if (result.failure == null) { if (result.excludedLeak) { contentTitle = - getString(R.string.leak_canary_leak_excluded, classSimpleName(result.className)); + getString(R.string.leak_canary_leak_excluded, classSimpleName(result.className), + formatShortFileSize(this, result.retainedHeapSize)); } else { contentTitle = - getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className)); + getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className), + formatShortFileSize(this, result.retainedHeapSize)); } } else { contentTitle = getString(R.string.leak_canary_analysis_failed); diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakCanary.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakCanary.java index b2d39a9fb3..f573f1ddaf 100644 --- a/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakCanary.java +++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/LeakCanary.java @@ -25,6 +25,7 @@ import com.squareup.leakcanary.internal.DisplayLeakActivity; import com.squareup.leakcanary.internal.HeapAnalyzerService; +import static android.text.format.Formatter.formatShortFileSize; import static com.squareup.leakcanary.internal.LeakCanaryInternals.isInServiceProcess; import static com.squareup.leakcanary.internal.LeakCanaryInternals.setEnabled; @@ -101,6 +102,7 @@ public static String leakInfo(Context context, HeapDump heapDump, AnalysisResult info += " (" + heapDump.referenceName + ")"; } info += " has leaked:\n" + result.leakTrace.toString() + "\n"; + info += "* Retaining: " + formatShortFileSize(context, result.retainedHeapSize) + ".\n"; if (detailed) { detailedString = "\n* Details:\n" + result.leakTrace.toDetailedString(); } diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java index 5f3fa421d9..4f89b11d51 100644 --- a/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java +++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/internal/DisplayLeakActivity.java @@ -59,6 +59,7 @@ import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.text.format.DateUtils.FORMAT_SHOW_DATE; import static android.text.format.DateUtils.FORMAT_SHOW_TIME; +import static android.text.format.Formatter.formatShortFileSize; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static com.squareup.leakcanary.LeakCanary.leakInfo; @@ -287,8 +288,8 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } HeapDump heapDump = visibleLeak.heapDump; adapter.update(result.leakTrace, heapDump.referenceKey, heapDump.referenceName); - setTitle( - getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className))); + setTitle(getString(R.string.leak_canary_class_has_leaked, classSimpleName(result.className), + formatShortFileSize(this, result.retainedHeapSize))); } } else { if (listAdapter instanceof LeakListAdapter) { @@ -367,8 +368,9 @@ class LeakListAdapter extends BaseAdapter { String title; if (leak.result.failure == null) { - title = getString(R.string.leak_canary_class_has_leaked, - classSimpleName(leak.result.className)); + title = + getString(R.string.leak_canary_class_has_leaked, classSimpleName(leak.result.className), + formatShortFileSize(DisplayLeakActivity.this, leak.result.retainedHeapSize)); if (leak.result.excludedLeak) { title = getString(R.string.leak_canary_excluded_row, title); } @@ -380,8 +382,9 @@ class LeakListAdapter extends BaseAdapter { + leak.result.failure.getMessage(); } titleView.setText(title); - String time = DateUtils.formatDateTime(DisplayLeakActivity.this, leak.resultFile.lastModified(), - FORMAT_SHOW_TIME | FORMAT_SHOW_DATE); + String time = + DateUtils.formatDateTime(DisplayLeakActivity.this, leak.resultFile.lastModified(), + FORMAT_SHOW_TIME | FORMAT_SHOW_DATE); timeView.setText(time); return convertView; } diff --git a/leakcanary-android/src/main/res/values/leak_canary_strings.xml b/leakcanary-android/src/main/res/values/leak_canary_strings.xml index f395dd63e4..ffa221eeb6 100644 --- a/leakcanary-android/src/main/res/values/leak_canary_strings.xml +++ b/leakcanary-android/src/main/res/values/leak_canary_strings.xml @@ -15,9 +15,8 @@ ~ limitations under the License. --> - - %s has leaked - %s has leaked (excluded leak) + %1$s has leaked %2$s + %1$s has leaked %2$s (excluded leak) Leak analysis failed Leaks in %s Click for more details diff --git a/leakcanary-sample/src/main/java/com/example/leakcanary/MainActivity.java b/leakcanary-sample/src/main/java/com/example/leakcanary/MainActivity.java index 870f59b124..b7c17a792b 100644 --- a/leakcanary-sample/src/main/java/com/example/leakcanary/MainActivity.java +++ b/leakcanary-sample/src/main/java/com/example/leakcanary/MainActivity.java @@ -23,11 +23,19 @@ public class MainActivity extends Activity { + //static int[] data = new int[1024 * 1024]; + static int[] data = new int[1024 * 1024]; + + Object[][] objects = new Object[1024 * 1024][]; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); View button = findViewById(R.id.async_task); + + objects[0] = new Object[1024 * 1024]; + button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask();