This blog post has been adapted from a popular conference talk given at DroidCon SF, AnDevCon, and various other company conferences. Learn about the common reasons Android apps crash and key takeaways for how to avoid them.
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:
Users are quick to abandon apps, even when they perform flawlessly. One in five users abandons an app after only one use. Before you say, “Well, that’s not too horrible,” keep in mind that the first use is only the initial hurdle. The average app loses 77 percent of its daily active users within three days of installation, 90 percent after 30 days, and more than 95 percent within the first three months. Tough audience.
You’ve got stiff competition. Whether you know it or not, an alternative to your app exists in the Android app store. With 2.6 million apps available as of December 2018, users can easily find a replacement if yours disappoints. When consumers have options, the pressure is on you to deliver a quality product, and a substantial part of that promise is stability.
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?
Detect features pre-download. Use the
<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.
Detect features at runtime. If the feature or hardware need is non-essential to your app, then you can push off the decision until runtime. In other words, if the user attempts to use the camera but there’s none available, the app can check and then tell the user they don’t have it available. This scenario is a much better user experience than a crash.
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.
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.
Specify supported versions in manifest. By identifying what the minimum version of Android is that you support in the manifest file, you can prevent your app from showing up in the Android store for any users with prior versions.
Detect version at runtime. If the API call is non-essential to your app, then you can push off the decision until runtime. For example, you can check for either a feature or the platform version at runtime and then disable particular features depending on the environment.
Use the Android Support Library. How would you like to make it seamless to support multiple API versions without sacrificing new features? When you embed the Support Library into your app, you can leverage it as a compatibility layer to older Android versions. That means less code to build and happier users who are supported appropriately in whatever version they use. In short, you don’t have to wait for your customers to upgrade, which is a reason to rejoice.
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.
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.
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.
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!