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对象:

1
2
Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_MOVIES);

从这里开始就是一般的Java IO API了。

特定应用的文件

这种类型的文件是用于存储那些只有该特定应用可以或者应该用的数据。这可以是一些具有所有权的文件比如电子书,对普通媒体播放器不可用的媒体文件(比如CD封面的缩略图),下载的杂志,数据库文件,设置偏好等等。

特定应用的文件可以被存储在内部或者外部(SD卡上),Android的API会帮你找到适当的目录。

Android清理机制对于那些遵循一定命名规范的特定应用目录来说是很好的。当用户卸载你的应用的时候Android会帮你删除掉这些目录。这样Android可以清除掉不必要的目录而用户也不必在任何卸载操作之后进行手动清理。

内部存储与外部存储

你应该了解任何应用都有两种特定应用目录,内部的和外部的,前者你可以用来存储私有的文件。外部存储是指Android设备的SD卡或者是那些不提供SD卡槽的设备(比如Nexus line)上同等的分区。

内部存储空间大小有限

特别对大文件来说你更应该选择外部存储。这样做是因为内部存储空间可能会非常有限,这取决于你用户的设备。我的旧LG Optimus One就是一个极端例子,它只有300M的内部存储空间。但使用16GB的SD卡我就有了很多的外部存储空间。尽管对于内部存储来说这台机器是最差的例子之一,但还有很多其他的设备也是只有非常小的内部存储空间。不是所有人都使用高端手机的。

访问外部存储的权限

你需要权限来访问外部存储。在你的manifest文件中添加以下权限:

1
2
3
4
    <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />

相对于内部存储,需要声明权限是使用外部存储的一个小缺陷。有些用户可能会对此感到厌恶,尤其是它被添加到一个原本已经很长的权限列表里。但如果你在应用描述里解释一下应该问题不大。

注意:在Jelly Bean之前读SD卡文件不需要权限。也就是说如果你的build target低于API level 16的话你可以去掉这个权限。

外部存储可能不可用

使用外部存储最大的问题是当你需要它的时候它可能没有被挂载。很明显SD卡被弹出以及把你的设备挂载到计算机上进行文件访问的时候就是这种情况。因此,你总要检查外部存储是否可用:

1
2
3
4
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
// you can go on
}

有些时候外部存储可能以read-only的模式挂载。如果你只需要读取数据那么下面的检查方式可能会更适合你:

1
2
3
4
if (Environment.getExternalStorageState().startsWith(
Environment.MEDIA_MOUNTED)) {
// you can go on
}

这是因为Environment.MEDIA_MOUNTED_READ_ONLY这个final字段的值是“mounted_ro”。实际上我并不喜欢final字段值的这段代码。我的观点是它应该写的更好些,Google应该选择使用整数,这样我们就可以用这些final字段作为位掩码来检测SD卡状态。

特定应用的内部存储目录

Android为你的应用创建了一个私有的目录。你的shared preferences,SQLite databases,native libraries或者cache文件都会放在这里。

所有特定应用的文件都会在这样命名的文件夹里

1
/data/data/your.package.name/

在这个文件夹里会有一些常见的子目录-这取决于你应用的需要:

  • 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值,你可以使用(“|”)操作符来组合它们-比如追加一个全局可写的文件:

1
2
openFileOutput("yourWritableFile",
Context.MODE_APPEND | Context.MODE_WORLD_READABLE);

特定应用的外部存储目录

这就是Waza_be’s咆哮的原因了-因为太多的应用忽略了处理外部存储中特定应用的目录的正确做法。

所有外部的特定应用文件应该被存储在这样命名的文件夹下

1
Android/data/your.package.name/

要注意的是我用了一个相对的路径。这个路径是相对于SD卡根目录的。SD卡挂载的路径约定,会根据Android的版本有所变化。

使用API调用替代硬编码值一直以来都是一个良好的编程实践,以前SD卡挂载点有过变化的这样一个事实应该让你在硬编码上更加谨慎。

对于外部文件现在只有一个方法供你使用:

1
getExternalFilesDir(String type)

如果你把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中,以便让这些文件即时的展示在公共媒体列表中。敬请期待。

如何正确的在Android中存储特定应用文件的更多相关文章

  1. android中使用Http下载文件并保存到本地SD卡

    1.AndroidMainfest.xml中设置权限 <uses-permission android:name="android.permission.INTERNET"& ...

  2. Android中aar和jar文件的认识

    在Android开发中,我们总是会引入其他第三方的库或者资源等,有时候是添加一个jar文件,有时候添加一个aar文件,那么这两种类型的文件有什么区别吗?详情请看下文. 一.描述. 1.   *.jar ...

  3. Android中使用SDcard进行文件的读取

    来自:http://www.cnblogs.com/greatverve/archive/2012/01/13/android-SDcard.html 平时我们需要在手机上面存储想音频,视频等等的大文 ...

  4. Android中的 init.rc文件简介

    init.rc脚本是由Android中linux的第一个用户级进程init进行解析的. init.rc 文件并不是普通的配置文件,而是由一种被称为"Android初始化语言"(An ...

  5. Android中对Log日志文件的分析[转]

    一,Bug出现了, 需要“干掉”它 bug一听挺吓人的,但是只要你懂了,android里的bug是很好解决的,因为android里提供了LOG机制,具体的底层代码,以后在来分析,只要你会看bug, a ...

  6. 又议android中的manifest清单文件

    写过java程序的人,都知道了配置文件时java实现各种各样的框架的一大利器,manifest清单文件对android的作用自然不言而喻,然而他里面究竟定义了些什么,并且他是如何加载到程序中的. 他里 ...

  7. Android中pull解析XML文件的简单使用

    首先,android中解析XML文件有三种方式,dom,sax,pull 这里先讲pull,稍候会说SAX和DOM pull是一种事件驱动的xml解析方式,不需要解析整个文档,返回的值是数值型,是推荐 ...

  8. 如何正确地使用android中的progressdialog

    网上有很多关于progressdialog的用法的介绍,下面这个是最具代表性的: http://sd8089730.iteye.com/blog/1441610 其核心代码: Handler hand ...

  9. Android 数据存储02之文件读写

    Android文件读写 版本 修改内容 日期 修改人 V1.0 原始版本 2013/2/25 skywang Android文件读写的有两种方式.一种,是通过标准的JavaIO库去读写.另一种,是通过 ...

随机推荐

  1. Ubuntu 安装gnome桌面及vnc远程连接

    安装gnome桌面 sudo apt-get install gnome-core 安装vnc sudo apt-get install vnc4server 启动vnc vncserver 设置一下 ...

  2. hibernate使用truncate清空表 截断表

    public void truncateTable(Session session, String tableNameInDb) { String sql = " truncate tabl ...

  3. 黑裙晖安装后修改mac和sn

    d当前使用6.2 打开putty sudo -i 然后在/tmp目录下创建一个临时目录,名字随意,如:boot mkdir -p /tmp/boot 第四步:切换到dev目录 cd /dev 第五步: ...

  4. nprogress 转

    转载:http://www.xuanfengge.com/front-end-nprogress-and-lightweight-web-progress-bar-nanobar.html 前言 进度 ...

  5. 安装sklearn的一点事故解决

    安装sklearn过程出现挺多问题的.这里记录下一下问题点避免下次走弯路 1.安装ANACONDA,避免太多插件的手动安装,选用版本Anaconda3-4.3.1-Windows-x86_64.rar ...

  6. python 创建txt每行写入

    txtPath=os.path.join(vocDir,"eval.txt") with open(txtPath,"w") as f: f.writeline ...

  7. C#端一个不错的订单号生成规则

    /// <summary> /// 订单助手 /// </summary> public class OrderHelper { /// <summary> /// ...

  8. sscanf linux-c从一个字符串中读进与指定格式相符的数据

    https://www.cnblogs.com/lanjianhappy/p/6861728.html 函数原型: Int sscanf( string str, string fmt, mixed ...

  9. UITableViewHeaderFooterView can't change custom background when loading from nib

    down voteforite I've created a custom UITableViewHeaderFooterView and successfully load from nib int ...

  10. 深入浅出 Java Concurrency (22): 并发容器 part 7 可阻塞的BlockingQueue (2)[转]

    在上一节中详细分析了LinkedBlockingQueue 的实现原理.实现一个可扩展的队列通常有两种方式:一种方式就像LinkedBlockingQueue一样使用链表,也就是每一个元素带有下一个元 ...