Access Unity StreamingAssets on Android

15,062

Solution 1

It may sound hard, but you really have to use the WWW class to access your files from StreamingAssets on Android.

And you have to keep a list of your filenames, to be able to access them, as there seems to be no way to use something like File.Exists or Directory.GetFiles.

Example:

WWW www = new WWW(source);
yield return www;

if (string.IsNullOrEmpty(www.error))
{
    // make sure the destination directory exists
    File.WriteAllBytes(destination, www.bytes); 
}
else
{
    // error handling
}

Your source files (base) path should look like this: jar:file://" + Application.dataPath + "!/assets/


From the docs:

Note that on Android, the files are contained within a compressed .jar file (which is essentially the same format as standard zip-compressed files). This means that if you do not use Unity’s WWW class to retrieve the file then you will need to use additional software to see inside the .jar archive and obtain the file.

Source: http://docs.unity3d.com/Manual/StreamingAssets.html

Solution 2

To solve this problem I created pre-compilation script which will run before the compilation; this script will list the names of the files you want to access later in text file

 #if UNITY_EDITOR
 using UnityEditor.Build;
 using UnityEditor;
 using System.IO;

 public class BM_AndroidBuildPrepartion : IPreprocessBuild
 {
     public int callbackOrder { get { return 0; } }
     public void OnPreprocessBuild(BuildTarget target, string path)
     {
         // Do the preprocessing here
         string[] fileEntries = Directory.GetFiles("Assets/Resources/Prefabs/alphabet", "*.prefab");
         System.IO.Directory.CreateDirectory("Assets/StreamingAssets/");
         using (StreamWriter sw = new StreamWriter("Assets/StreamingAssets/alphabet.txt", false))
         {

         foreach (string filename in fileEntries) {
                 sw.WriteLine(Path.GetFileNameWithoutExtension(filename));
         }

         }
     }
 }
 #endif

then I read the text file and you can access the files in same way but you have to put them inside your project in Assets/StreamingAssets/

#if UNITY_ANDROID
        string  path = "jar:file://" + Application.dataPath + "!/assets/alphabet.txt";
          WWW wwwfile = new WWW(path);
          while (!wwwfile.isDone) { }
          var filepath = string.Format("{0}/{1}", Application.persistentDataPath, "alphabet.t");
          File.WriteAllBytes(filepath, wwwfile.bytes);

          StreamReader wr = new StreamReader(filepath);
              string line;
              while ((line = wr.ReadLine()) != null)
              {
              //your code
              }
   #endif
Share:
15,062

Related videos on Youtube

Elbbard
Author by

Elbbard

Updated on June 04, 2022

Comments

  • Elbbard
    Elbbard almost 2 years

    I wrote some code to get all the folders inside a folder inside the StreamingAssets and then to get all the files in that folder. It works well on Windows but I can't get it to work on Android.

    Here is the code :

    foreach (string s in Directory.GetDirectories(model.path + "/Levels")) {
            GameObject el = (GameObject)Instantiate(listButton, Vector3.zero, Quaternion.identity);
            el.transform.SetParent(grid1.transform);
            string[] words = s.Split('/');
            #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
                                words = s.Split('\\');
            #endif
            el.GetComponentInChildren<Text>().text = words[words.Length - 1];
            el.GetComponent<Button>().onClick.AddListener(() => {
                MapSizeButtonClick(Int32.Parse(el.GetComponentInChildren<Text>().text));
            });
        }
    

    I had to add the #if UNITY_STANDALONE_WIN for windows because of the '\'.

    Then for the files inside that folder I wrote this :

    foreach (string s in Directory.GetFiles(model.path + "/Levels/" + model.mapSize + "/" + model.colorNumber)) {
            string[] words = s.Split('/');
            #if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
                        words = s.Split('\\');
            #endif
            words = words[words.Length - 1].Split('.');
            if (words[1] == "json") {
                GameObject el = (GameObject)Instantiate(listButton, Vector3.zero, Quaternion.identity);
                el.transform.SetParent(grid3.transform);
                el.GetComponentInChildren<Text>().text = words[0];
                el.GetComponent<Button>().onClick.AddListener(() => {
                    levelButtonClick(Int32.Parse(el.GetComponentInChildren<Text>().text));
                });
            }
        }
    

    I'm using UnityEngine.UI.Directory but I read that it can't be use on Android and I must use WWW instead. Something like this :

    string xmlSetUpPath = "jar:file://" + Application.dataPath + "!/assets/xml/xmlSetUp.xml";
    
            WWW book1WWW = new WWW(book1Path);
            yield return book1WWW;
            if(!File.Exists(Application.persistentDataPath + "/xml/book1.xml"))
            {
                File.WriteAllBytes(Application.persistentDataPath + "/xml/book1.xml", book1WWW.bytes); 
            }
    

    However I couldn't find anything like Directory.getFiles() or Directory.getFolder(). So I thought I could use WWW to check if the file exists (as the file names are 1.json, 2.json, 3.json) and iterate until a file doesn't exist.

    But I would also need to check if a folder exists for the 1st part of the script and I can't find how to check if a folder exists with WWW. (My folders names are also 1,2,3,4...)

    So how to check if a folder exists with WWW ?

    Or what else can I do ?

    UPDATE :

    I updated the code, now it looks like this :

    for (int i = 0; i < 100; i++) {
            if (Directory.Exists(Path.Combine(model.path, "Levels/" + i.ToString()))){
                GameObject el = (GameObject)Instantiate(listButton, Vector3.zero, Quaternion.identity);
                el.transform.SetParent(grid1.transform);
                el.GetComponentInChildren<Text>().text = i.ToString();
                el.GetComponent<Button>().onClick.AddListener(() => {
                    MapSizeButtonClick(Int32.Parse(el.GetComponentInChildren<Text>().text));
                });
            }
        }
    

    I use path.combine() and check every folder from 0 to 100. It works on Windows but it still doesn't work in Android.

    The path is set like this :

    #if UNITY_IPHONE
            path = Application.dataPath + "/Raw";
        #endif
    
        #if UNITY_ANDROID
            path = "jar:file://" + Application.dataPath + "!/assets";
        #endif
    
        #if UNITY_STANDALONE || UNITY_EDITOR
            path = Application.dataPath + "/StreamingAssets";
        #endif
    

    QUESTION UPDATE

    I made a script that generate the file paths. The paths are always the same : "FolderNumerotedFromYToX/FolderNumerotedFromYToX/FileNumerotedFrom1ToX.json". The generation works but the writing to JSON doesn't. It outputs "{}".

            // Generate level list
        List<KeyValuePair<int, List<KeyValuePair<int, List<int>>>>> l;
        l = new List<KeyValuePair<int, List<KeyValuePair<int, List<int>>>>>();
        for (int i = 1; i < 100; i++) {
            if (Directory.Exists(Path.Combine(Path.Combine(Application.dataPath, "StreamingAssets"), "Levels/" + i.ToString()))) {
                l.Add(new KeyValuePair<int, List<KeyValuePair<int, List<int>>>>(i, new List<KeyValuePair<int, List<int>>>()));
                for (int j = 1; j < 100; j++) {
                    if (Directory.Exists(Path.Combine(Path.Combine(Application.dataPath, "StreamingAssets"), "Levels/" + i + "/" + j))) {
                        l[l.Count - 1].Value.Add(new KeyValuePair<int, List<int>>(j, new List<int>()));
                        int k = 1;
                        while (true) {
                            if (File.Exists(Path.Combine(Path.Combine(Application.dataPath, "StreamingAssets"), "Levels/" + i + "/" + j + "/" + k + ".json"))) {
                                l[l.Count - 1].Value[l[l.Count - 1].Value.Count - 1].Value.Add(k);
                            }
                            else
                                break;
                            k++;
                        }
                    }
                }
            }
        }
        string ljson = JsonUtility.ToJson(l);
        var lsr = File.CreateText("Assets/StreamingAssets/levels.json");
        lsr.WriteLine(ljson);
        lsr.Close();
    

    Can you help me finding a way to store this to a file ?

    • LorDFaKeR
      LorDFaKeR about 8 years
      IMO you should use Path.Combine() to concatenate a valid path to a string. And just where did you read that you can't use "normal" File operations? File.Exists() should just work fine.
    • Elbbard
      Elbbard about 8 years
      I will try Path.Combine(), thanks ! I read it here forum.unity3d.com/threads/… and in other places, they say you must use WWW on Android. Maybe I got a little bit confused, File.Exists() should work but it's about Directory.GetFiles() and Directory.GetFolders().
    • Elbbard
      Elbbard about 8 years
      I'm getting tired searching... (more than 4 hours now) I found another code where they use Application.streamingAssets. snip2code.com/Snippet/14142/… . Should I use this ?
  • Elbbard
    Elbbard about 8 years
    I made a script which generate the list of the files (Added to the post). But now I don't know how to store it in a file. The JSON part doesn't work and juste writes "{}" to the file. Can you help me ?
  • d4Rk
    d4Rk about 8 years
    Are you able to load/read the files on Android now? That was the actual question, wasn't it? But ok.. do you create the JSON stuff during runtime? If so, you should not put them in Streaming Assets.. Use Application.persistentDataPath for such data.