Performance is the most crucial aspect of any mobile application, if your app is slow and laggy, users will likely reject it. And by the same token, the better your app performs, the more likely it will succeed in the market. For that, developers should pay some more attention and take some time to improve the performance.
Today’s article is going to cover some of these tricks, best practices, tools, and mechanisms to better debug and enhance your android application performance.
This article presumes you have some experience in building Android apps with Kotlin, and some general knowledge of Android studio.
Table of Contents
- Optimizing Thread Usage
- Caching and Offline Mode
- App Launch Time
- Tools for Performance Monitoring
- Memory Usage in Android
- Other Tips
In Android, there are two main types of threads you should be aware of. The main thread, also known as the UI thread, is responsible for drawing the UI and managing user interaction with the app. The second type are background threads, which can handle long-running operations while the main thread stays to manage UI updates.
To maintain good performance and good user experience, the UI thread will always redraw your user interface every 16ms. To accommodate for that, you shouldn’t run anything that takes a significant amount of time in the UI thread, otherwise, you will miss that 16ms limit, and your app will look laggy and unresponsive, you should always perform long-running tasks in background threads.
The Android SDK provides several ways to perform long-running tasks:
- Kotlin Coroutines: https://developer.android.com/kotlin/coroutines
- Executors: https://developer.android.com/reference/java/util/concurrent/Executors
- Work Manager: https://developer.android.com/topic/libraries/architecture/workmanager
- Services: https://developer.android.com/guide/components/services
These mechanisms can be used to handle tasks that take a significant time like loading an image from a remote server, making API calls, parsing a JSON, or performing SQLite insert or search queries.
For a stable and high-performing application, you should know how to manage threads and background/parallel processing well.
Reducing network traffic, in general, helps to decrease battery consumption. To get the best networking performance, load textual data first. Image-based content in your application can then be loaded asynchronously as the second preference.
When dealing with images, don’t forget to use well-tested libraries that provide caching mechanisms such as Glide or Picasso, or Retrofit when dealing with general networking.
Avoid downloading the same data over and over again, you can cache static data and resources when possible, such as full-size images (Use Glide for that) and keep them for as long as you can. That way, you avoid redundant network calls because duplicated requests increase system congestion and decrease the application performance, especially when you have a lot of users.
You can also use Jetpack’s WorkManager to sync and load data that your app will need in the future when the user is off the application. Don’t wait until the user enters the app to start that work. You can set some conditions and constraints with WorkManager, such as when the network is connected, and when the charger is plugged in, and at a specific time, for example midnight, to perform some jobs, and this will help you optimize data loading and caching.
Finally, think about adjusting the app requests based on the network quality available: your app can dynamically change the content quality delivered based on the available network quality. This will make your app seem aware and intelligent, and more importantly, it uses the network wisely.
Caching and Offline Mode
This is a trick for times when your network is weak, or there is no network. It’s always better to display something rather than displaying nothing at all. To do that, your app should perform some sort of caching, where it caches the data from the network (or some other source) on device storage, and you display it from there.
This is especially powerful if most of the data you are loading is the same each time you run the app. This lets you reduce the server load, and consequently, you reduce the mobile phone’s radio usage, which results in improved battery life.
You can test this logic by switching your phone into airplane mode so you can see how your application behaves in offline mode.
App Launch Time
One of the first things that users will experience when using your application is its launch time. You really need your app to launch and start as quickly as possible to avoid user frustration. There are multiple principles to follow that will contribute to a faster launch time:
- Don’t instantiate too many views on the first screen that appear to the user
- Don’t inflate parts of the UI that you don’t need during the initial launch
- Don’t run expensive content decoding like bitmaps
- Use Firebase Performance Monitoring or Android Vitals to monitor and optimize your app launch time
- Avoid heavy initialization in your application object
Tools for Performance Monitoring
There is a saying: “If you can’t measure it, you can’t improve it.” This rule applies everywhere, from our personal lives to our professional lives. If you want to improve your application performance, you have to measure its performance at all times, and here we present some of the tools you can use.
If an app reacts slowly, has choppy animations, freezes, or consumes too much power, we label it as having poor performance. Identifying areas where your program uses resources inefficiently, such CPU, memory, graphics, network, and device battery usage is the first step to resolving performance issues.
Another thing to pay attention to is the variety of devices you run tests on, since not all users have +2GB of Ram and a good CPU. A popular mistake among developers can make is optimizing the code for the wrong device. It’s usually high-end devices available to them or emulators configured with high-end resources running on workstation. A good rule of thumb is to have a variety of devices on which to optimize your app’s performance. This variety can be in CPU speed, resolution, and memory size.
Let’s have a look at the tools we can use to monitor our apps performance so that we can fix performance issues:
1. Android Profiler
Android Profiler is a tool built into Android Studio. You can find it at the bottom left corner of Android Studio beside the logcat and terminal tab. The profiler provides real-time data about your app usage in terms of CPU, Network, Battery, and Memory.
Figure 1 – Android Profiler tab in Android Studio
Let’s look at the various profilers views to track different performance metrics in Android profiler:
- CPU profiler: It helps track down runtime performance problems.
Figure 2 – CPU profiler
- Memory profiler: This tool can help you see the current memory allocations alongside memory leaks. You can also force Garbage Collector with it too, and see what objects are allocated in real-time. Garbage Collector (GC) is a process from Java Virtual Machine (JVM) that is responsible for finding unused objects and deleting them to free up memory. It executes autonomously when the JVM needs some space to allocate an object, with that in mind, you can use this profiler to force these GC events.
Figure 3 – Memory profiler
- Network profiler: it shows the incoming and outgoing traffic and its usage.
Figure 4 – Network profiler
- Energy profiler: This tool tracks energy and usage. You can check the Measuring performance page to learn techniques for measuring and optimizing performance, with some examples of how to use these tips to fix specific issues.
Figure 5 – Energy profiler
2. Firebase Performance Monitoring
Firebase Performance Monitoring is a free tool that allows you to measure the performance of your iOS, Android, and web apps. You can integrate it with your apps to collect performance data, review them, and analyze these data in the firebase console. With this monitoring, you can understand in real-time which parts of your application performance need to be improved and how to fix it.
Figure 6 – Firebase Performance Monitoring dashboard
This documentation shows you how to integrate it Firebase Performance Monitoring: https://firebase.google.com/docs/perf-mon/get-started-android, and this one shows you how to use the dashboard https://firebase.google.com/docs/perf-mon/console?platform=android
3. Android vitals
Android vitals created by Google, aims to enhance the stability and performance of Android devices. When a user uses your app, their Android device records information such as app stability, startup time, battery usage, render time, and permission denials. Then Google Play Console aggregates and display the data in their corresponding dashboard.
The cool thing is that the dashboard highlights crash rate, ANR rate, wakeups, background WIFI scans, app startup time, and much more, so the developer can give attention to these metrics in order to improve app performance.
Check the documentation here to learn more about Android vitals https://developer.android.com/topic/performance/vitals.
Memory Usage in Android
Before talking about memory management in Android, we should know how the Android system provisions memory for our application. Since the Android platform wants to provide their mobile users with the ability to multitask, the system limits the amount of RAM made available to each application. These limits are not fixed, and dynamically increase or decrease depending on corresponding mobile usage.
When the space allocated for your application is full, either the system will increase that space, or it will run a Garbage Collector Event to free up some space. If these two options don’t work, the Java Virtual Machine will throw an OOM Exception, or OutOfMemory Exception, so you need to be careful when you see such an exception in your stack trace.
Now, that we’ve briefly seen how Android manages memory to our application, we can turn to managing it. We need to be careful with how we manage our app’s memory. The most important problem most developers don’t pay attention to is memory leaks.
Memory leaks are a phenomenon that occur when unused objects occupy unnecessary space in memory and the garbage collector is not able to collect and free up that space. The problem here is that these unused objects are still being referenced. Let’s say, for example, that we hold a reference in our code to an unused Activity, then we are holding all of the layouts of this Activity, in turn, all of its views, and everything else that the Activity holds.
To avoid memory leaks and solve them here are some tips:
- Always unregister events and handlers after your app is done using them, or wait for the on destroy method, which is the final call you receive before your activity or component is destroyed.
- Avoid Storing Context Objects in your made classes. Some developers tend to pass Context from activities to classes, which stores the context in a private variable. If this happens, congratulations, we’ve created a memory leak!
- Use the Android profiler or LeakCanary to find the leaks in your application.
Figure 7 – Heap Dump for all the allocation in the heap in real-time
- Avoid Static References, since activities and fragments have a lifecycle, if you reference these components with a static reference, a garbage collector will not be able to collect such a component.
- Use and understand your architecture well, so you don’t do anything unnecessary.
- Sometimes code reviews and pair programming reveal some of the memory leaks because peers can point on things you might not have noticed at all.
Besides the tips and best practices mentioned in this article, here’s a list of miscellaneous tips to keep in mind when you’re trying to optimize your Android app’s performance:
- Don’t use deprecated APIs because either these API’s maintainers have provided new versions that run better than the previous versions, or the API is deprecated because of some issue that you aren’t aware of.
- Try to minimize your application size
- Use the latest format of packaging (Android App Bundle: .aab) over the previous one (APK)
- Always use the latest versions of whatever libraries and dependencies you use. (Newer is always better).
- Minimize the use of external libraries
This article explored and explained strategies for improving an Android application’s performance. These approaches, when combined, can significantly reduce the amount of work that a CPU does and the amount of memory that an application consumes.
When working on your app development services, be sure to follow these best practices. This might be the key to ensuring user retention and acquisition, which will eventually lead to benchmarking and breaking applications.