There were times when it felt like every Android user was almost guaranteed to see the "Force Close" dialog at least once a day. Today the framework provides all the tools and patterns you need to develop stable apps running within an extremely fragmented hardware eco system. But there are still some pitfalls you will run into sooner or later after you started your Android development journey.
To keep the system responsive there are certain conditions that will trigger an dialog that allows the user to kill your app.
- After an input event was generated, your app has 5 seconds to consume it. If it doesn't respond within this time frame, the dialog will be shown.
- Misused BroadcastReceivers that do not finish execution within 10 seconds will trigger the ANR ("Application Not Responding") dialog.
There is an official training article about "Keeping your App Responsive" that you might find useful.
Ignoring Activity Lifecycle
Your app should be able to deal with changes in the device configuration, with limited resources, with sleeping and rebooting devices and many, many other changes in the environment it is running in. If you do not feel the need to react to specific changes in the configuration, your app should at least respect the activity lifecycle. The framework provides hooks into the activity lifecycle that you should use to handle state or configuration changes so that your app can restore its state after it is resumed.
If your application is multithreaded, you should use the components provided by the framework to achieve what you need. Those components implement the desired functionality while being aware of lifecycles and system limitations. To develop stable, multithreaded applications for Android you should at least know about the following components:
- AsyncTask is a class that lets you perform heavy operations on a background thread and populate the GUI thread with the results without having to think about management of threads.
- Communication between threads is done by using Handlers and Loopers.
- If you want to manage the threads on your own, you should use ThreadPools and Executors.
Keep in mind:
- Never manipulate the UI from an background thread!
- Never do network operations on the UI thread!
This will instantly crash your app.
Although there's a steep increase in available resources with cutting edge devices, you have to keep in mind that only a small fraction of your users will use your app on the latest hardware. To protect the user from apps that are wasting resources, Android uses hard coded limits that you should avoid to hit:
- Android sets an hard limit per app for the heap size. This limit is individual to each system. If you're exceeding this limit, your app gets killed. Too fast growing heap sizes also result in your app being killed by the system.
- Deeply nested view hierarchies can exceed the hard stack size limit set by the system and result in an StackOverflowError (stack sizes: 8KB for API level 3, 12KB for API level 4-10, 16KB for API levels 14-17)
Frequently Done Mistakes
After you convinced the system that your app is a good citizen, there are still plenty of mistakes to do that will make your app crash.
- Using new components of later framework versions on an older Android platform. Look at the API levels in the documentation!
- AsyncTasks loosing their linked Activity lead to IllegalStateExceptions (use AsyncTaskLoaders if possible).
- Adding Fragments after onPause or before onResume yields an IllegalStateException.
- If you are binding data sources to the UI, use CursorLoaders.
- Be aware that you can't assume your app is running well if you're using the framework as intended. Vendors like to do their own things and change the framework to remove expected behavior and to add unexpected behavior.