Memory is one of the most important assets your Android application has. The Android platform tries to keep as many applications in memory as possible, even when they’re not in use. This isn’t simply a cache of the files that make up your application, but the running application process and its threads. This behaviour makes switching between applications and tasks quick and keeps a device feeling “snappy” to the end users. On lower-end devices however things can start to get messy if a running application is taking up more than its fair-share of memory. The result of this situation is an increase in what is called “memory pressure”.
You can think of memory pressure as the primary application, the one the user is interacting with, squeezing the other applications out of memory. This is a process and doesn’t happen all at once, or all of the time. Instead the Android platform (specifically the Low Memory Killer or LMK) will take incremental steps in an attempt to free-up more RAM for a memory-hungry foreground application.
To a degree not seen on other platforms Android is largely composed as an ecosystem of interdependent applications, communicating with each other using shared memory or Inter Process Communication (IPC). Many of the service classes you retrieve with Context.getSystemService are in fact lightweight facades that communicate with the system service processes using IPC. This makes for an amazingly interconnected system, where apps can safely share data and resources smoothly and where the whole experience becomes “more than the sum of its parts” for the end-user. It can also lead to unforeseen, and difficult to debug stability issues.
So how does memory pressure affect your application stability? The most common and obvious answer is Out Of Memory errors, but on Android there are several other possible cases. If your application is in the foreground then it is other applications that are being terminated, and if your application is in the background the user won’t notice if it’s killed, right? Wrong.
Your application can be terminated because other applications are consuming too much memory, and if your application is doing any work at the time (backup, server-sync, cache pruning, etc.) you may end up with a crash report that doesn’t appear to be memory related.
Another memory situation that can cause problems is when your application is killed as a user navigates from one application to another. For example when sharing a photo with another application, or navigating to a help page hosted in Chrome. If there isn’t enough memory to keep both applications in memory at the same time, Android will terminate the application in the background and restore it when the user navigates back. This doesn’t seem like a problem at first, but your application may have to cold-start leading to significant delays and potentially Application Not Responding (ANR) reports, as the system thrashes to both restart your application and unload the previous one. This can be made even worse in use cases such as content sharing. For example sending a photo from your amazing photo editing app to a friend over a messaging app.
Bugsnag reports on Android recently received a refresh. As of version 5.12.0 we’ve added some new parameters that we automatically report to your dashboard.
Bugsnag divides memory reporting into two existing metadata categories: App, and Device. The App memory properties are specific to your application, while the Device properties are there to give you an idea what else is happening on the device at the same point in time. We divide these fields into two main categories (as with many other fields we capture): App (specific to your app), and Device (specific to the device your app was running on when the report was generated).
Under the App tab on the Bugsnag Dashboard we report:
totalMemory - freeMemory. This is an estimate of how much memory your app was using when the report was generated.
memoryTrimLevel, and is provided mostly for backwards compatibility.
Under the Device tab we report:
One significant change is that we now track the Memory Trim Level in the App tab for both JVM and NDK reports. This allows you to see what sort of memory pressure was last reported to your app:
This attribute is the most recent value reported to your application using onTrimMemory, providing valuable insight into why that Bitmap allocation failed, or why your application was terminated. We’ve also started automatically collecting breadcrumbs when these events are delivered, so you’ll have a view of the memory pressure leading up to a crash report.
The Device tab also includes a clear idea of the memory situation for the physical device, including the amount of installed RAM (totalMemory) and how much of that memory is considered to be free and available (freeMemory). Knowing how much RAM is available on a device can lead to valuable insights when a user has managed to crash a memory-heavy feature in your application.
While memory is typically not a problem on flagship and mid-tier devices, many commodity devices in the Android ecosystem can cause strange and unexpected stability issues under high memory pressure situations. Knowing what features of your app were being accessed; the memory pressure leading up to a crash; and the overall memory health of a device can help you find the real source of these errors. Knowing when an error is caused by memory pressure from your own app — or another app — makes it easier to decide which are worth fixing, and which can be safely left alone.
If you’re currently using Bugsnag to manage the stability of your Android application, upgrade to our latest notifier version to start capturing end-to-end memory diagnostics. Please don’t hesitate to contact Support if you have any questions or feedback.