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)
FragmentActivity is placed in the background, its
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
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
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
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
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!