Debugging without a stacktrace: Using Android Studio’s Performance Monitors
What do you do when your app freezes up, becomes unresponsive, and the logcat just isn’t being helpful? I found myself in this exact situation while debugging a music player I’m currently developing. For context, this is the Now Playing screen of the app where the problem arose:
As you can see, we’ve got all the basics: title, artist, album and some nice images (artist image and album cover) to tie everything together. For most songs this screen loads just fine, but for some reason, playing a few seconds of Dum Dee Dum by Keys N Krates, turned the app into an unresponsive mess. Clicking or swiping on any part of the screen gave no response, and my entire device (Nexus 6P), not just the app, became incredibly sluggish, if not completely unresponsive. Eventually, the Now Playing screen would just terminate, returning to the previous Activity, while the logcat would reset itself as if the app had just launched. Something was very wrong.
Looking for clues
The logcat wasn’t giving me any useful information as to where the problem could be, just that there was a problem. There were several entries in the logcat along the lines of
I/Choreographer: Skipped 265 frames! The application may be doing too much work on its main thread.
as well as
W/art: Suspending all threads took: 170.488ms
but no actual stacktrace. How was I supposed to know what was going on with my code? In desperation, I turned to Android Studio’s performance monitors, which easily allow you to visualize the behavior and performance of your app. More information about Android Studio’s performance monitors can be found here, but there are basically 4 monitors you need to know about: the Memory, CPU, GPU, and Network monitors. I opened up the performance monitors, and reproduced the bug. The CPU, GPU, and Network monitors, were relatively stable and uninteresting. However, the Memory monitor had a story to tell…
Every time I opened the Now Playing screen to play this troublesome song, I saw a massive spike in memory usage, pictured above. It was quite apparent at this point that I had a memory issue. Although I didn’t get an Out of Memory exception, my app was allocating massive amounts of memory, resulting in unresponsiveness and a soft crash. Now I was getting somewhere.
Now that I knew that I was running into memory issues, I was presented with even more questions: Why am I using so much memory? What’s being allocated that’s so massive? What can I do to reduce my memory footprint and have a responsive application? Thankfully, Android Studio comes equipped with a special tool that led me in the right direction: the Allocation Tracker. From the documentation:
Tracking memory allocation allows you to monitor where objects are being allocated when you perform certain actions. Knowing these allocations enables you to adjust the method calls related to those actions to optimize app performance and memory use.
Before I reproduced the bug again, I started allocation tracking by pressing this icon in the memory monitor
and stopped by pressing it again a little after I saw a spike in the monitor. As of this writing, Android Studio doesn’t provide any visual feedback that anything is happening and a bug is filed here. But after several seconds, Android Studio presented me with the following screen, detailing running threads and what percentage of the total memory each thread was using.
As you can see from the heap snapshot that Android Studio took, Thread 31 was using 43.03% of the heap, and is the thread with the largest piece of the pie in the sunburst at the bottom of the screenshot. Looking at the stack trace for Thread 31, there are references to Picasso, an image loading library. Both images in the Now Playing screen are loaded using Picasso, so I knew the culprit had to be either the album art or the artist image. I removed the call for fetching the album art, re-ran my code, and the app still froze. I added back in the album art and removed the call for fetching the artist image and voilà! No more unresponsive app. But this didn’t really solve the problem, because I didn’t have an artist image.
Loading the artist image was causing me to run out of memory, so my first thought was to figure out how big the image was. When loading a bitmap into memory in Android (using the default configuration), each pixel is treated as ARGB and takes up 4 bytes. After some inspection, I found that the artist image I was trying to load was around 19 megapixels. 19,000,000 pixels * 4 bytes/pixel equates to 76 megabytes which is roughly equivalent to the amount of memory that Thread 31 had allocated. Since the artist images were coming from LastFM, I just asked LastFM for a smaller image, and I was back in business! Had it not been possible to get a smaller image from LastFM, I would have sampled the bitmap down to obtain a smaller image. This is a great video explaining how to do that. The moral of the story is to always make sure that the image you’re using is of an appropriate size relative to the size that you’re displaying it at. For a 50dp square image (on my Nexus 6P this equates to 150 pixels square), 19 megapixels was most certainly too large. And if the logcat isn’t being helpful while debugging, perhaps Android Studio’s Performance Monitors can give you some insight into the issue you’re dealing with.
Be sure to to follow me on Twitter @AOrobator for more awesome Android tips. If you found this article helpful, make sure to hit the green heart!