Join us for ⁠#Android11: The Beta Launch Show on June 3!

Crashes

An Android app crashes whenever there’s an unexpected exit caused by an unhandled exception or signal. An app that is written using Java or Kotlin crashes if it throws an unhandled exception, represented by the Throwable class. An app that is written using native-code languages crashes if there’s an unhandled signal, such as SIGSEGV, during its execution.

When an app crashes, Android terminates the app's process and displays a dialog to let the user know that the app has stopped, as shown in figure 1.

An app crash on an Android device

Figure 1. An app crash on an Android device

An app doesn’t need to be running in the foreground for it to crash. Any app component, even components like broadcast receivers or content providers that are running in the background, can cause an app to crash. These crashes are often confusing for users because they were not actively engaging with your app.

If your app is experiencing crashes, you can use the guidance in this page to diagnose and fix the problem. For guidance on how to diagnose crashes in apps built using native-code languages, see Diagnosing native crashes.

Detect the problem

You may not always know that your users are experiencing an inordinate number of crashes with your app. If you have already published your app, Android vitals can help make you aware of the problem.

Android vitals

Android vitals can help improve your app's performance by alerting you, via the Play Console, when your app is exhibiting excessive crashes. Android vitals considers crashes excessive when an app:

  • Exhibits at least one crash in at least 1.09% of its daily sessions.
  • Exhibits two or more crashes in at least 0.18% of its daily sessions.

A daily session refers to a day in which your app was used. For information on how Google Play collects Android vitals data, see the Play Console documentation.

After you learn that your app is suffering from too many crashes, the next step is to diagnose them.

Diagnose the crashes

Solving crashes can be difficult. However, if you can identify the root cause of the crash, most likely you can find a solution to it.

There are many situations that can cause a crash in your app. Some reasons are obvious, like checking for a null value or empty string, but others are more subtle, like passing invalid arguments to an API or even complex multithreaded interactions.

Reading a stack trace

The first step to fix a crash is to identify the place where it happens. You can use the stack trace available in the report details if you are using Play Console or the output of the logcat tool. If you don’t have a stack trace available, you should locally reproduce the crash, either by manually testing the app or by reaching out to affected users, and reproduce it while using logcat.

The following trace shows an example of a crash on a sample app:

--------- beginning of crash
AndroidRuntime: FATAL EXCEPTION: main
Process: com.android.developer.crashsample, PID: 3686
java.lang.NullPointerException: crash sample
at com.android.developer.crashsample.MainActivity$1.onClick(MainActivity.java:27)
at android.view.View.performClick(View.java:6134)
at android.view.View$PerformClick.run(View.java:23965)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:156)
at android.app.ActivityThread.main(ActivityThread.java:6440)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746)
--------- beginning of system

A stack trace shows two pieces of information that are critical to debugging a crash:

  • The type of exception thrown.
  • The section of your of the code where the exception is thrown.

The type of exception thrown is usually a very strong hint as to what went wrong. Look at whether it is an IOException, an OutOfMemoryError, or something else, and find the documentation about the exception class.

The class, method, file a line number of the source file where the exception is thrown is shown on the second line of a stack trace. For each function that was called, another line shows the preceding call site (called a stack frame). By walking up the stack and examining the code, you may find a place that is passing an incorrect value. If your code doesn’t appear in the stack trace, it is likely that somewhere, you passed an invalid parameter into an asynchronous operation. You can often figure out what happened by examining each line of the stack trace, finding any API classes that you used, and confirming that the parameters you passed were correct, and that you called it from a place that is allowed.

For more information about crashes on native apps, see Diagnosing native crashes.

Tips for reproducing a crash

It’s possible that you can’t quite reproduce the problem just by starting an emulator or connecting your device to your computer. Development environments tend to have more resources, such as bandwidth, memory, and storage. Use the type of exception to determine what could be the resource that is scarce, or find a correlation between the version of Android, device type or your app’s version.

Memory errors

If you have an OutOfMemoryError, then you could create an emulator with low memory capacity to begin with. Figure 2 shows the AVD manager settings where you can control the amount of memory on the device.

Memory setting on AVD manager

Figure 2. Memory setting on AVD manager

Networking exceptions

Since users frequently move in and out of mobile or WiFi network coverage, in an application network exceptions usually should not be treated as errors, but rather as normal operating conditions that happen unexpectedly.

If you need to reproduce a network exception, such as UnknownHostException, then try turning on airplane mode while your application attempts to use the network.

Another option is to reduce the quality of the network in the emulator by choosing a network speed emulation and/or a network delay. You can use the Speed and Latency settings on AVD manager, or you can start the emulator with the -netdelay and -netspeed flags, as shown in the following command-line example:

emulator -avd [your-avd-image] -netdelay 20000 -netspeed gsm

This example sets a delay of 20 seconds on all network requests and an upload and download speed of 14.4 Kbps. For more information on command-line options for the emulator, see Start the Emulator from the Command Line.

Reading with logcat

Once you are able have the steps to reproduce the crash, you can use a tool like logcat to get more information.

The logcat output will show you what other log messages you have printed, along with others from the system. Don’t forget to turn off any extra Log statements that you have added because printing them wastes CPU and battery while your app is running.

Prevent crashes caused by null pointer exceptions

Null pointer exceptions (identified by the runtime error type NullPointerException) occur when you're trying to access an object that is null, typically by invoking its methods or accessing its members. Null pointer exceptions are the largest cause of app crashes on Google Play. The purpose of null is to signify that the object is missing - for example, it hasn't been created or assigned yet. To avoid null pointer exceptions, you need to make sure that the object references you're working with are non-null before calling methods on them or trying to access their members. If the object reference is null, handle this case well (for example, exit from a method before performing any operations on the object reference and write information to a debug log).

Because you don't want to have null checks for every parameter of every method called, you can rely on the IDE or on the type of the object to signify nullability.

Java programming language

The following sections apply to the Java programming language.

Compile time warnings

Annotate your methods' parameters and return values with @Nullable and @NonNull to receive compile time warnings from the IDE. These warnings prompt you to expect a nullable object:

Null pointer exception warning

These null checks are for objects that you know could be null. An exception on a @NonNull object is an indication of an error in your code that needs to be addressed.

Compile time errors

Because nullability should be meaningful, you can embed it in the types you use so that there is a compile time check for null. If you know an object can be null and that nullability should be handled, you could wrap it in an object like Optional. You should always prefer types that convey nullability.

Kotlin

In Kotlin, nullability is part of the type system. For example, a variable needs to be declared from the beginning as nullable or non-nullable. Nullable types are marked with a ?:

// non-null
var s: String = "Hello"

// null
var s: String? = "Hello"

Non-nullable variables cannot be assigned a null value and nullable variables need to be checked for nullability before being used as non-null.

If you don't want to check for null explicitly, you can use the ?. safe call operator:

val length: Int? = string?.length  // length is a nullable int
                                   // if string is null, then length is null

As a best practice, make sure you address the null case for a nullable object, or your app could get into unexpected states. If your application won't crash anymore with NullPointerException, you won't know that these errors exist.

The following are some ways to check for null:

  • if checks

    val length = if(string != null) string.length else 0
    

    Due to smart-cast and the null check, the Kotlin compiler knows that the string value is non-null so it allows you to use the reference directly, without the need for the safe call operator.

  • Elvis operator ?:

    This operator allows you to state "if the object is non-null, return the object; otherwise, return something else".

    val length = string?.length ?: 0
    

You can still get a NullPointerException in Kotlin. The following are the most common situations:

  • When you're explicitly throwing a NullPointerException.
  • When you're using the null assertion !! operator. This operator converts any value to a non-null type, throwing NullPointerException if the value is null.
  • When accessing a null reference of a platform type.

Platform types

Platform types are object declarations coming from Java. These types are specially-treated; null checks are not as enforced, so the non-null guarantee is the same as in Java. When you access a platform type reference, Kotlin does not create compile time errors but these references can lead to runtime errors. See the following example from the Kotlin documentation:

val list = ArrayList<String>() // non-null (constructor result) list.add("Item")
val size = list.size // non-null (primitive int) val item = list[0] // platform
type inferred (ordinary Java object) item.substring(1) // allowed, may throw an
                                                       // exception if item == null

Kotlin relies on type inference when a platform value is assigned to a Kotlin variable, or you can define what type to expect. The best way to ensure the correct nullability state of a reference coming from Java is to use nullability annotations (for example, @Nullable) in your Java code. The Kotlin compiler will represent these references as actual nullable or non-nullable types, not as platform types.

Java Jetpack APIs have been annotated with @Nullable or @NonNull as needed, and a similar approach has been taken in the Android 11 SDK. Types coming from this SDK, that are used in Kotlin, will be represented as correct nullable or non-nullable types.

Because of Kotlin's type system, we've seen apps have a major reduction in NullPointerException crashes. For example, the Google Home app saw a 30% reduction in crashes caused by null pointer exceptions during the year that it migrated new feature development to Kotlin.