Increasing performance in an Android application

Performance is the most important parameter in a mobile application—if it’s slow and/or buggy, then on the whole, it’s likely to be rejected by users. Then there’s a competitive factor. The better your app performs, the better the chances are for your app in the market.

Personally, I’d spend a couple of extra dollars for significantly better performance. Someone wise once said time is money, so let’s cut to the chase and look at how developers can optimize performance on Android.

Overview:

In this post, we’ll work through things that will help you increase the performance for your Android apps. These improvements primarily cover micro-optimizations that can improve overall app performance when combined. This post covers best practices and common mistakes I’ve seen people make—or mistakes I’ve made myself and have learned how to correct. Let’s get right into it.

Table of Contents

Working with threads:

One of the secrets for a stable and consistently high-performing application is how you manage threads within it. I’d say 90% of performance issues can be solved if you know how to work with threads.

First of all, there are two main types of threads that you should be aware of: the main thread, also called UI Thread, and the background thread. Any changes that you make to the UI thread block the main thread, and this won’t be assigned to another method or call until the current task is finished and it follows the FIFO method.

Briefly, never do the following things on the UI thread:

  • Load Images or Streams
  • Parse a JSON
  • Access a local or remote server
  • Make database calls, even SQLite (if possible)
  • Make API calls

There is an extensive document by the Android team on working with threads, which can be found below:

Managing memory leaks:

The first and the most important problems that you need to take care of are memory leaks. What are memory leaks, and how do they happen? A memory leak is when the garbage collector is not able to collect the allocated memory.

For example: If we hold the reference in our code to an unused Activity, we hold all of the Activity’s layout and, in turn, all of its views and everything else that the Activity holds.

  • Avoid Static References: Another problem is when you keep static references. Don’t forget, both Activities and fragments have a lifecycle if referenced improperly, Once we have a static reference, this Activity, or fragment, will not be garbage collected.
  • Unregister your events and handlers: I notice that a lot of times developers don’t unregister events and handlers. This is one of the easiest ways your app could leak. Assume you have an Activity that has a listener assigned to a singleton:
 @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SomeManager.getInstance().addListener(this);
  }

You’ll need to make sure you remove this listener once you’re done:

@Override
public void onDestroy() {
  super.onDestroy();
  SomeManager.getInstance().removeListener(this);
}

In the onDestroy, for our Activity, we simply unregister ourselves from all events and handlers. Of course, this raises another important question: Should we use singletons in the first place (as shown in the example above)? Usually, we shouldn’t (depends on your architecture, of course), but sometimes it’s the only solution.

Steps to remove leaks:

  • Avoid using static variables.
  • Always unregister your events and listeners.
  • Use tools such as Eclipse Analyzer or LeakCanary to find these leaks.
  • Perform code reviews and code review implementations. Sometimes your peers can point out things you might not have noticed at all. Usually, this happens when you’ve been looking at the same code over and over again.
  • Understand your architecture properly before writing any piece of code, so you don’t do anything unnecessary.

Removing deprecated APIs:

Deprecation essentially references when an API is removed and, it could be that one or two days after a major release, your app might not work on certain devices. To make things even worse, sometimes if you’re dependent on an older version of a library, there’s no way to update both APIs and tools.

The major reason you shouldn’t use deprecated APIs is that after the API is removed from the Android system, your application won’t be able to find it and will crash with a RuntimeException saying that the method was not found.

Similarly, always make sure that if you’re using libraries, that they’re maintained by the providers—if they’re not maintained, you might again face the above issues and get stuck in a pothole without a way out.

To avoid issues with deprecations:

  • Know and use proper APIs.
  • Refactor your dependencies.
  • Don’t abuse the system by accessing private methods using reflection.
  • Update your dependencies and tools periodically.
  • Newer is better.

Some Tips:

Prefer Toolbar over ActionBar, and prefer RecyclerView over ListView, especially for animations, and if you have a bunch of huge images, because it’s optimized for that.

An example of deprecation prevention is the Apache HTTP connection that was removed in Android M. By the way, it’s still available as a Gradle dependency. So you’d need to use HttpURLConnection instead. It has simpler APIs, light-weight, transparent compression, better response caching, and other amazing features.

Avoid Abuse:

You might read this section title and ask, what does avoiding abuse actually mean? What I mean is to avoid the following:

  • Don’t call private APIs by reflection.
  • Don’t call private native methods from the NDK or C level.
  • Using adb shell am to communicate with other processes should be avoided.
  • Don’t use Runtime.exec to communicate with processes.

Prefer static methods over virtual methods

If you don’t need to access an object’s fields, make your method static. Invocations will be about 15%-20% faster. It’s also a good practice because you can tell from the method signature that calling the method can’t alter the object’s state.

Use static final for constants

I’ve seen many people make this mistake, wherein they declare static constants without the final key. You should always define your constants with the final keyword. When you have a static field without the final keyword, that variable is considered as a field and not as a constant. For example, imagine you have two fields defined, as shown below:

The compiler generates a class initializer method called <clinit> that’s executed when the class is used for the first time. The method stores the value 42 into integerVal and extracts a reference from the classfile string constant table for stringVal. When these values are referenced later on, they are accessed with field lookups. But when you add the final keyword to it…

the class no longer requires a <clinit> method, because the constants go into static field initializers in the dex file. Code that refers to integerVal will use the integer value 42 directly and accesses to stringVal will use a relatively inexpensive “string constant” instruction instead of a field lookup.

Use for-each loop instead of for loop

The for-each loop, also known as the enhanced for loop, is always a better option when trying to iterate through collections that implement the Iterable interface, or even for arrays.

The Android docs say:

So you should use the enhanced for loop by default, but consider a hand-written counted loop for performance-critical ArrayList iteration.

Use Profilers to profile performance

Profiling is one of the most important steps that you should perform on any application that has any kind of performance issues. There are multiple profiler’s available that you can use for this, you can check the below docs by Android for understanding and working with profilers.

Avoid using float

As a rule of thumb, floating-point is about 2x slower than the integer on Android devices.

In terms of speed, there’s no difference between float and double on more modern hardware. Space-wise double is 2x larger. As with desktop machines, assuming space isn’t an issue, you should prefer double to float.

Also, even for integers, some processors have hardware multiply but lack hardware divide. In such cases, integer division and modulus operations are performed in software — something to think about if you’re designing a hash table or doing loads of math.

Layout performance improvements

Layouts are a fundamental part of any Android application, and they directly affect the UX. If implemented inadequately, your layout will lead to a memory-hungry application with slow UIs. The good part is that the Android SDK includes certain tools to help you identify problems in your layout performance, which when combined with the below tips, will help you implement smooth scrolling UI’s with a minimal memory trace.

Re-using layouts with <include/> and <merge/>

Although Android has a variety of controls to provide efficient and re-usable interactive elements, you might also need to re-use larger components like custom controls that require a special layout.

To efficiently re-use complete layouts, you can use the <include/> and <merge/> tags to embed another layout inside the current one. Reusable layouts are particularly powerful, as they allow you to create a reusable complicated layout. For example, a yes/no button panel, or custom progress bar with description text.

It also means that any parts of your application that are common across multiple layouts can be extracted, managed separately, and then included in each layout using the above methodology. So while you can create individual UI components by writing a custom View, you can do it even more easily by re-using a layout file. You can check the Android documentation for a detailed guide on working with reusable layouts.

Delayed View Loading

Sometimes your layouts might require complex views that are rarely used, i.e. under a certain condition. Whether they’re item details, progress indicators, or undo messages, you can reduce memory usage and speed up layout rendering by loading the views only when they’re needed. Delayed loading of resources is an important technique to use when you have complex views that your app might need in the future. You can implement this technique by defining a ViewStub for those complex and rarely-used views.

You can check out this amazing blog by Google on optimizing layouts with ViewStub

Optimized Hierarchy

It’s a common misconception amongst the community that using basic layout structures is the most efficient way of coming up with consistent and high-performance UI controls and layouts. However, each widget and layout you add to your application requires initialization, layout, and drawing. For example, using a nested LinearLayout can lead to an excessively deep view hierarchy.

Moreover, nesting several instances of LinearLayout that use the layout_weight parameter can be especially time-consuming, as each child needs to be measured twice. This is particularly important when the layout is inflated in the View repeatedly, such as when used in a ListView or GridView.

You can use Hierarchy Viewer and Lint to examine and optimize your layout.

Inspecting your layout with Hierarchy Viewer: The Android SDK has a tool called the Hierarchy Viewer that allows you to examine your layout while your application is running. Using this tool helps you discover bottlenecks in layout performance.

For more on how to use this, you can check the Android docs.

Working with Lint: When you check the Android docs in regards to working with lint it says:

Other benefits of using Lint include:

  • Lint is integrated into Android Studio.
  • Lint automatically runs whenever you compile your program.
  • With Android Studio, you can also run lint inspections for a specific build variant, or for all build variants.
  • Lint has the ability to automatically fix some issues, provide suggestions for others, and jump directly to the offending code for review.

Know and use libraries

Always know the API(s) and the libraries that you use. Always prefer to use a library’s code over writing your own. Remember that the system has the privilege to replace your calls to the library method(s) using a hand-coded assembler, which could be even better than the best code the JIT can come up with for the equivalent Java.

A classic example here would be the String’s IndexOf method and associated APIs, which Dalvik will replace with an inlined intrinsic. Similarly, the System’s ArrayCopy method is about 9x faster than a hand-coded loop on a Nexus One with the JIT.

Use native methods carefully

Another very famous myth is that developing your app with native code using the Android NDK is more efficient than programming with Java/Kotlin. Not necessarily. Moreover, there’s a loss associated with the Java-native transition, and the JIT can’t optimize across these boundaries. If you’re allotting native resources (memory on the native heap, file descriptors, or whatever), it can be significantly more complex to arrange timely collection of these resources.

You also need to compile your code for each architecture(ABI) you wish to run on (rather than rely on it having a JIT).

Native code is essentially useful when you have an existing native codebase that you want to port to Android, not for “speeding up” parts of your Android app written in Java.

If you do need to use native code, you should read Android’s JNI Tips.

Summary

This blog described and discussed techniques for increasing the performance of an Android application. Collectively, these techniques can greatly reduce the amount of work being performed by a CPU and the amount of memory consumed by an application.

If I’ve missed something, go ahead and add it in the comments. I’ll make sure to add any needed changes to the post. Also, if you find something incorrect in the blog, please go ahead and correct me in the comments.

Smash that clap button if you liked this post.

You can reach out to me on LinkedIn or StackOverflow! I am always available! 😛

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 0 Responses

Leave a Reply

Your email address will not be published. Required fields are marked *

wix banner square