前言

发现 **Application.temporaryCachePath 和 Application.persistentDataPath 返回空字符串 **。便花时间认真研究了一下 Unity3D 的路径问题。

正文

我们常用的是以下四个路径:

Application.dataPath
Application.streamingAssetsPath
Application.persistentDataPath
Application.temporaryCachePath

根据测试,详细情况如下:


iOS:

Application.dataPath            /var/containers/Bundle/Application/app sandbox/xxx.app/Data
Application.streamingAssetsPath /var/containers/Bundle/Application/app sandbox/test.app/Data/Raw
Application.temporaryCachePath /var/mobile/Containers/Data/Application/app sandbox/Library/Caches
Application.persistentDataPath  /var/mobile/Containers/Data/Application/app sandbox/Documents

iOS 和 Mac OS X 不同于 Windows,app 都是在一个沙盒空间中运行,每个 app 也有一个独立的数据存储空间,各 app 彼此不能互相访问、打扰。

dataPath 是 app 程序包安装路径,app 本身就在这里,此目录是只读的。streamingAssetsPath 是 dataPath 下的 Raw 目录。

app 的独立数据存储目录下有三个文件夹:Documents,Library 和 tmp。
Documents 目录,这个目录用于存储需要长期保存的数据,比如我们的热更新内容就写在这里。需要注意的是,iCloud 会自动备份此目录,如果此目录下写入的内容较多,审核的可能会被苹果拒掉。

Library 目录,这个目录下有两个子目录,Caches 和 Preferences。
    Caches 是一个相对临时的目录,适合存放下载缓存的临时文件,空间不足时可能会被系统清除,Application.temporaryCachePath 返回的就是此路径。我把热更新的临时文件写在这里,等一个版本的所有内容更新完全后,再把内容转移到 Documents 目录。
    Preferences 用于应用存储偏好设置,用 NSUserDefaults 读取或设置。

tmp 目录,临时目录,存放应用运行时临时使用的数据。
需要注意的是,以上无论临时、缓存或者普通目录,如果不需要的数据,都请删除。不要占用用户的存储空间,像微信就是坏榜样。

下面是各路径对应的 OC 访问方法
app 安装路径: [[NSBundle mainBundle] resourcePath]
app 数据沙盒存储根目录: NSHomeDirectory()
Documents: NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
Library:     NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)
Caches:     NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)
tmp:        NSTemporaryDirectory()


Android:

Application.dataPath            /data/app/package name-1/base.apk
Application.streamingAssetsPath jar:file:///data/app/package name-1/base.apk!/assets
Application.temporaryCachePath /storage/emulated/0/Android/data/package name/cache
Application.persistentDataPath   /storage/emulated/0/Android/data/package name/files

看 Android 上的路径,跟 iOS 有点类似,简单说一下。Android 的几个目录是 apk 程序包、内存存储 (InternalStorage) 和外部存储 (ExternalStorage) 目录。

**apk 程序包 ** 目录: apk 的安装路径,/data/app/package name-n/base.apk,dataPath 就是返回此目录。

** 内部存储 ** 目录: /data/data/package name-n/,用户自己或其它 app 都不能访问该目录。打开会发现里面有 4 个目录(需要 root)
    cache 缓存目录,类似于 iOS 的 Cache 目录
    databases 数据库文件目录
    files 类似于 iOS 的 Documents 目录
    shared_prefs 类似于 iOS 的 Preferences 目录,用于存放常用设置,比如 Unity3D 的 PlayerPrefs 就存放于此

** 外部存储 ** 目录: 在内置或外插的 sd 上,用户或其它 app 都可以访问,外部存储目录又分私有和公有目录。
    ** 公有目录 ** 是像 DCIM、Music、Movies、Download 这样系统创建的公共目录,当然你也可以像微信那样直接在 sd 卡根目录创建一个文件夹。好处嘛,就是卸载 app 数据依旧存在。

    ** 私有目录 ** 在 / storage/emulated/n/Android/data/package name/,打开可以看到里面有两个文件夹 cache 和 files。为什么跟内部存储目录重复了?这是为了更大的存储空间,以防内存存储空间较小。推荐把不需要隐私的、较大的数据存在这里,而需要隐私的或较小的数据存在内部存储空间。

下面是各路径对应的 Java 访问方法:
apk 包内: AssetManager.open(String filename)
内部存储: context.getFilesDir().getPath() or context.getCacheDir().getPath()
外部存储: context.getExternalFilesDir(null).getPath() or context.getExternalCacheDir().getPath()

理解了 Android 存储的原理,最后来说说开头提到的 bug,Application.temporaryCachePath/persistentDataPath 返回空字符串。这其实因为权限的原因,app 没有声明 ** 访问外部存储空间的权限 **,但是 Application.temporaryCachePath/ ApplicationpersistentDataPath 却想返回外部存储的路径。这是 Unity3D 的 bug,没有权限本应该抛出一个异常或者错误,让开发者知道原因。

经反复测试发现,有【外置 SD 卡】的设备上,如果声明读 / 写外部存储设备的权限,会返回外部存储路径,不声明则会返回内部存储路径,这样不会有问题。而在【无外置 SD 卡】的设备上,不管是否声明读 / 写外部存储设备的权限,Application.temporaryCachePath/persistentDataPath 都返回外部存储路径,但是又没有权限,就可能会导致返回 null 了,之所以说可能是因为这个 bug 不是必现,如果出现了设备重启之后就好了,怀疑是 linux 设备 mount 问题。但是出了问题,我们不能跟用户说你重启一下手机就好了。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Windows:

Application.dataPath:            应用的 appname_Data/
Application.streamingAssetsPath: 应用的 appname_Data/StreamingAssets
Application.temporaryCachePath: C:\Users\username\AppData\Local\Temp\company name\product name
Application.persistentDataPath:   C:\Users\username\AppData\LocalLow\company name\product name


PlayerPrefs 路径(补充)

Android: /data/data/pkg-name/shared_prefs/pkg-name.v2.playerprefs.xml
iOS:/Library/Preferences/[bundle identifier].plist
Windows:HKEY_CURRENT_USER/Software/CompanyName/ProductName
Mac:~/Library/Preferences/com.CompanyName.ProductName.plist


参考:

  1. iOS Data Storage Guidelines
  2. Android API: Storage Options
  3. 彻底理解 Android 中的内部存储与外部存储
  4. Unity - Scripting API:PlayerPrefs
  5. Unity3D 各平台 Application.xxxPath 的路径

to be continued…