You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/changelog.md
+7Lines changed: 7 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,13 @@ Releases before 2.8.1 predate these markers.
19
19
20
20
## Unreleased
21
21
22
+
* 🔀 [#2790](https://github.com/square/leakcanary/issues/2790) [#2719](https://github.com/square/leakcanary/issues/2719) [#2174](https://github.com/square/leakcanary/issues/2174) Heap dumps are now always stored in a `leakcanary` directory inside the app's [no backup directory](https://developer.android.com/reference/android/content/Context#getNoBackupFilesDir()), i.e. `/data/data/com.example/no_backup/leakcanary/`. They used to go to a `leakcanary-com.example` directory in the public `Download` folder, falling back to the app's cache directory when that wasn't writable, which in practice meant three different behaviors: on API 26 to 28 the destination depended on whether the app happened to hold `WRITE_EXTERNAL_STORAGE`, which LeakCanary declared but never requested; on API 29 scoped storage made the public directory unwritable and everything fell back to the cache directory; and from API 30 on, writing there needs no permission, so every heap dump went to `Download` and the fallback became unreachable. Storing heap dumps in public storage is what caused the failures above: a heap dump in `Download` belongs to the app in `MediaStore`, and clearing the app's data or reinstalling it clears that ownership while leaving the file on disk, after which the app can't read the file (the analysis fails with *"Hprof file missing"*), can't list it (so the `maxStoredHeapDumps` cleanup stops seeing it — one reporter accumulated 400+ heap dumps, about 40 GB) and can't delete it. None of that applies to app storage, which behaves the same way on every Android version.
23
+
* ⚠️ LeakCanary no longer declares the `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` permissions, so they disappear from the merged manifest of debug builds.
24
+
* ⚠️ `LeakCanary.Config.requestWriteExternalStoragePermission` and `LeakCanary.Config.Builder.requestWriteExternalStoragePermission()` are deprecated and do nothing. There's no external storage permission to request anymore.
25
+
* 🔀 Heap dumps taken by `leakcanary-android-test` and `leakcanary-android-instrumentation` move from the files directory of the app under test to its no backup directory, so they aren't backed up either. Heap dumps taken by `leakcanary-android-uiautomator` stay in `/data/local/tmp`: they're written by `am dumpheap` running as the shell user, which can't write to an app's private directory.
26
+
* 🔀 `adb pull /sdcard/Download/leakcanary-com.example/…` doesn't work anymore. Share the heap dump from the LeakCanary UI, or run `adb exec-out run-as com.example cat no_backup/leakcanary/<name>.hprof > dump.hprof`.
27
+
* 🔀 Heap dumps written by an earlier version of LeakCanary are left where they are. LeakCanary only cleans up the directory it writes to, and on API 30 and above it couldn't delete the ones it no longer owns anyway. Delete the `Download/leakcanary-com.example` directory by hand to reclaim that space.
28
+
* ⚠️ The *"Render Heap Dump"* screen in the LeakCanary UI is gone, along with its *"Generate HQ Bitmap"* action. The action rendered the heap dump to a PNG in the public `Download` folder, gated on the `WRITE_EXTERNAL_STORAGE` permission — which apps targeting Android 11 and above can't be granted, so since 2020 tapping it only ever showed a toast asking for a permission that would have bought nothing.
22
29
* 💥 [#2789](https://github.com/square/leakcanary/issues/2789) [#2773](https://github.com/square/leakcanary/issues/2773) The heap analysis of a large heap failed with an `OutOfMemoryError` while growing the set of objects the path finding traversal has already visited. That set was keyed by object id, so it held every reachable object in an 8 byte per slot hash table at a 0.75 load factor, sized from a guess (`instanceCount / 2`) that is always too small: on the Android heap dumps in our test resources the traversal ends up visiting 0.65x to 1.08x of `instanceCount`. Growing doubles the table and rehashes into it while the old one is still referenced, so the moment of growth needs 1.5x the new table — on a heap of 4 million objects, a 33.6 MB table and a 67.1 MB one live at the same time, in an app capped at a 512 MB growth limit. The visited set is now one bit per object in the heap dump, keyed by the object's index rather than by its id, allocated once at a size that's known upfront, so it can't grow and can't rehash: 509 KB rather than a 100.7 MB peak on that 4 million object heap. On a 4.4 million object Android heap dump, the smallest heap the analysis completes in goes from 513 MB to 385 MB.
23
30
* 🔀 Mapping an object id to its index is a binary search where a hash lookup used to do, and the traversal does that once per reference it reads, so the analysis is about 2% slower end to end (4% to 8% of the path finding step) on the Android heap dumps in our test resources.
24
31
* 💥 [#2773](https://github.com/square/leakcanary/issues/2773) The path finding traversal also kept a set of the object ids waiting in its queue, keyed by object id and therefore growing to the size of the traversal frontier — on a heap dump whose objects are reachable through a wide array, most of the heap. It turned out to be write-only: an object is added to exactly one of the two queues and removed from it when polled, so an object waiting in the low priority queue is never in the high priority queue as well, which is all the one read of that set was checking. Removing it takes the smallest heap the analysis of a 4.4 million object Android heap dump completes in from 385 MB to 321 MB, and makes path finding 2.6% to 4.3% faster, since maintaining the set cost a hash insert per reference enqueued and a hash removal per object visited.
The default behavior is to store heap dumps in a `leakcanary` folder under the app directory. If the app has been granted the `android.permission.WRITE_EXTERNAL_STORAGE` permission, then heap dumps will be stored
41
-
in a `leakcanary-com.example` folder (where `com.example` is your app package name) under the `Download` folder of the external storage. If the app has not been granted the `android.permission.WRITE_EXTERNAL_STORAGE` permission but that permission is listed in `AndroidManifest.xml` then LeakCanary will show a notification that can be tapped to grant permission.
40
+
In a `leakcanary` folder inside the app's [no backup directory](https://developer.android.com/reference/android/content/Context#getNoBackupFilesDir()),
41
+
i.e. `/data/data/com.example/no_backup/leakcanary/` where `com.example` is your app package name.
42
+
That directory needs no permission on any Android version, and heap dumps stored there are excluded
43
+
from Android Auto Backup, so they never count against the user's backup quota.
44
+
45
+
LeakCanary keeps the [LeakCanary.Config.maxStoredHeapDumps](/leakcanary/api/leakcanary-android-core/leakcanary-android-core/leakcanary/-leak-canary/-config/max-stored-heap-dumps/)
46
+
most recent heap dumps, 7 by default, and deletes the older ones.
47
+
48
+
To pull a heap dump off the device, either share it from the LeakCanary UI (go to a heap analysis
49
+
screen, click the overflow menu and select *Share Heap Dump*), or read it through the app's own
50
+
user with `run-as`, which works because LeakCanary is a `debugImplementation` dependency and the app
0 commit comments