Unity How To: Saving on the SD Card

Unity How To: Saving on the SD Card

Changelog

26.06.2019

Unity changed the API for calling java functions with return types. Changed (“getExternalFilesDirs”, null) to (“getExternalFilesDirs”, (object)null).

 

 

Today I want to show you, how you can save reliably on the sd card/external storage. This will only work for devices with android Lollipop (API level 21) or greater.

 

No java code necessary, only Unity!

 

If you must save on the sd card and the emulated external storage on the internal storage is no option for you, this is the code you need.

 

There are two facts that are essential to this approach.

 

  • First fact: It is possible to get the path to the sd card with native java code. (But we don’t want to use that, we are unity developers.)
  • Second fact: Every java function can be called via string with unity .

 

  • Conclusion: If we can do it in java, we can do it in unity too. We just need to know which native functions we want to call.

 

So I searched the native android developer forums for a way to get a reliable path to the sd card. And finally I found this in the xamarin forum:

https://forums.xamarin.com/discussion/103478/how-to-get-path-to-external-sd-card

 

In java you need 3 functions to get the sd card path:

  1. GetExternalFilesDirs
  2. IsExternalStorageRemovable
  3. IsExternalStorageEmulated

We can also call this functions with the androidJavaObject integration from unity. We just have to pass the names of the upper native functions. At this point it is really important to mind the spelling. You must call GetExternalFilesDirs, the s is important!

 

AndroidJavaObject[] externalFilesDirectories = 
                    context.Call<AndroidJavaObject[]> 
                    ("getExternalFilesDirs", (object)null);

 

If you forget the s you will get a path to a random external storage. That can be the sd card but it’s more likely that you get the emulated storage. But we want a list of the paths to all available external storages (sd card and emulated).

 

If we got them, we can find the path to the sd card via its properties. The sd card is in contrast to the emulated storage not emulated (Jep, that’s a property) and removable.

So we check these properties for each directory.

 

bool isRemovable = environment.CallStatic<bool> 
                   ("isExternalStorageRemovable", directory); 

bool isEmulated = environment.CallStatic<bool> 
                  ("isExternalStorageEmulated", directory);

// We found the sd card!
if (isRemovable && isEmulated == false) 
    return directory;

 

If we now wrap all this together and add a few lines, we get the code below. A function that will give you the path to the sd card, if it’s available. If there is no sd card you will get the emulated storage as a fallback.

 

And now have fun  and save everything you got to the sd card!

 

 

private static string GetAndroidExternalFilesDir()
{
     using (AndroidJavaClass unityPlayer = 
            new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
     {
          using (AndroidJavaObject context = 
                 unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
          {
               // Get all available external file directories (emulated and sdCards)
               AndroidJavaObject[] externalFilesDirectories = 
                                   context.Call<AndroidJavaObject[]>
                                   ("getExternalFilesDirs", (object)null);

               AndroidJavaObject emulated = null;
               AndroidJavaObject sdCard = null;

               for (int i = 0; i < externalFilesDirectories.Length; i++)
               {
                    AndroidJavaObject directory = externalFilesDirectories[i];
                    using (AndroidJavaClass environment = 
                           new AndroidJavaClass("android.os.Environment"))
                    {
                        // Check which one is the emulated and which the sdCard.
                        bool isRemovable = environment.CallStatic<bool>
                                          ("isExternalStorageRemovable", directory);
                        bool isEmulated = environment.CallStatic<bool>
                                          ("isExternalStorageEmulated", directory);
                        if (isEmulated)
                            emulated = directory;
                        else if (isRemovable && isEmulated == false)
                            sdCard = directory;
                    }
               }
               // Return the sdCard if available
               if (sdCard != null)
                    return sdCard.Call<string>("getAbsolutePath");
               else
                    return emulated.Call<string>("getAbsolutePath");
            }
      }
}