Search Unity

Data Storage - How to read/write files

Discussion in 'Android' started by SebastianVargas, Sep 23, 2010.

  1. SebastianVargas

    SebastianVargas

    Joined:
    Jun 29, 2009
    Posts:
    18
    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:

    Code (csharp):
    1.  
    2. public string GetCachePath ()
    3. {
    4.        
    5.             string cachePath="Noset";
    6.            
    7.         if (Application.isEditor) {
    8.             return "cache/";
    9.         }
    10.        
    11.         if(Application.platform == RuntimePlatform.IPhonePlayer)
    12.         {
    13.             //for iPhone
    14.             //path should be <Application_Home>/Library/Caches
    15.             //app data Path is /<Application_Home>/AppName.app/Data    
    16.             string dataPath = Application.dataPath;
    17.             cachePath = dataPath.Replace ("/Data", "");
    18.             //find index of "/" in /AppName.app
    19.             int index = cachePath.LastIndexOf ("/");
    20.             cachePath = cachePath.Substring (0, cachePath.Length - (cachePath.Length - index));
    21.             cachePath = cachePath + "/Library/Caches/";
    22.         }
    23. ...
    24.  

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



    Code (csharp):
    1.     if(Application.platform == RuntimePlatform.Android)
    2.         {
    3.         //  Application.dataPath returns: /data/app/net.xorgames.mapp.apk/
    4.        
    5.                  // maybe the cache path should be this:
    6.         // -> /data/<package_name>/files/     OR
    7.         // /Android/data/<package_name>/cache/
    8.            
    9.             string dataPath = Application.dataPath;
    10.            
    11.             cachePath = dataPath.Replace(".apk", "/files/");
    12.             cachePath = cachePath.Replace("app/", "");
    13.            
    14.         }
    15. ...

    But doesn´t work according with Android SDK Documentation: http://developer.android.com/guide/topics/data/data-storage.html , 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 :)

    thanks!
     
  2. eriQue

    eriQue

    Unity Technologies

    Joined:
    May 25, 2010
    Posts:
    595
    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

    Code (csharp):
    1.  
    2. jclass cls_Env = jni_env->FindClass("android/os/Environment");
    3. jmethodID mid_getExtStorage = jni_env->GetStaticMethodID(cls_Env, "getExternalStorageDirectory",  "()Ljava/io/File;");
    4. jobject obj_File = jni_env->CallStaticObjectMethod(cls_Env, mid_getExtStorage);
    5.  
    6. jclass cls_File = jni_env->FindClass("java/io/File");
    7. jmethodID mid_getPath = jni_env->GetMethodID(cls_File, "getPath", "()Ljava/lang/String;");
    8. jstring obj_Path = jni_env->CallObjectMethod(cls_Path, mid_getPath);
    9. const char* path = jni_env->GetStringUTFChars(obj_Path);
    10.  
    11. // copy 'path' somewhere
    12.  
    13. jni_env->ReleaseStringUTFChars(obj_Path, path);
    14.  
    15.  
    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).
     
    neginfinity likes this.
  3. SebastianVargas

    SebastianVargas

    Joined:
    Jun 29, 2009
    Posts:
    18
    thank you Enrique!, i´m going to try this way and tell how it works :wink:
     
  4. eriQue

    eriQue

    Unity Technologies

    Joined:
    May 25, 2010
    Posts:
    595
    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)
     
  5. AmazingRuss

    AmazingRuss

    Joined:
    May 25, 2008
    Posts:
    933
    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.
     
  6. ezone

    ezone

    Joined:
    Mar 28, 2008
    Posts:
    331
  7. Estalex

    Estalex

    Joined:
    Jul 13, 2009
    Posts:
    4
    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
     
  8. Phil™

    Phil™

    Joined:
    Jan 2, 2011
    Posts:
    73
    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.

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

    Phil™

    Joined:
    Jan 2, 2011
    Posts:
    73
    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.
     
    Last edited: Feb 7, 2011
  10. Wozik

    Wozik

    Joined:
    Apr 10, 2009
    Posts:
    662
    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.
     

    Attached Files:

  11. Phil™

    Phil™

    Joined:
    Jan 2, 2011
    Posts:
    73
    Excellent. Thanks very much.
     
  12. mRadek

    mRadek

    Joined:
    Feb 17, 2009
    Posts:
    25
    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'
     
  13. mtewks

    mtewks

    Joined:
    Jun 10, 2011
    Posts:
    82
    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.
     
  14. MarkPixel

    MarkPixel

    Joined:
    Apr 15, 2010
    Posts:
    39
    Thanks Wozik!!
     
  15. lyh1

    lyh1

    Joined:
    Jun 30, 2010
    Posts:
    92
    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?
     
    Last edited: Jul 25, 2011
  16. Tseng

    Tseng

    Joined:
    Nov 29, 2010
    Posts:
    1,217
    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"
    http://developer.android.com/refere...per.html#getExternalFilesDir(java.lang.String)
     
  17. lyh1

    lyh1

    Joined:
    Jun 30, 2010
    Posts:
    92
    So accessing photo folder on android need to use JNI and call getExternalStoragePublicDirectory, and no generic way to do it?
     
  18. andresp

    andresp

    Joined:
    Aug 12, 2011
    Posts:
    38
    what if you want to include (and access) an asset file (e.g. a sqlite database) within the apk itself?
     
  19. bernardfrancois

    bernardfrancois

    Joined:
    Oct 29, 2009
    Posts:
    373
  20. ina

    ina

    Joined:
    Nov 15, 2010
    Posts:
    1,085
    This may be a newcomer's question, but why not just write the data using PlayerPrefs?
     
  21. cupsster

    cupsster

    Joined:
    Apr 14, 2009
    Posts:
    363
    there is hard limit of how much data you can store using prefs..
     
  22. Charles L

    Charles L

    Joined:
    Jul 30, 2010
    Posts:
    128
    "there is hard limit of how much data you can store using prefs.."

    I had a hard time!! to make a storing of 209 products with setInt and getInt, i needed to make 12 functions, to win to make this working.
    On starts i had only 1 function. and the app have closed. and after divided it in 4 functions, and after 12.
    To finaly got the android be ok.:)
     
    Last edited: Nov 28, 2011
  23. Clark990

    Clark990

    Joined:
    Jul 11, 2012
    Posts:
    20
    Yes its better to do it the right way, don'rt know about any generic way to do this.
     
  24. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,822
    Anyone know some encryption methods in C# that work on Android?
    Seems the System.Cryptography namespace is not welcome.
     
  25. lmbarns

    lmbarns

    Joined:
    Jul 14, 2011
    Posts:
    1,628
    If you're using a DB you might get away with md5 hash like in the server side high scores on the wiki?
    There's even a MD5 class: http://wiki.unity3d.com/index.php?title=MD5
     
  26. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,822
    This was simply for encrypting PlayerPrefs. But it seems Android doesn't like anything to do with System.Security.Cryptopgraphy
     
  27. Clas

    Clas

    Joined:
    Mar 27, 2013
    Posts:
    6
    You can get your Android apps internal path directly from Unity without a plugin. Similar code can be used to get any other path. I spent 2 days on this :p. First I made my own Java plugin, but as I do not like to extend my current Activity or Application I ran into problems. Finally I pieced together how to do it directly from Unity with the following code:

    Code (CSharp):
    1. string path = "";
    2. #if UNITY_ANDROID &&  !UNITY_EDITOR
    3. try {
    4.          IntPtr obj_context = AndroidJNI.FindClass("android/content/ContextWrapper");
    5.          IntPtr method_getFilesDir = AndroidJNIHelper.GetMethodID(obj_context, "getFilesDir", "()Ljava/io/File;");
    6.  
    7.          using (AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
    8.             using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
    9.                IntPtr file = AndroidJNI.CallObjectMethod(obj_Activity.GetRawObject(), method_getFilesDir, new jvalue[0]);
    10.                IntPtr obj_file = AndroidJNI.FindClass("java/io/File");
    11.                IntPtr method_getAbsolutePath = AndroidJNIHelper.GetMethodID(obj_file, "getAbsolutePath", "()Ljava/lang/String;");  
    12.                                
    13.                path = AndroidJNI.CallStringMethod(file, method_getAbsolutePath, new jvalue[0]);                    
    14.  
    15.                if(path != null) {
    16.                   Debug.Log("Got internal path: " + path);
    17.                }
    18.                else {
    19.                   Debug.Log("Using fallback path");
    20.                   path = "/data/data/*** YOUR PACKAGE NAME ***/files";
    21.                }
    22.             }
    23.          }
    24.       }
    25.       catch(Exception e) {
    26.          Debug.Log(e.ToString());
    27.       }
    28. #else
    29.       path = Application.persistentDataPath;
    30. #endif
    I hope it will help some devs without Java knowledge (like myself :)) to save some time and headache.
     
    luunn_54 and neginfinity like this.