During the last years I has been working in different apps and spend some time developing my own app. I learnt a lot about Android and about the user experience. Now it is time to give something in return to the great Android community.
What I am going to describe are not just only ideas, I have applied all of them into my app AppHunt. So you can check what I am talking about instead of believing in what I say.
1. Startup time
Users are impatient, when they are opening an app they want it to be open even before they click the icon of the app. This time is important because taking a few seconds to start an app only leads to set the user in a bad mood. No one wants a user in a bad mood before using your app.
The startup time is about initializing your app and load everything you need before start showing data. So basically what you should do is to load less things in the startup time.
There are some libraries that helps you to write less boilerplate code with some syntactic sugar. I am talking mostly about dependency injection libraries. They do it so well because they use reflection in order to understand the code and reflection is really slow. Fortunately, lately most of those libraries are moving to code generation instead of reflection to solve this issue.
If you are using content providers, the first time a user opens your app those providers need to be initialized. The more providers you have and the more complex database creating script you have the more time it is going to take. It is not a big deal as you most probably need them but they add to your startup time.
Resources also takes time to load. You can reduce the size of your bitmaps so they load faster. Also, don’t forget to add all the screen densities for your resources, that will save time to Android because it doesn’t have to scale them on the fly.
It is very common to notify the servers when a user opens the app, synchronize some data with the sever as the user profile or collect data the app will need later. All these things are done in background processes and they usually are initialized in the application class because it is the starting point in every app. The problem with this background jobs is that they will use the network connection and next network requests will be affected. So it is a good idea to delay a few seconds the initialization of the background processes, the resources will be free to load the first screen faster.
No matter what you do, the initialization of the app is going to take some time and you cannot do anything about it. For example, the support libraries are becoming heavier with every version and the activities takes longer to load. It is easy to notice it in old devices. You cannot make things faster but you can make the user feel everything is fast. Although I am not a big fan of launch screens they help (if they are done correctly). Instead, of showing a white screen you can use the startup time to show your logo and make some branding. This way the user is not seeing a blank square in the mobile but seeing the awesome logo of the app they are about to use.
You write you app once but users are going to open it millions of time, so keep it in mind. Load only what you need to show some data to the user.
2. Loading data
Most apps need to load data from a remote serve before they can show anything to the user. This means that after waiting for the app to start now the user has to wait until you make a http request to get the data, parse it and show it. You need this to be really fast.
The faster things are the things you don’t have to do.
A cache means that you have the data closer, you don’t need to make a http request because you already have the data. You can use a memory cache which will be cleaned up every time the user starts the app, a persistent cache as the SQLite database in Android or a mix of both. Ideally you will have a mix of both, don’t use only memory because there are mobiles that do not have a lot of memory and heavy users fill it very quickly and they start facing out of memory errors, which are a pain.
When the user opens the app you may want to show the data you already had in your SQLite database. But this solution has other problems. First, the data is probably going to be outdated and it is very likely the user has seen it before. Users like fresh content, otherwise they are unhappy. Second, after showing this old data the user may start to scroll or interact with it and you are going to interrupt his actions changing all the data in a blink. This is bad, really bad. Some apps, like Digg, do this and it is very frustrating. Don’t do it.
It is a good idea to load data that the user may need later in advance. For example, if the app is showing a list of items you can assume the user is going to click in some of those items because he wants to see more about them. So when you make a http requests to get a list of items, you can store more information about those items and not just what the list shows. When the user goes to the detailed view of the image you should first load the data you have cached before, then make the http request for the additional data you need and finally update the view with the all the data. A more concrete example: you show a feed with items like Facebook, the user clicks in an element and you fill the detailed view with the data you already had and then make a http request to get the comments of that item. As the comments are usually at the bottom of the screen they probably will be loaded before the user even look at them.
Another common case where you can load the data in advance are the notifications. If a user receive a notification you can assume he is going to click on it. When the app receives a notification, before showing it to the user, you can make the http requests you need to load all the data related with the notification. When the user clicks in the notification the data will be in the cache already, ready to be displayed.
A typical case for cache is when the user rotates the device and the activity is recreated. You probably don’t need to make the http request again, you can load the data from the cache. Some developers like to use a retained fragment, a singleton or other ways of memory cache. But Android has already a loader manager which handles the lifecycle of activity and fragments. It requires more code as you have to create data providers, but it is usually a good idea to use providers for data anyway. Android loaders are the best way to load data.
Finally, and not less important, don’t forget the cache in the server side. If the data you are showing the the users in the first screen is the same for all of them you definitely need a cache in the server for this data. Without going in a lot of details, usually in the server side is a good idea to sacrifice a bit of freshness in the data in exchange of speed. You can cache the requests for 5 minutes and assume the data wont change in those 5 minutes, or invalidate the cache when you know it changes.
There is a technique that I haven’t used but it is a good idea for caching between the servers and the app: the entity tags or etags. An etag is basically a fingerprint of the data. When you make a request to the server it returns the data and the etag. Next time you make the same request to the server you send the etag. The server will compare the etag you have sent with the last etag of the data you are requesting, if both etags are the same it means the data hasn’t changed. As you already have the data in the cache of the app the server doesn’t have to send anything and you can display the data to the user faster. If the etags are different the server sends back the new data and the new etag.
2.2. Network requests
Cache is mostly related with the networks requests, so when you have a miss in the cache you want the request to be fast. In the case of http requests you must assume the worst conditions, which usually are the opposite of what you see when you are testing your app to your local server. Don’t spend all your development time faking a 2G connection, but try it from time to time to get a feeling of what a user may experience. There are a lot of Android users in India with slow connections and you don’t want to lose such a huge growing market.
In general the less data you request the better. It is going to take less time for the server to retrieve the data from the database, less time to process it, less time using the network and less time to parse it in the app. Obviously less data per request means more requests to get the data, so you have to find a good balance between small requests and less requests. For example, the web page of Facebook loads one item of your feed very quickly and them loads more items. As the first item takes a big space in your screen you feel it is fast. I suggest you to do something similar for your app, try to load chunks of data that fill the screen and not more, then load the rest of the data in other requests.
In addition to the previous example, if you are showing a feed you can start making the request for the next page of data before you reach the last item of data in the list. Users usually scroll slowly while they read the content so a couple of items before the last one in your list you can start making the requests to load the next page and append the next data to the list. If the requests are fast it feels like a real infinite scrolling for the user.
I am assuming most network request for the data are http requests against a REST API. There are important differences between http1 and http2. In case of apps you should know that http2 allows you to reuse connections and keep them alive longer. Reusing http connections is a really important if your requests are small as you can reuse TCP connections and avoid the overhead of starting the connection. I recommend you to to use use okhttp3 and take a look to its documentation as it supports http2 as protocol.
If images are part of your app they are probably going to be your bigger network requests. So try to compress them with web friendly formats (jpg, png, webp, etc) and don’t request full size images. The screen resolutions of phones are very dense, you probably wont need the same resolution for the image and users wont notice you are displaying images with less resolution.
If you are in control of the REST API for the app there are a lot of things you can do here too. I think it is very important to keep your endpoints simple and fast. The API should do a lot of things, just get the data and serve it to the app. One thing you can do to same some bandwidth is to send only the data the app will need and no more, you can removed fields of your objects not relevant for the request. You can also avoid to send the same object twice in the same http requests. For example, if you are requesting a feed of items and per every item you add the user related, it is probably you are going to add the same user to different items in the same request. You can only send the first one and the id of the user in the rest of items. Then in your app, if you are using a content provider, a sql to load the data it is very easy to get the data in the format you want.
If your app is a bit complex you probably have user authentication and you server will have to check the user to create a response. For example, the request could be to like an item in the feed, the server needs to know who is the user performing the action. If you send the user id in the requests then you have a security hole, a malicious user can create those requests and fake likes. You can fix this by storing a session id for the user that only the user and the server knows, so other users cannot like content in his name. But a session id means that the server has to make a call to the database to get the user id in order to save the like. You are adding some overhead to improve the security of your API. One way to solved this is using signed tokens. Instead of storing in the app the session id you store the signed token, which contains the information you may need per requests as the user id or the user profile. The token is signed by the server, so instead of making a call to the database the server only needs to verify the signature of the token and get the data it needs from it. You have security in your app without a lot of overhead. Bearing in mind that you probably need this for a lot of requests, they will save you a lot of time at the end.
2.3. Parse the data
Once you have made the http request you have the data in raw format in the http response body and you need to parse it before doing anything with it. Probably after parsing it you need to save it in you cache, in memory or in the sql database, and finally you can bind it to your layout.
Ideally you would only have the deal with the data the user is going to see. If you are processing data the user is not going to see in the screen then you are wasting resources and making the user to wait longer. That is why small requests, with the required fields and without repeated content make the process of parsing data faster.
After parsing the http response you could bind it directly to the layouts but as the cache it is very important you need to store in it first. If you are using the SQLite database as the cache you better do bulk inserts. A bulk insert means all the rows you insert in the database are treated as a single sql transaction. If you insert the rows one by one you are going to have a big overhead because every row is going to be a sql transaction.
I am going to make a small break here, because storing the data in the SQLite database is slower than binding it directly in the layout. But there are some advantage of storing the data in the SQLite database: you can use cursors which are memory friendly (good for endless lists), you can perform relational algebra to request your data (for example joining two tables, the feed and the users) and you can user content loaders.
Once the data is in the database you need to tell to the activity that it needs to update the data. You should do this in the same thread that made the http requests. Try to avoid broadcasts intents as they are usually slow and add a great delay.
The network request has finished and now the data is ready to be displayed. Now the layouts are in charge of keeping with a good user experience. Binding the data to the layouts should be a straight forward process, the problems here lies on the time the interface spends rendering the layouts. You want it to be small to keep at least 30 fps all the time. If views stay still the user wont notice the rendering issues, but he will do if the content scrolls, like in endless lists.
The best layouts are the custom views you can create to show your data in a very efficient way. But custom views means more time of development and more time to maintain or modify them. When creating your layouts try to keep them simple and avoid nesting views. A nested view means those pixels are going to be painted again, you can end up easily painting the same pixels 4 or 5 times and decreasing the frame rate.
Android has a lot of tools to measure the frame rate and the overdraw for the nested views, use them.
Using animations for speed and responsiveness is a bit counter intuitive. If your goal is to make the app fast why would you add animations? The response is simple, your app doesn’t only have to be fast it has to feel it is fast and not laggy, and animations are a good way for this. For example, it is better to show a loading indicator moving when you are doing a network requests that showing an still indicator. The user will sense something is happening and he has to wait a little, if he doesn’t see movement he is going to think the app is laggy and it can be a frustrating experience.
Animations also help to mask network requests in activity transitions. If you use a share element transition you can start the http requests when the activity is created while the animation is still running. Don’t make the animations longer than 300 milliseconds or the user will get tired of them after some time seeing them. Use those milliseconds to introduce the activity and to get the data you need, and don’t forget a loading indicator in case the http requests takes longer than the animation.
The monetization is the only way to make apps survive in the long term. So you also have to pay special attention to them, without any type of revenue the development of the app will stop. But without users to click on those ads they worth nothing. Users are more important than ads, and so showing the data is more important than showing ads.
The data always has to be loaded before the ads, but it is a good idea to start the request to get the ads just after starting the request to get the data. Even if you are not going to show the ad immediately you can have it in cache to show it when appropriate. If you are using native ads embedded in the content, as in the middle of a list, you can hide the view of the ad and make it visible when you have the data to fill the view. In this case nothing will be show if the ad requests fails, or if the user leaves the activity first. You should also be carefull with ads in the the middle of a list as the views are recycled and the users scroll fast. It is better to show the same ad than to load new ads when the user scroll because every new ad is a new http requests and the app can reach a point where is making so many http requests for ads that other requests have to wait.
Try to cache ads and don’t wait until you need them to make the http requets. If an ad takes few seconds more to load than the data users might left the activity before the ad is even visible. And if the users don’t see the ads they hardly are going to click on them.
- Startup time
- load less things in the startup
- reflection is really slow
- providers need to be initialized, be aware
- reduce the size of your bitmaps
- delay a few seconds background processes for synchronization with the server
- use launch screens
- Loading data
- load data that the user may need later in advance
- use the loader manager
- don’t forget the cache in the server side
2.2. Network requets
- the less data you request the better
- start making the request for the next page of data before you reach the last item
- don’t request full size images
- keep your endpoints simple and fast
- removed fields of your objects not relevant for the request
- use signed tokens for user authentication
2.3. Parse data
- processing data the user is not going to see in the screen then you are wasting resources and making the user wait longer
- bulk inserts
- custom views
- avoid nesting views
- use the android tools for the frame rate and overdraw
- your app doesn’t only have to be fast it has to feel it is fast
- mask networks requests with activity transitions
- data is more important than ads
- load the ads before needed if possible
- if the users don’t see the ads they hardly are going to click on them
I will appreciate any comment to improve this post or suggestion to write a new one. Contact me in email@example.com
If you are a developer you may consider to share your app in AppHunt.