Five Lesser-Known Ways to Hang Your Main Thread

By now, most Android Developers already know that the main thread of an app is the one that handles user interaction, and should consequently be considered off-limits for time-consuming work. In general, any method invocation that causes the main thread to hang for 16*N milliseconds will lead to N dropped frames. We call such methods hung methods. In this blog post, we’ll first look at a hung method example, then look into five lesser-known ways that may hang the main thread.

Example Hung Method in Hollister

Let’s check out an example of a hung method in Hollister v3.1.2

Hollister v3.1.2 Icicle Graph
prefetchData() hangs the main thread for a very long time

As you might have already noticed, the method AFSDK.prefetchData() runs for a very, very long time. The main cause of this delay is ObjectMappger.readValue(), which is called by the Jackson library.

Fortunately, our developer friends at Abercrombie & Fitch were able to use NimbleDroid’s profiling tools to detect and fix this issue (as well as others) in version 4.0.0, dramatically increasing their application’s performance and their users’ satisfaction :).

v4.0 Icicle Graph
2.2 seconds for the new cold-start time, a huge improvement from 5.2 seconds.

Let’s take a closer look at the icicle graph:

Hollister v4.0 Icicle Graph
Parsing code was moved to RxCachedThreadScheduler-2

You can tell that it wasn’t a super complicated fix - developers simply moved the parsing of a large initial config into another thread (using the popular RxJava library).

A similar example is Fox News, which spends over 700ms reading data during startup.

There are many possible ways to hang the main thread. It is often a bad idea to use the main thread to access networks, storage, or databases - which are accesses that Android’s Strict Mode can detect. There are, however, other lesser known ways to hang the main thread that Strict Mode unfortunately can’t detect. The battle here is to understand all the different ways the main thread can be hung, and to check apps often to avoid these issues. Let’s take a look at five other problems that can also lead to hung UI threads.

1. Parsing Network Responses in the Main Thread

Although the developers of Akinator FREE app correctly access the network in a background thread, they parse the response in the main thread.

Akinator FREE app Icicle Graph
273ms for 192 calls of java.util.Scanner.nextInt

The developers parse data using java.util.Scanner, which, as you can see, is not very fast: 192 calls take up 279 ms.

2. Doing Cryptography Computations

Wiper 2.5, a free messaging and calling app that incorporates music and video sharing, invokes the bitcoinj library for over 4 seconds in the main thread.

Wiper app Icicle Graph
4060ms for call to org.bitcoinj.params.MainNetParams.get

We see another big issue with Kaave Falı 1.8.0, which generates cryptographic keys in the main thread using java-aes-crypto library.

Kaave Falı app
1882ms for call to javax.crypto.SecretKeyFactory.generateSecret

3. Eager Initialization

Initialization can take time, so it’s more efficient to do expensive initialization either in the background or lazily. FIFA spends over 269ms in Charset.availableCharsets(). The problem is somewhere deep in the SDK, but please note that this issue might be device specific.

FIFA app Icicle Graph
269ms for call to java.nio.charset.Charset.availableCharsets

4. Listing Assets

Talking James Squirrel spends a lot of time in AssetManager.list().

Kaave Falı app
378ms for call to android.content.res.AssetManager.list

Remember that the more assets you have, the more time this method call will take.

5. Waiting For Synchronization

The hung method examples that we’ve given so far prevent the main thread from responding to user actions by keeping the main thread busy. There are also hung methods that block the main thread by waiting for synchronization (e.g., inside Object.wait or Thread.join).

To illustrate this issue, consider the Crittercism SDK. During Crittercism.initialize(), the developers correctly spawn a background thread to do expensive network operations, but initialize() calls Thread.join(), which waits for the background thread to finish, effectively blocking whichever thread is calling initialize(). Hotel Tonight 8.5.0 is a good example of this problem; there are many more apps that also suffer from the same issue.

Hotel Tonight Blocked By Crittercism.initialize()
116ms for call to Crittercism.initialize()

Recommendation: Profile your app frequently to check if the main thread is hung. Move work to background threads, and as always, write efficient code.