如何正确的在Android中存储特定应用文件
2013-10-17 13:20 三戒1993 阅读(117) 评论(0) 编辑 收藏 举报Christophe Versieux (Waza_be)发表了一篇rant about android developers’ bad habit to store files directly on the root of the sd card的文章。我非常赞同这篇文章的观点。在SD卡的根目录下直接创建特定应用的目录不是一个好的做法。如果你装了非常多的应用,那么SD卡的根目录将会很快的变得凌乱起来(译者注:同感,最近重新使用Android手机,发现没多久SD卡就一团糟,就是很多主流的应用都在乱建文件夹,这也是为什么翻译这篇文章的原因)。
有一条评论也提到了大部分教程都没有提及怎么来创建特定应用目录,那么让我用这个小教程告诉大家怎么样做才是对的。
特定应用的文件 vs. 不依赖应用的文件
如果你要存储文件,那么一般来说有两种类型的文件:
- 不依赖应用的数据
- 特定应用的数据
在下面的章节中我会用更多的细节来覆盖这两种类型。但总而言之我会用以下的方式来区分这两种类型:特定应用的文件是那些仅在应用安装期间有用的数据(比如带所有权的电子书);另一方面,不依赖应用的文件就是那些不管是不是某个特定应用创建的,用户都会关心的那些文件(比如照片)。
不依赖应用的文件
这种类型的数据是那些就算你的应用被卸载了,你的用户还很可能关心的东西。比如拍摄的照片,处理或者勾画过的图像,编辑过的代码文件,购买过的音频文件等等。
Android为大部分这些类型的数据提供了特定的目录。Android提供的这种目录完整的列表可以在Environment类的文档中看到。这些字段全部都以“DIRECTORY”开头。比如DIRECTORY_MUSIC或者DIRECTORY_PICTURES。
这些文件总是要被存储在SD卡中(在Google Nexus line这类没有SD卡槽的设备中会被存储在等同的分区)。这样做的原因可能是这些文件相当大,也可能是它们需要全局可读,也可能是它们不能存储在一旦你的应用被卸载就被清除的目录里面。我会在后面的章节用更多的细节来覆盖外部存储。
你可以调用Environment类中的getExternalStorageDirectory()
来访问SD卡根目录。
你也可以用getExternalStoragePublicDirectory(String
type)
来直接获取任意一个支持类型的File
对象:
从这里开始就是一般的Java IO API了。
特定应用的文件
这种类型的文件是用于存储那些只有该特定应用可以或者应该用的数据。这可以是一些具有所有权的文件比如电子书,对普通媒体播放器不可用的媒体文件(比如CD封面的缩略图),下载的杂志,数据库文件,设置偏好等等。
特定应用的文件可以被存储在内部或者外部(SD卡上),Android的API会帮你找到适当的目录。
Android清理机制对于那些遵循一定命名规范的特定应用目录来说是很好的。当用户卸载你的应用的时候Android会帮你删除掉这些目录。这样Android可以清除掉不必要的目录而用户也不必在任何卸载操作之后进行手动清理。
内部存储与外部存储
你应该了解任何应用都有两种特定应用目录,内部的和外部的,前者你可以用来存储私有的文件。外部存储是指Android设备的SD卡或者是那些不提供SD卡槽的设备(比如Nexus line)上同等的分区。
内部存储空间大小有限
特别对大文件来说你更应该选择外部存储。这样做是因为内部存储空间可能会非常有限,这取决于你用户的设备。我的旧LG Optimus One就是一个极端例子,它只有300M的内部存储空间。但使用16GB的SD卡我就有了很多的外部存储空间。尽管对于内部存储来说这台机器是最差的例子之一,但还有很多其他的设备也是只有非常小的内部存储空间。不是所有人都使用高端手机的。
访问外部存储的权限
你需要权限来访问外部存储。在你的manifest文件中添加以下权限:
相对于内部存储,需要声明权限是使用外部存储的一个小缺陷。有些用户可能会对此感到厌恶,尤其是它被添加到一个原本已经很长的权限列表里。但如果你在应用描述里解释一下应该问题不大。
注意:在Jelly Bean之前读SD卡文件不需要权限。也就是说如果你的build target低于API level 16的话你可以去掉这个权限。
外部存储可能不可用
使用外部存储最大的问题是当你需要它的时候它可能没有被挂载。很明显SD卡被弹出以及把你的设备挂载到计算机上进行文件访问的时候就是这种情况。因此,你总要检查外部存储是否可用:
有些时候外部存储可能以read-only的模式挂载。如果你只需要读取数据那么下面的检查方式可能会更适合你:
这是因为Environment.MEDIA_MOUNTED_READ_ONLY
这个final字段的值是“mounted_ro”。实际上我并不喜欢final字段值的这段代码。我的观点是它应该写的更好些,Google应该选择使用整数,这样我们就可以用这些final字段作为位掩码来检测SD卡状态。
特定应用的内部存储目录
Android为你的应用创建了一个私有的目录。你的shared preferences,SQLite databases,native libraries或者cache文件都会放在这里。
所有特定应用的文件都会在这样命名的文件夹里
在这个文件夹里会有一些常见的子目录-这取决于你应用的需要:
- databases – 存放SQLite databases
- shared_prefs – 存放你的preferences
- cache – 存放缓存文件和数据
- lib – 存放native libraries
- files – 存放不适合放在其他目录的文件
Context类提供了一些你可以用来创建新目录,打开输入流等等的方法。下面的表格列举了这些方法:
辅助你使用内部存储的方法
Method
When to use
deleteFile(String name)
Deletes the file with the given name
fileList()
Returns a list of files
getDir(String name, int mode)
Returns a file object to this directory. If the directory doesn’t exist yet, it gets created.
getFilesDir()
Returns a File object pointing to the files directory
openFileInput(String name)
Opens an InputStream object to the file with the given name
openFileOutput(String name, int mode)
Opens an OutputStream object to the file with the given name. The file gets created if it does not exists
一些方法使用一个mode参数。这可以是一下Context类中常量的任意一个:
- MODE_APPEND
- MODE_PRIVATE
- MODE_WORLD_READABLE
- MODE_WORLD_WRITEABLE
这些是int值,你可以使用(“|”)操作符来组合它们-比如追加一个全局可写的文件:
特定应用的外部存储目录
这就是Waza_be’s咆哮的原因了-因为太多的应用忽略了处理外部存储中特定应用的目录的正确做法。
所有外部的特定应用文件应该被存储在这样命名的文件夹下
要注意的是我用了一个相对的路径。这个路径是相对于SD卡根目录的。SD卡挂载的路径约定,会根据Android的版本有所变化。
使用API调用替代硬编码值一直以来都是一个良好的编程实践,以前SD卡挂载点有过变化的这样一个事实应该让你在硬编码上更加谨慎。
对于外部文件现在只有一个方法供你使用:
如果你把null值作为参数传递到这个方法里,返回的File对象会指向files目录(译者注:Android/data/your.package.name/files/
)。如果你添加了Environment类中directory常量中的任意一个,你会得到一个指向你files目录下子目录的File对象。如果该目录还不存在,Android会帮你创建好。如果外部存储没有被挂载,该方法返回null。
注意:这个方法是在API Level 8(也就是Froyo或者说Android 2.2)之后引入的。在下一节中我会简要触及你跟更旧的设备打交道时会碰到的问题。
更旧的设备
依然有些你想支持的设备使用更旧的Android版本。在这种情况下使用上面提到的命名规范依然是个不错的主意。
唉,虽然这些旧版本没有getExternalFilesDir(String type)
方法,应用卸载之后Android也不会帮你清理掉这些目录。但使用同样的命名规范依然可以避免SD卡根目录下堆满了太多刺眼的目录。
缓存
很多时候你会需要缓存从网络上下载下来的数据或者你应用创建的数据。Android允许你使用内部存储以及外部存储空间来进行缓存。但使用外部存储可能会有些风险,因为当你需要的时候你的这些缓存文件可能不可用。
Context对象提供了两个方法来获取File对象,一个指向内部缓存目录,一个指向外部缓存目录:
- getCacheDir()
- getExternalCacheDir()
你必须自己来控制缓存的大小。你的应用被卸载的时候Android会把这两个目录都删除掉,但从另一方面来说,你可以自行决定是否删除不再需要的缓存文件。
如果Android运行时内部存储过低它会先清理掉一些缓存文件,但API明确地说明你不应该依赖Android系统来为你做清理。
对于外部的内存文件Android就完全不关心了。就算外部存储空间已经满了,也不会有任何缓存文件被删除。
文件夹的命名
目录(译者注:外部存储的目录)的官方命名规范包含你应用的包名。Christophe Versieux (Waza_be)提到他自己习惯使用应用名来替代包名作为目录名,原因是用户会更熟悉应用的名字而不是包名。
尽管熟悉度是应该被考虑,但我却不赞同这种做法。首先API调用是使用包名,那为什么不用呢。只有用这个方法你在安全上面才有依靠。第二就是Android只会清理用包名命名的目录(译者注:Android/data/下的目录)。最后一个就是你可能会在应用名不是必须唯一这件事上受到打击。这种情况很可能会导致你目录中的内容会跟其他应用的意图相冲突。
了解“.nomedia”-开关
Android的MediaScanner经常扫描SD卡中的媒体文件并把它们添加到公共的媒体文件列表中。因此图片会呈现在Gallery应用中,音乐文件会呈现在音频播放器中。
但并不是所有时候你都想要这样子做。有些时候那些文件应该只有你的应用能够呈现。这时候“.nomedia”就起作用了。如果一个目录包含一个叫“.nomedia”的文件,MediaScanner会跳过这个目录,因此该目录里的所有媒体文件都不会在公共媒体列表里看到。
这也是使用标准的特定应用目录的另外一个原因。data目录里包含“.nomedia”文件(译者注:Android系统会在Android/data/目录下自动为你创建“.nomedia”文件),这样所有你添加到特定应用目录中的媒体文件都不会显示在公共媒体列表中(译者注:不会被MediaScanner扫描,也就不会存储到MediaProvider中,这也就是“.nomedia”开关的意思)。
经验
在这个教程里你了解到了特定应用文件以及不依赖应用的文件的不同点以及在Android中如何应用这个知识点。
同样的你也看到了在Android中应该怎么使用特定应用文件,以及内部存储和外部存储如何平衡。
在接下来的一篇文章中我会来展示如何添加特定应用文件到相应的content providers中,以便让这些文件即时的展示在公共媒体列表中。敬请期待。