Unity and ANR
Published: 2024-04-03 18:10:21

My story is about how, by using a closed component, in simple terms a "black box" in the form of an SDK, frameworks, or any other module in our product, we end up in a tricky circle because we are dependent on this "black box".

In my products, I encountered ANR issues, as I use Unity. Therefore, I will further discuss the problems specifically related to Unity within the Android ecosystem.

In 99% of Unity projects, when exporting an Android project, the default `UnityPlayerActivity` is used. The proxy engine part is located in `com.unity3d.player.UnityPlayer`. So, using Android Studio, we can inspect what's inside `UnityPlayer`, let's go dipper.

`public class UnityPlayer extends FrameLayout implements IUnityPlayerLifecycleEvents` and another 1500 lines of code.

Let's start with something interesting, an example of ANR from **UnityPlayer**:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o5ks3t839mpctfif07m5.jpg)

Here is the code from Android Studio:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i5nermdzo6r0bnfcxgqq.png)


```
    private void pauseUnity() {
        this.reportSoftInputStr((String)null, 1, true);
        if (this.mState.f()) {
            if (m.c()) {
                final Semaphore var1 = new Semaphore(0);
                Runnable var2;
                if (this.isFinishing()) {
                    var2 = new Runnable() {
                        public final void run() {
                            UnityPlayer.this.shutdown();
                            var1.release();
                        }
                    };
                } else {
                    var2 = new Runnable() {
                        public final void run() {
                            if (UnityPlayer.this.nativePause()) {
                                UnityPlayer.this.mQuitting = true;
                                UnityPlayer.this.shutdown();
                                var1.release(2);
                            } else {
                                var1.release();
                            }
                        }
                    };
                }

                this.m_MainThread.a(var2);

                try {
                    if (!var1.tryAcquire(4L, TimeUnit.SECONDS)) {
                        com.unity3d.player.f.Log(5, "Timeout while trying to pause the Unity Engine.");
                    }
                } catch (InterruptedException var3) {
                    com.unity3d.player.f.Log(5, "UI thread got interrupted while trying to pause the Unity Engine.");
                }

                if (var1.drainPermits() > 0) {
                    this.destroy();
                }
            }

            this.mState.d(false);
            this.mState.b(true);
            if (this.m_AddPhoneCallListener) {
                this.m_TelephonyManager.listen(this.m_PhoneCallListener, 0);
            }

        }
    }
```
 
**1)** The first thing that catches your eye is:
```
if (!var1.tryAcquire(4L, TimeUnit.SECONDS)) {
    com.unity3d.player.f.Log(5, "Timeout while trying to pause the Unity Engine.");
}
```
The **tryAcquire()** method with a timeout of 4 seconds is a blocking call. If this operation takes longer than the specified timeout, it can cause an ANR.

**2)** If the **destroy()** method involves heavy operations, it can also cause an ANR.
```
if (var1.drainPermits() > 0) {
    this.destroy();
}
```
**3)** In short, each line of code below performs a heavy operation on the main thread.
```
var2 = new Runnable() {
                        public final void run() {
                            if (UnityPlayer.this.nativePause()) {
                                UnityPlayer.this.mQuitting = true;
                                UnityPlayer.this.shutdown();
                                var1.release(2);
                            } else {
                                var1.release();
                            }
                        }
                    };
```



**Conclusion:**
As you may have noticed, the main issue in this code is the handling of events in the main thread and proxying calls from the main thread to native code. Please note, this is not about writing to a file or downloading data from the internet. The way to solve these issues can be through `CountDownLatch`, `Thread`, `HandlerThread`, and many other built-in mechanisms.


Happy coding!

Thanks for reading!
If you enjoyed this article you can like it by clicking on the👏 button (up to 100 times!), also you can share this article to help others.

Have you any feedback, feel free to reach me on [twitter](https://twitter.com/pavel_pbs).