Embrace’s iOS and Android SDKs are now built on OpenTelemetry!

Read the release

Solving UnityPlayerActivity.onPause ANRs

Learn why ANR errors are mistakenly associated with the OnApplicationPause Unity callback in your stack traces.

Editor’s note: This post was originally posted on Oct. 4, 2023 and was republished on Jan. 10, 2024 to ensure all content and links are up to date and accurate.

Identifying and resolving ANR (Application Not Responding) issues can be frustrating and time consuming, especially in the realm of Unity-based apps. Unfortunately, it’s work that can’t be avoided, as ANRs severely impact the user experience and Google Play Store discoverability.

A common source of pain for Unity engineers comes from ANRs that are mistakenly associated with the OnApplicationPause Unity callback.

In this post, we’ll provide some context, outline the common mixup, and explain how Unity engineers can avoid this confusion by focusing their efforts in more appropriate areas.

First, some context.

Android lifecycle events in Unity

The UnityPlayerActivity is a Java class implemented by Unity that is included in all Unity Android builds. It is the entry-point for the application, runs on the Android UI thread, is responsible for managing the app lifecycle, and serves as a sort of middleman between the OS and the Unity runtime.

The Unity runtime, and all game code, runs on a separate thread.

When the OS tells the app to pause, that message goes to the UnityPlayerActivity on the Android UI thread, which eventually gets to the UnityPlayerActivity.onPause function.

One of the things the activity needs to do to proceed with the pause is tell the Unity engine to invoke OnApplicationPause and suspend the game loop. But, since the Unity engine is executing on a separate thread, the activity must wait for a synchronization point at the end of the engine’s frame loop (this wait is the java.util.concurrent.Semaphore.tryAcquire we see in the ANR stack trace).

An example of an Embrace stack trace.
An example of java.until.concurrent.Semaphore.tryAcquire appearing in a Unity stack trace.

Under normal circumstances that wait would be only a few milliseconds, but if the Unity thread happens to already be blocked on some long running operation when this pause process starts, that is what results in an ANR.

It is important to note in this discussion that Unity is aware of this problem and does try to circumvent the issue themselves. 

To do this, it constructs the semaphore as a time-bound semaphore with a timeout of four seconds. Keeping in mind that Google classifies ANRs as 5+ seconds of the main thread being blocked, that means that everything else your app is doing must take one second or less. This is because lifecycle events of Android Activities are called serially, meaning that everything that’s co-synchronous must share the same resources. In this case, that’s Unity, the Activity processing itself, and any other SDKs and other Unity handlers.

Further complicating things, when the aforementioned tryAcquire semaphore exceeds its synchronization timeout, the UnityPlayerActivity tries to initiate app shutdown. However, this creates a situation requiring another tryAcquire semaphore. In some cases, this can create two four-second timeouts which together could be read as an 8 second ANR by Google if the two events happen close enough together.

As we can see, working with the interaction between the Unity thread, the Activity main thread, and other possible interceding threads (such as other SDKs that may be included, like the Meta Business SDK) can each individually contribute to a complicated and hard to debug problem.

Identifying UnityPlayerActivity.onPause ANRs

The Unity thread has been a constant source of ANR-related headaches for many engineers. Understanding the root cause of these issues is the first step towards resolving them.

Often, ANRs are mistakenly associated with the OnApplicationPause Unity callback, leading to confusion and ineffective problem-solving strategies. For example, the mixup can lead to logs and breadcrumbs being implemented in the wrong place, resulting in the appearance of the ANRs occurring before you thought they should.

To shed light on this misunderstanding, let’s dissect a common example:

  1. Synchronous operation on Unity thread: During normal gameplay, the Unity main thread initiates a synchronous operation, such as scene loading, asset bundle loading, or resource loading. These operations can be time-consuming and any one of them could be responsible for blocking the Unity thread.
  2. App backgrounded: Before the synchronous operation can be completed, the user sends the app to the background.
  3. OnApplicationPause is triggered: UnityPlayerActivity.onPause is called on the Java thread in response to the app being backgrounded. However, this call does not mean the ANR is happening inside the OnApplicationPause Unity callback itself. Rather, it’s because the Java thread is waiting for the Unity thread to finish its ongoing operation and reach the end of the current frame loop so that it can execute OnApplicationPause.
  4. Unity thread remains blocked: If the Unity thread remains blocked for several seconds while the Java thread is waiting, the ultimate result is an ANR.

In essence, the ANR is not directly tied to the pause callback but is a result of the Unity thread being stuck in some other operation when the pause is triggered.

To effectively address ANRs in this scenario, developers should focus on identifying synchronous operations on the Unity thread that could be slowing down the application, rather than focusing solely on the OnPauseApplication callback.

Learn more about solving Unity ANRs

Understanding the intricacies of ANRs in Unity applications is crucial for effective troubleshooting. With that in mind, it’s important to remember that ANRs related to Unity threads often result from synchronous operations on the Unity thread, rather than the OnApplicationPause callback itself.

Issues like these can be more easily identified and resolved with tooling like Embrace.

To learn more about solving Unity-based ANRs with Embrace, including ones related to the OnApplicationPause callback, read the Wildlife Games Studios case study, here.

Embrace Learn how Wildlife uses Embrace to solve ANRs

ANRs negatively impact player experience and Google Play Store visibility. Learn how one of the biggest mobile gaming studios in the world is using Embrace to eliminate ANRs from your favorite games.

Read the case study

Build better mobile apps with Embrace

Find out how Embrace helps engineers identify, prioritize, and resolve app issues with ease.