leakcanary: LeakCanary can't dump heap on Android R devices when targetSdkVersion <= 28

Description

When running an app which targets SDK 28 (or probably anything lower), LeakCanary fails to dump the heap when executing on Android R device (currently tested against most recently available DP2 version)

Steps to Reproduce

  1. install app from https://github.com/CDRussell/LeakCanaryHeapDumpProblem
  2. run app on Android R device, and rotate the screen a few times to deliberately trigger a leak detection
  3. note in logcat that LeakCanary fails due to write permissions issues

Expected behavior: [What you expect to happen] LeakCanary shouldn’t have permissions issues.

Version Information

  • LeakCanary version: 2.2
  • Android OS version: Android R (dp2)
  • Gradle version: gradle-6.1.1 / com.android.tools.build:gradle:4.0.0-beta04

Additional Information

  • Note, only happens if targetSdk is 28 or lower. targetSdk 29 works fine
  • If i manually grant the WRITE_EXTERNAL_STORAGE permission at runtime, it works again

Stack Trace

2020-04-22 21:16:27.722 6894-6936/com.duckduckgo.myapplication D/LeakCanary: Could not dump heap
    java.lang.RuntimeException: Couldn't dump heap; open("/storage/emulated/0/Download/leakcanary-com.duckduckgo.myapplication/2020-04-22_21-16-26_695.hprof") failed: Operation not permitted
        at dalvik.system.VMDebug.dumpHprofData(Native Method)
        at dalvik.system.VMDebug.dumpHprofData(VMDebug.java:384)
        at dalvik.system.VMDebug.dumpHprofData(VMDebug.java:361)
        at android.os.Debug.dumpHprofData(Debug.java:2016)
        at leakcanary.internal.AndroidHeapDumper.dumpHeap(AndroidHeapDumper.kt:68)
        at leakcanary.internal.HeapDumpTrigger.dumpHeap(HeapDumpTrigger.kt:156)
        at leakcanary.internal.HeapDumpTrigger.checkRetainedObjects(HeapDumpTrigger.kt:146)
        at leakcanary.internal.HeapDumpTrigger.access$checkRetainedObjects(HeapDumpTrigger.kt:28)
        at leakcanary.internal.HeapDumpTrigger$scheduleRetainedObjectCheck$3.run(HeapDumpTrigger.kt:293)
        at android.os.Handler.handleCallback(Handler.java:907)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:216)
        at android.os.HandlerThread.run(HandlerThread.java:67)
2020-04-22 21:16:27.723 6894-6936/com.duckduckgo.myapplication D/LeakCanary: Failed to dump heap, will retry in 5000 ms```

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 26 (12 by maintainers)

Most upvoted comments

I’m going to close this issue as it sounds like this has been fixed in the latest android candidate release? Let me know if not.

Apologies for being non-responsive recently; been swamped. Closing sounds good to me and if I notice the same issue again on the latest Android 11 beta, I’ll comment again about it.

I’m going to close this issue as it sounds like this has been fixed in the latest android candidate release? Let me know if not.

Actually, just one thing: https://developer.android.com/reference/java/io/File#canWrite()

true if and only if the file system actually contains a file denoted by this abstract pathname and the application is allowed to write to the file; false otherwise.

So, given that, it would make sense that File.canWrite() returns false given that the file wasn’t created yet… Maybe that’s not the issue at hand here?

Screen Shot 2020-05-06 at 9 06 54 PM

The problem is somewhere in amongst the files created by that method. ☝️. According to those docs, there shouldn’t be a problem targeting 28, but 🤷‍♂️.

private fun externalStorageDirectory(): File {
    val downloadsDirectory = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)
    return File(downloadsDirectory, "leakcanary-" + context.packageName)
  }

downloadsDirectory.canWrite() == true. when you create that new sub-directory, the result of that is that canWrite() == true. but when you create the File for the hprof, it says that canWrite == false. in other words, it says

  • /storage/emulated/0/Download is writable
  • /storage/emulated/0/Download/leakcanary-com.duckduckgo.myapplication is writable
  • /storage/emulated/0/Download/leakcanary-com.duckduckgo.myapplication/2020-05-06_21-24-29_136.hprof is not writable

It never tries to dump when I have the debugger attached.

Try LeakCanary.config.dumpHeapWhenDebugging = true

@consp1racy The main advantage is that if you uninstall / reinstall the hprof file is still there. In LeakCanary 1 the heap analysis was stored as a file so it survived reinstalled, but in 2 it’s stored in SQLite so it disappears anyway… Also it doesn’t count towards the app size, though not sure how exactly that matters. Also sometimes the app partition is reaching its max size. Maybe those are problems of the past? We could change LeakCanary to never store on the sdcard, maybe.

Screenshot_20200504-231005

Looks like they’ve updated the docs! Here’s a screen grab from the old docs page. You’re right that it’s probably a DP2-specific issue. I’ll test it out with DP3 when I can and report back.

I just created an emulator running R and I can’t reproduce the issue, both with the provided sample as well as with LeakCanary’s sample

Logcat shows the expected log:

WRITE_EXTERNAL_STORAGE permission not granted, ignoring

This means the permission is missing and LeakCanary won’t try to use the sdcard. In the example you provided, it looked like that check passed and LeakCanary had decided it could use the sdcard, and then that didn’t work.

Now, another weird thing: @CDRussell you pasted a link to https://developer.android.com/preview/behavior-changes-11 but I couldn’t find any reference to scoped storage there. A quick googling brought up this page: https://developer.android.com/preview/privacy/storage

Maybe a change of behavior between dp2 and dp3? Can you still make this crash happen?

We should always use MediaStore on R+, it has new constants for Downloads directory.

https://commonsware.com/blog/2019/12/21/scoped-storage-stories-storing-mediastore.html