Data Storage - How to read/write files

Hi everybody, greetings from Colombia!.

We are porting an iPad application to Android tablet, that use a lot write and read data from the disk(memory). On the iPad and Editor we get the cache path in this way:

public string GetCachePath ()
{
		
	        string cachePath="Noset";
			
		if (Application.isEditor) {
			return "cache/";
		}
		
		if(Application.platform == RuntimePlatform.IPhonePlayer)
		{
			//for iPhone
			//path should be <Application_Home>/Library/Caches
			//app data Path is /<Application_Home>/AppName.app/Data		
			string dataPath = Application.dataPath;
			cachePath = dataPath.Replace ("/Data", "");
			//find index of "/" in /AppName.app
			int index = cachePath.LastIndexOf ("/");
			cachePath = cachePath.Substring (0, cachePath.Length - (cachePath.Length - index));
			cachePath = cachePath + "/Library/Caches/";
		}
...

So, first thanks for the RC2 that fix Application.dataPath on Android just in time :stuck_out_tongue: but it´s seems that is not enough to write files, we are trying this:

	if(Application.platform == RuntimePlatform.Android)
		{
		//	Application.dataPath returns: /data/app/net.xorgames.mapp.apk/
		
                 // maybe the cache path should be this:
		// -> /data/<package_name>/files/     OR
		// /Android/data/<package_name>/cache/
			
			string dataPath = Application.dataPath;
			
			cachePath = dataPath.Replace(".apk", "/files/");
			cachePath = cachePath.Replace("app/", "");
			
		}
...

But doesn´t work according with Android SDK Documentation: 数据和文件存储概览  |  Android Developers , there are different locations to write a file, and each one is calling by a method that returns the FileStreaming ready to use and not a path.

We pretend to use internal or external(SD memory) storage.

Any info to solve this is welcome :slight_smile:

thanks!

Yeah, that won’t work. And with RC2 there is no built-in function to solve that either, unfortunately. What you need to get is either the Context.getCacheDir() or (which is way easier) the Environment.getExternalStorageDirectory().

So, these live in Java land, and we cant call directly to java but instead we need to go through a native layer.

As getExternalStorageDirectory is a static method it’s actually quite easy to write this code directly in native code (using JNI) instead of calling a java method and do it in java land.

Basically you need to setup a native plugin http://docwiki.unity3d.com/index.php?n=Main.Plugins#AndroidPlugins with a JNI_OnLoad function that does something like

jclass cls_Env = jni_env->FindClass("android/os/Environment");
jmethodID mid_getExtStorage = jni_env->GetStaticMethodID(cls_Env, "getExternalStorageDirectory",  "()Ljava/io/File;");
jobject obj_File = jni_env->CallStaticObjectMethod(cls_Env, mid_getExtStorage);

jclass cls_File = jni_env->FindClass("java/io/File");
jmethodID mid_getPath = jni_env->GetMethodID(cls_File, "getPath", "()Ljava/lang/String;");
jstring obj_Path = jni_env->CallObjectMethod(cls_Path, mid_getPath);
const char* path = jni_env->GetStringUTFChars(obj_Path);

// copy 'path' somewhere

jni_env->ReleaseStringUTFChars(obj_Path, path);

Ok, so there might be typo or two there, but this is more or less how you interact with Java objects from a native plugin.

We will of course solve this in a more elegant way in future releases.

EDIT: I accidentally used the non-static version of the JNI method functions. I.e. it should be GetStaticMethodID instead of GetMethodID, and CallStaticObjectMethod instead of CallObjectMethod (because the method we are trying to call is static).

1 Like

thank you Enrique!, i´m going to try this way and tell how it works :wink:

I’ve added two plugin examples to our online documentation.
One is a very simple how to create your first plugin type. The other shows how to call Java code (via a native code bridge) and interact with the Android OS. I think you may find the second particularly interesting as it shows how to retrieve the CacheDir(). (For use with RC3 and later)

Is all this still necessary just to get a path to a writable folder? Seems like it would have been added to Unity by now.

+3

Wouldn’t it make sense for ‘Application.dataPath’ to return a writable directory on all platforms - iOS, Android, Mac, PC, etc?

I started an idea on the wish-list, click below to vote it up:

http://feedback.unity3d.com/forums/15792-unity/suggestions/1275927-application-datapath-return-writable-directory-on-?ref=title

Hello, I’m testing the android output, thus i’m interest for the exemple. But when I try to access it, I have the page 403…
Can you help me, please?
Thanks

Is this still the only way to get a path to a folder we can write to? I agree with the earlier suggestion that Application.datapath should return a useable folder on all platforms. Couldn’t we at least have a few prebuilt plugins for things like this which are currently missing/broken? Surely it couldn’t take long if you know what you’re doing, but it’s reinventing the wheel for us all to do it independently.

EDIT: Well here’s some C++ code which apparently works. Now all I have to do is figure out the NDK, the JDK, how plugins work and I’m there.

   1. JNIEXPORT void JNICALL Java_com_jongwook_test_TestActivity_test(JNIEnv * env, jobject obj)  
   2. {  
   3.  jclass cls = env->GetObjectClass(obj);  
   4.  jmethodID getFilesDir = env->GetMethodID(cls, "getFilesDir", "()Ljava/io/File;");  
   5.  jobject dirobj = env->CallObjectMethod(obj,getFilesDir);  
   6.  jclass dir = env->GetObjectClass(dirobj);  
   7.  jmethodID getStoragePath =  
   8.                        env->GetMethodID(dir, "getAbsolutePath", "()Ljava/lang/String;");  
   9.  jstring path=(jstring)env->CallObjectMethod(dirobj, getStoragePath);  
  10.  const char *pathstr=env->GetStringUTFChars(path, 0);  
  11.  chdir(pathstr);  
  12.  env->ReleaseStringUTFChars(path, pathstr);  
  13. }

Would anyone be so kind as to modify the GetCacheDir example above to use GetFilesDir instead? I’ve tried. My inane ramblings on this forum alone demonstrate that I’ve tried. I can’t do it though. I can’t even understand what is there. It grabs a method called getActivityCacheDir but googling that function name turns up precisely zero, so where am I suppose to get this method name from?

EDIT: Oh never mind. I thought this circumvented all the SDK stuff, but I see now that it doesn’t. So disregard.

well, there’s an Application.persistentDataPath and Application.temporaryCachePath for that reason in Unity 3.2. No need to use any Java code.

Anyway, here’s a small project that saves and deletes the file using no native code. Enjoy.

496569–17508–$file-io-test.zip (47.9 KB)

Excellent. Thanks very much.

Is it still working correct or I need some changes?

I get error:
Assets/FileIO.cs(39,52): error CS0117: System.IO.File' does not contain a definition for CreateText’

Well, I can’t seem to get this to work with XML using C#. I’ve gotten it to work on the Mac, but when
I do it on the android I don’t know what goes wrong. Took a look at that zip and I seem to be doing the saving
and loading the same way and still it doesn’t work.

Thanks Wozik!!

How about getting Environment.SpecialFolder on Android?
I want to load some photo from SD Card, but it keep crashing when I call this line in c#:
string path = Environment.GetFolderPath(Environment.SpecialFolder.Personal)

edit:
When I use absolute path /sdcard/ to do other file operation and use www to load local texture on sd is ok,
Just Environment.GetFolderPath(Environment.SpecialFolder.Personal) keep crashing the player on android.
Is it made not to work on android by design?

Environment is a part of the .NET/Mono Framework and it usually reads out the Windows special folder structure. I don’t think there is such a structure for android, especially since Android basically have a per-app folder structure.

Every android app has a data folder in /data/data/com.yourcompany.yourgame.packagename/, where you have subfolders (files, databases, cache, pictures) etc. Then you also have a similar structure on the SD-card, just that the path is /sdcard/Android/com.yourcompany.yourgame.packagename/).

But the only secure way to get the right folder is by using the getExternalFilesDir, if you want to save content on the SD Card or getFilesDir for internal storage via JNI.

If you take a look at the Android Documentation, with getExternalFilesDir you get folder path to androids “specialfolders”

So accessing photo folder on android need to use JNI and call getExternalStoragePublicDirectory, and no generic way to do it?

what if you want to include (and access) an asset file (e.g. a sqlite database) within the apk itself?

Some additional info about reading/writing files in Unity can be found here: File I/O in Unity3D - PreviewLabs

This may be a newcomer’s question, but why not just write the data using PlayerPrefs?