---
title: Avoiding Out of Memory Crashes on Mobile
teaser: 'How to use Streams to more efficiently manage memory when downloading large
  files using Unity Engine.

  '
tags: android,ios,mobile,unity
author: Steff Kelsey
published_on: 2016-12-20
---

One reason why it is difficult to develop software for mobile devices is that
the hardware is not the best compared to deploying to a console or a "real"
computer. Resources are limited. One particularly sparse resource is RAM. Out
of memory exceptions are common on both Android and iOS if you're dealing with
large files. Recently, when building a Google VR 360 video player, we went over
the 1GB of RAM available on older iOS devices pretty quickly.

## What Not to Do

One of my big complaints about the Unity manual and many tutorials is they
usually just show you how to do something really quickly and don't always tell
you the exact use case or how it can just flat out fail. For example, using the
relatively new `UnityWebRequest`, you can download a file over HTTP like this:

```csharp
private IEnumerator loadAsset(string path)
{
  using (UnityWebRequest webRequest = new UnityWebRequest(path))
  {
    webRequest.downloadHandler = new DownloadHandlerBuffer();
    webRequest.Send();
    while (!webRequest.isDone)
    {
      yield return null;
    }
    if (string.IsNullOrEmpty(webRequest.error))
    {
      FileComplete(this, new FileLoaderCompleteEventArgs(
        webRequest.downloadHandler.data));
    }
    else
    {
      Debug.Log("error! message: " + webRequest.error);
    }
  }
}
```

These are all off the shelf parts from Unity, with the exception of the
`FileLoaderCompleteEventArg` but just assume that we use that to pass off the
downloaded bytes as an array eg: `byte[]`. Notice this returns an `IEnumerator`
and utilizes `yield` statements so it should be run in a `Coroutine`. What
happens here is that the `UnityWebRequest` will open up a connection to the
given path, download everything into a byte array contained within the
`DownloadHandlerBuffer`. The `FileComplete` event will fire if there are no
errors, sending the entire byte array to the subscribing class. Easy, right? For
small files, sure. But we were making a 360 Video player. Our max resolution was
1440p. The first sample files we got for testing were bigger than 400MB. The
iPhone 7, with 2GB of RAM, took it like a champ. The iPhone 6, with 1GB of RAM,
crashed like a piano dropped from a helicopter.

## Why Did my App Just Crash?

Let's look at the guts of these components. The main focus is on the
`DownloadHandlerBuffer` object. When it is first created, it will start by
preallocating memory for a small byte array where it will store all the
downloaded bytes. As the bytes come in, it will periodically expand the size of
the array. In our test case, it was expanding the array until it could hold
400MB. And because each additional allocation is a guess, it will most likely
overshot that amount. Note, I am speculating here because I have not looked at
the source code for the `DownloadBufferHandler`. There is a chance it allocates
space based on the Content-Length header returned with the HTTP Response. But,
the result is the same; it will use up at least 400MB of RAM. That's 40% of the
1GB that the iPhone 6 has! We're already in dangerous territory. I know what
you're saying, "Steff, why did it crash if we only used 40% of the RAM?" There
are two ways to find the answer. One (and give Unity credit here) is in the
documentation for [`DownloadHandlerBuffer`][unity-buffer-handler].

> Note: When accessing DownloadHandler.data or DownloadHandler.text on this
> subclass, a new byte array or string will be allocated each time the property
> is accessed.

So, by accessing the data property, Unity allocates an _additional 400MB of
memory_ to pass off the byte array into the EventArg. Now we have used 800MB of
RAM just on handling this one file. The OS has other services running plus you
very likely have RAM allocated for bitmaps and UI and logic. You're doomed!

## Profiling Memory Allocations

If you didn't read the docs, and they're long: I get it, you could have found
this memory leak by running the application in Unity while using the Profiler
_AND_ by running the application on an iOS device while using a valuable free
tool from Apple: Instruments. The Allocations instrument captures information
about memory allocation for an application. I recommend using the Unity Profiler
heavily for testing in the Editor and then continuing performance testing on
device for each platform. They all act differently. Using the Profiler in the
Editor is only your first line of defense. In this case I only properly
understood what was happening when I watched it unfold in a recording using the
Allocations instrument.

## Streams to the Rescue

There is a way to download large files and save them without using unnecessary
RAM. *Streams!* Since we plan on immediately saving these large video files  in
local storage on device to be ready for offline viewing, we need to send the
downloaded bytes right into a File as they are received. When doing that, we can
reuse the same byte array and never have to allocate more space. Unity outlines
how to do that [here][unity-dl-handler], but below is an expanded example that
includes a `FileStream`:

```csharp
public class ToFileDownloadHandler : DownloadHandlerScript
{
  private int expected = -1;
  private int received = 0;
  private string filepath;
  private FileStream fileStream;
  private bool canceled = false;

  public ToFileDownloadHandler(byte[] buffer, string filepath)
    : base(buffer)
  {
    this.filepath = filepath;
    fileStream = new FileStream(filepath, FileMode.Create, FileAccess.Write);
  }

  protected override byte[] GetData() { return null; }

  protected override bool ReceiveData(byte[] data, int dataLength)
  {
    if (data == null || data.Length < 1)
    {
      return false;
    }
    received += dataLength;
    if (!canceled) fileStream.Write(data, 0, dataLength);
    return true;
  }

  protected override float GetProgress()
  {
    if (expected < 0) return 0;
    return (float)received / expected;
  }

  protected override void CompleteContent()
  {
    fileStream.Close();
  }

  protected override void ReceiveContentLength(int contentLength)
  {
    expected = contentLength;
  }

  public void Cancel()
  {
    canceled = true;
    fileStream.Close();
    File.Delete(filepath);
  }
}
```

And to use the above in our coroutine:

```csharp
private IEnumerator loadAsset(string path, string savePath)
{
  using (UnityWebRequest webRequest = new UnityWebRequest(path))
  {
    webRequest.downloadHandler = new ToFileDownloadHandler(new byte[64 * 1024],
      savePath);
    webRequest.Send();
    ...
    ...
  }
}
```

Looking first at our new `ToFileDownloadHandler`, we extended Unity's
`DownloadHandlerScript` and have overridden the required methods. The magic
happens in two places. First, we pass in a byte array to the base class via the
constructor. This let's Unity know that we want to re-use that byte array on
each `ReceiveData` callback where we only allocate a small amount of RAM once.
Second, we use a `FileStream` object to write the bytes directly to our desired
file. The rest of the code is there to handle canceling the request. Whenever
you deal with `FileStream` objects, you _must_ remember to close them out when
you're done.

Looking at the `loadAsset` method, we added a parameter for the path to where
the file will be saved locally and we defined the size of the buffer at 64MB.
This size is dependent on your network speeds. We were focussed on WiFi
connections, so a larger buffer made sense. Too small and you will make the
download take longer than necessary to complete.

## Where to Go from Here

Now you have an understanding of one way that your application can eat up RAM.
If you only take away one thing from reading this post it's this: for managing
memory  allocations, streams are your friends. And you should be constantly
performance testing as you develop your application, unless you're trying to
maximize one-star reviews in the App Store.

## Gotchyas

One final note on the code above: we did not end up going to production using
`UnityWebRequest` on iOS. When we tried using a similar streaming solution
as above, we found that the request was not clearing from memory if it was
canceled due to the user sending the application to the background. Using the
Time Profiler Instrument showed that `NSURLSession` objects were not being
cleaned up when the application paused and resumed, so eventually the CPU would
max out and crash. We had to seek an alternative solution for iOS using a native
plugin. However, in the final code we still used HTTP streaming directly into a
file via `FileStream`. Just not wrapped up in `UnityWebRequest` objects.

[unity-dl-handler]: https://docs.unity3d.com/Manual/UnityWebRequest-CreatingDownloadHandlers.html
[unity-buffer-handler]: https://docs.unity3d.com/ScriptReference/Networking.DownloadHandlerBuffer.html
