Photo by
Chris Liverani on
Unsplash
Common issues with Android Performance
Posted: 01 Nov 2021.
Last modified on 04-Jun-22.
This article will take about 4 minutes to read.
Performance is a topic which is usually saved until the last minute, or treated as tech debt. This writeup goes over some of the common performance issues that happen in Android apps.
The most important one
Doing work on the main thread
- Forgetting to move long-running work to background threads means that your app will hang!
- Users will receive an ANR (App Not Responding) notification after 5 seconds of inactivity
- Whenever you make a network call, access a database or do some other long-running task, make sure it is done asynchronously
- Some options for this include
rxjava
or kotlinx.coroutines
Allocations in the onDraw() or onDispatchDraw() methods
- All allocations should be made out of the draw loop!
- Each frame will be wasting work, which increase the chance of a dropped frame
- Increased allocations increase the risk of a garbage collection, which will create a longer skip
Overdraw
- Overdraw happens when a portion of the screen is painted over multiple times within the same frame.
- A common example of this is setting a background for an element that will always take up the full size of its viewport
- This is wasted work! Why paint twice when only the results of one of them will show?
- This can be visualized with the
GPU overdraw
tool in settings
Multiple Recyclerview Viewpools
- When a recyclerview has other recyclerviews within it (think horizontal carousels), all of the views in the child carousel have to be reinflated when the carousel scrolls on screen.
- This can be solved by sharing all views in the parent and child RecyclerViews, into a shared
RecyclerView.RecycledViewPool
Nested RelativeLayouts and Constraintlayouts
- The amount of time spent calculating the results of
requestLayout()
goes up exponentially when you have multiple Relativelayouts
or ConstraintLayouts
- There should be a top level layout that orchestrates the rest of the views instead. Use
LinearLayouts
or FrameLayouts
to group nested content.
- setting
setHasFixedSize(true)
on a RecyclerView
means we can skip checking if the size of individual items changes when new items are added
- Request layout called repeatedly within recyclerviews with only one type of view
Setting the background multiple times
- It is possible to call
setBackground
repeatedly on the same views
- This contributes to overdraw, which gives the GPU more work to do
- If it spends more than 16.6ms drawing (60fps = 16.6ms * 60), the frame will drop and be emitted a frame late
Other UI issues
- Overlays being drawn inefficiently
- Not using fresco placeholders in favor of setBackground
There are more possible recyclerview optimizations, such as the ones outlined here: https://medium.com/google-developers/recyclerview-prefetch-c2f269075710
Cache miss
- The data that we need is not being locally stored
- If the app is making the same call time and time again, see if you can cache the response with a timeout, instead
Too much data
- Some endpoints give back much more information than you will be able to use
- Copying all of that information from the network takes time, and will make your app appear to be working slower
- Try breaking a large endpoint into smaller, more focused ones, or moving to
graphql
, which will give you more control over what you request
Too little data
- Some endpoints might require you to make calls to other endpoints as well, to get all of the data required to make a final submission.
- Cart endpoints sometimes fall into this category
- Make sure that you are making as many requests in parallel as you can, and show the user that you have received at least some information, and are waiting on the next set.
Lack of feedback
- Users have a high abandon rate after 3 seconds of inactivity, so they need to know that you are making progress for them
- Simple spinners can show that work is going on in the background
Notification of new data
- A common pattern is to serve whatever data you have in your cache, while sending a request for new data from the server
- The content will show immediately, but could be out of date
- When the new data arrives, you can show the user a notification that lets them know their content is out of date.