Whether you’ve recently launched an Android app or are in the process of building one, every Android developer quickly discovers a fundamental truth: crashes are your worst enemy. Not only are they harder to prevent in native Android apps than in iOS, but crashes are also more likely to occur.
Before we dive into six reasons why these failures happen and how to defend against them, let’s start with some basics about bugs and user behavior.
Do you know the dirty little secret of software development? It’s a simple, if rather unfortunate, truth: you cannot fix every bug. No matter how much you try, bugs will always exist.
What you can control is identifying the most harmful issues and collaborating to fix them as quickly as possible. Not surprisingly, crashes qualify. They matter a great deal, and here are two reasons you want to minimize their occurrence:
Given those scary user retention numbers, it’s imperative that your app make a strong impression from the get-go and then continue to provide a crash-free experience. Users have a very low tolerance for crashes, and a full 84 percent will abandon your app completely if they experience two crashes.
If you still have your doubts about the seriousness of a crashing app, peruse the comments section in Google’s Play Store on a couple low-ranking apps. What you’ll discover quickly is that crashy apps make for cringe-worthy fodder (although this feedback is obviously not-so-fun for the app developer).
In short, trust goes out the window when an app crashes, and along with trust is the app’s reputation. A crash is a death knell and demonstrates why you must care about production monitoring from the very beginning.
As you likely know, building client-side software means your apps are running in an environment that’s out of your control. While server-side you can determine hardware, operating system, and which version of your app is live, none of that holds true client-side.
Think of client-side as the Wild West. Roughness, lawlessness, and disorderly behavior prevail. As befits this environment, there are six reasons why your Android apps are crash-prone. Thankfully, there are (usually) steps you can take to address the challenges.
Unlike iOS hardware, which Apple keeps on a notoriously tight leash, Android devices multiply like rabbits. In fact, more than 24,000 unique Android devices were counted in 2015, which was six times as many as three years before in 2012. The situation has only intensified in the last three years, so it’s safe to say that Android app developers are dealing with an incredibly fragmented hardware environment where many old versions are still in use, including phones from 2009.
Yup, phones from ten years ago are still in use. And, yes, that is scary.
What that means is that your app needs to run on devices with different CPU architectures, varying CPU and memory constraints, and that lack hardware features you might naturally expect (camera, Bluetooth). Because Android devices are often found in emerging markets and developing countries, there are many versions out there that cost less, have cheaper components and CPU, and have low amounts of memory. How do you handle these scenarios?
Actionable defenses: You have a couple options that can help safeguard you from disappointing a user.
<uses-features>element in your app manifest to filter your app so that it doesn’t show up for users whose devices do not meet your hardware and software requirements. For example, if a device doesn’t have a camera, then you won’t show up in that user’s app store options.
It’s much easier to gain root access to the Android operating system, which leads some advanced (and sometimes not-so-advanced) users to tweak their phones and tablets in unpredictable ways. When various subsystems and settings change, the components your app relies upon may be inaccessible. For example, rooted devices may block access to certain permissions at runtime, which inevitably leads to crashes.
Actionable defense: Your best defense is to detect rooted devices at runtime. It is possible to use root detection techniques and thereby restrict access to certain features and functionality within your app. Alternatively, some companies stop the app from working at all on rooted devices to avoid the dreaded crashes.
What’s the only thing more frightening than how many different Android devices are being used? That would be the number of operating systems currently in use. While Android lists thirteen platforms with more than 0.1% usage, it’s striking to note that the Marshmallow platform from 2015 is the second most widely used version (21.3%) behind the current Oreo platform (21.5%). Even though Marshmallow is over three years old, it’s still going strong, as are the two platforms that preceded it (2014 Lollipop at 17.9% usage, 2013 KitKat at 7.6% usage).
Needless to say, people in Androidland do not upgrade (and sometimes can’t do so even if they want to due to carrier settings), so it’s guaranteed that your app will run on multiple platforms. The question is, how many? The Android developer website recommends supporting 90% of active devices, but targeting your app to the latest version.
Actionable defenses: Once again, there are several best practices you can follow.
Well, this reason is terrifying: phone manufacturers can make modifications to core parts of the Android operating system. Not surprisingly, when core parts are edited, it can lead to crashes in your app. One example came about when HTC shipped their own version of GSON as part of their Android fork, which was super buggy and caused major issues with apps. The result? Crashes, of course.
Actionable defense: Sadly, there’s not much you can do about these types of crashes. Your only line of defense is to use a stability monitoring tool like Bugsnag so you can at least see what the problem is when this type of crash happens. Otherwise, the best we can say is…
Here’s a frustrating fact of client-side applications: customers do not update apps immediately. While it’s getting better these days with Android’s automatic updates, there’s still the potential that multiple different versions of your app can be live at the same time. You need to make sure the language they are speaking is the same language your service is speaking.
Actionable defense: Thankfully, this defense is relatively straightforward: version your APIs from day one. Any app that accesses network services must be backward compatible.
Our final crash reason happens to be the bane of developers’ existence, and even a stacktrace for the error won’t be helpful for solving the issue. An out of memory error is thrown when there is no memory left for your app to use. Keep in mind though, the last memory allocation that triggered the error is not necessarily what caused the memory leak, which is why the stacktrace won’t help you. Instead, prior memory allocations added up, and it was only the last one that reached the threshold. Think of it as the straw that broke the camel’s back.
Actionable defense: There’s a tool called LeakCanary that was built by the team at Square. It detects memory allocations and can spot when this scenario is occurring. LeakCanary has been billed as a memory leak detection library for Android (and you’ll be happy to know there’s a Bugsnag integration for it as well!).
The heartening news is that you’re not alone. Crashes happen to every app. In fact, in our experience, even high-quality applications crash between 0.1-1% of the time, and that’s okay.
Crashes are a part of software life, but they don’t have to ruin your app’s chance at long-term existence. Come prepared, put up defenses like the ones described above, and never fly blind in production. Use a stability monitoring tool like Bugsnag to make sure you know your bugs and can stop the ones that make you crash-prone.
Now go forth and conquer Androidland with your crash-resistant app!