这是一个我花了至少五天的空闲时间才解决的问题,网上找到的博客大多对这一过程介绍得并不完整甚至有些存在错误。在此,谨以此博客作为开发经验的记录,同时也自立门户发表一篇教程式的笔记。
Android 平台的文件夹结构和路径访问方式是与 PC 端存在差异的,这也导致了对于开发和调试方面的不便。主要的差异表现在 Application.streamingAssets
和 Application.persistenDataPath
等路径 API 的返回结果上。
同时,对文件的操作,我们在较新版本的 Unity3D 中使用的 API 是 UnityWebRequest
类。
以上就是本篇博客将会讨论的主要内容。
两个重要的文件夹
这里引用一张表格帮助读者进行梳理:
上表已经展示了部分文件夹的读写权限,下面我们对其中两个重要的文件夹的应用场景作一些简单介绍。
Application.streamingAssets
这个成员储存着该项目的 StreamingAssets/
文件夹对应在 Android 平台上的位置,应当注意的是,StreamingAssets/
文件夹一般是需要开发者自己在 Assets/
文件夹下创建的。
这是一个只读的文件夹,并且存放在其中的文件不会进行任何处理(包括导入设置中的转码和压缩),所以我们在任何情况下都只推荐将那些需要进行文件操作的资源放入其中。
通常情况下,我们对于此文件夹下的内容采取的访问方式是先将文件复制到 Application.persistentDataPath
下,然后再对新产生的这部分文件进行读写操作。
Application.persistentDataPath
这个地址被称为持久化数据路径,它是不需要开发者自行创建的,当用户将 *.apk
包安装到设备上时,它就被自动创建到用户的系统目录中了。
它之所以被称为“持久化”,是因为其中的数据不会因为程序的退出而被擦除,也就是说它是“有记忆的”。而且更重要的是,它可读可写。这也是为什么我们一般会将用户的本地数据存放在此路径下。
正因为它是被自动创建的,所以在程序刚刚被解压安装时,它几乎是空的(说几乎是因为其中还包含了一些 unity 自行创建的预置文件)。此时,我们就首先需要通过文件操作的方式去在此文件夹下创建我们需要的文件,换言之,此文件下的几乎所有文件都是由开发者亲手创建的,管理起来非常容易(当然这也取决于开发者是否安排了合理的文件结构)。
UnityWebRequest
这个类被用来通过一个指定的 url 获取资源文件,这个 url 当然既可以来自互联网,也可以来自本地。我们正是通过这个 API 来获取需要的数据。这个 API 的具体使用方法可以参照 GitHub 中文文档。
在这里,我们用获取图片的例子对它的基本用法做一个简单的介绍:
private IEnumerator GetImage(string _path) {
using (UnityWebRequest UWR_file = UnityWebRequestTexture.GetTexture("file://" + _path) {
yield return UWR_file.SendWebRequest();
if (UWR_file.isError) yield break;
Texture2D _texture = DownloadHandlerTexture.GetContent(UWR_file);
_image = Sprite.Create(_texture, new Rect(0, 0, _texture.width, _texture.height), Vector2.zero);
}
yield break;
}
这是一个协程,用来从给定的文件路径 _path
处获取纹理信息并在内存中创建一个 Sprite
进行储存。由于从硬盘中或互联网上申请读取信息是需要一段相对较长的时间的,所以我们通过协程来异步处理。
下面我们逐行进行讲解:
- 我们在第 2 行创建了一个
UnityWebRequest
对象,并将其要获取的文件路径指定为"file://" + _path
,这里的file://
前缀是否需要添加由其所运行的平台决定,我们本篇博客讨论的是在 Android 平台环境下进行的开发,所以添加了这个前缀。 - 我们在第 3 行通过
UWR_file.SendWebRequest()
发送了读取请求,迭代器将在这个请求结束后继续运行。 - 第 5 行是一个判断,假如读取过程中发生了任何问题,我们就立刻返回,避免进一步出错。
- 我们在第 7 行将
UWR_file
获取到的信息转换为Texture2D
并保存在内存中。 - 我们在第 8 行由刚刚保存的
Texture2D
创建了一个Sprite
并保存在先前创建好的Sprite _image
中,至此,目的达成,返回。
新版的 UnityWebRequest
代替了旧版的 WWW
,现在后者已弃用,我们可以通过上面展示的流程来获取各式各样的文件。
在 Android 平台加载 StreamingAssets 中的纹理
首先说一下基本的流程,我们需要将 StreamingAssets/
中的文件复制到 Application.persistentDataPath
中,然后再对复制产生的文件其进行读写。
下面我们逐步进行讲解。
复制文件
public void CopyFileToPersistentDataPath(string _pth, string fileName)
{
string per_path = Application.persistentDataPath;
string str_path = Application.streamingAssetsPath;
StartCoroutine(Copy(str_path + _pth, per_path + _pth, fileName));
return;
}
我们首先通过上面的第 3 行至第 5 行获取源路径和目标路径,使用 Application.persistentDataPath
和 Application.streamingAssetsPath
可以返回对应的文件夹地址(这在任意平台下都能返回正确的结果),然后在这个地址后面加上资源在该文件夹下的相对路径即资源的绝对路径。
之后我们在第 5 行开启了一个协程对文件进行复制,具体的复制函数如下:
// 将文件 _name 从 _frm 复制到 _to
private IEnumerator Copy(string _frm, string _to, string _name)
{
// 若 persistentDataPath 下已包含该文件,返回
if (File.Exists(_to + _name)) yield break;
Directory.CreateDirectory(_to);
UnityWebRequest UWR_file = UnityWebRequest.Get(_frm + _name);
yield return UWR_file.SendWebRequest();
if (UWR_file.error != null) yield break;
if (UWR_file.isDone)
{
string fullName = _to + _name;
FileStream newFile = File.Create(fullName);
byte[] _data = UWR_file.downloadHandler.data;
newFile.Write(_data, 0, _data.Length);
newFile.Flush();
newFile.Close();
}
UWR_file.Dispose();
yield break;
}
我们依旧逐行讲解:
- 我们看到,第 5 行出现了
File.Exists(string)
这个函数,由于persistentDataPath
具有可读可写的性质,所以它是有效的;**但在StreamingAssests/
文件夹内,这个函数则是无效的,它的返回值总是false
。**不仅如此,File
类、Document
类、Directory
类等文件操作类在StreamingAssests/
下都是无效的。 - 第 6 行,新建路径,注意,当已经路径存在时不会新建。
- 第 7 行,由于我们这里不需要对文件的内容进行解析,所以不需要区分资源类型,直接用
Get(string)
这个函数获取即可。 - 第 15 行,实例化一个
FileStream
对象,用来操作新建的空文件。 - 第 17 行至第 18 行,将
UnityWebRequest
获取的文件内容逐字节原封不动地写入这个空文件。 - 最后将
FileStream
对象、UnityWebRequest
对象全部清除释放。
至此我们就完成了资源从 StreamingAssets/
文件夹到 Application.persistentDataPath
路径的转移。
加载文件
实际上当图片文件被复制到 Application.persistentDataPath
路径下后,我们就具有了对其进行读写操作的权限,此时各种文件操作函数都是可用的。
我们用上面提及的 UnityWebRequest
进行资源加载即可,这边只要注意一点,使用 UnityWebRequest
是需要在前面添加 "file://"
协议的,而使用其他文件操作函数不需要。