by Lien Mamitsuka, Software Engineer

At Crashlytics, we‘re constantly exploring ways to help developers build the most stable apps. With this in mind, we recently began researching common reasons why Android apps crash. We were especially curious to see any trends in crashes originating from the Android Support Library, given that it is one of the most widely-used libraries in Android applications.

We found that about 4% of the 100 million crashes that we analyzed were related to the Support Libraries. Digging deeper, our research showed that the overwhelming majority of those crashes are caused by a small handful of recurring, preventable errors. Based on this analysis, we’ve identified the best practices for using the Support Libraries that are commonly overlooked and three key ways to increase stability.

1. AsyncTasks and Configuration Changes

AsyncTasks are used to perform background operations and optionally update the UI after completion. Using AsyncTasks and handling configuration changes is a common source of bugs. If the fragment is detached from its activity while your AsyncTask is running, and you attempt to access that activity, your application will crash with a call stack that looks like:

java.lang.IllegalStateException: Fragment MyFragment not attached to Activity at android.support.v4.app.Fragment.getResources(Fragment.java:551) at android.support.v4.app.Fragment.getString(Fragment.java:573)

In the above stack trace, the fragment is relying on a valid activity to access the application's resources. One way to prevent this crash from happening is to retain the AsyncTask across configuration changes.

This can be done using a RetainedFragment that executes the AsyncTask and notifies listeners about the status of the AsyncTask operations. For more information, see the FragmentRetainInstance.java sample.

2. Safely Performing Fragment Transactions

Fragment transactions are used to add, remove, or replace fragments in an activity. Most of the time, fragment transactions are performed in the activity’s onCreate() method or in response to a user interaction. However, we’ve seen cases where fragment transactions were committed when resuming an activity. When this happens, your application may crash with the following:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327) at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager:1338) at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595) at android.support.v4.app.BackStackRecord.commit(BackStackRecord:574) at android.support.v4.app.DialogFragment.show(DialogFragment:127)

Whenever a FragmentActivity is placed in the background, its FragmentManagerImpl ’s mStateSaved flag is set to true. This flag is used to check whether there could be state loss. If the flag is true when attempting to commit a transaction, the above IllegalStateException is thrown. To prevent state loss, fragment transactions cannot be committed after onSaveInstanceState() is called. The reason this crash may occur is that there are some cases in which onResume() is called before the flag is set back to false, when the state is restored.

To prevent this kind of crash, avoid committing fragment transactions in the activity’s onResume() method. Instead, use onResumeFragment() , which is the recommended approach to interact with fragments in their proper state.

3. Managing the Cursor Lifecycle

A CursorAdapter makes it easy to bind data from a Cursor to a ListView object. However, if the cursor becomes invalid and we attempt to update the UI, the following crash occurs:

java.lang.IllegalStateException: this should only be called when the cursor is valid at android.support.v4.widget.CursorAdapter.getView(CursorAdapter.java:245) at android.widget.HeaderViewListAdapter.getView(HeaderViewListAdapter.java:253)

This exception is thrown if the CursorAdapter ’s mDataValid field is set to false, which happens when:

- the cursor is set to null

- a requery operation on the cursor failed

- onInvalidated() is called on the data

One reason this may occur is if you’re using both CursorLoader and startManagingCursor() to manage your cursor. startManagingCursor() has been deprecated in favor of CursorLoader . If you are working with fragments, be sure to use CursorLoader to manage the cursor lifecycle and remove all references to startManagingCursor() and stopManagingCursor() .

Summary

By implementing these three guidelines, the chances of the Support Library throwing a fatal exception will be greatly diminished. Fewer crashes lead to happier customers, better ratings, and a more successful app!

Crashlytics for Android reports uncaught exceptions thrown by the Support Library or anywhere else in your app. Add our Android SDK to your app and see what other crashes you've been missing!