首页 > 代码库 > 安卓训练-开始-保存数据-保存文件
安卓训练-开始-保存数据-保存文件
保存文件
- 选择内部或外部存储
- 获取外部存储权限
- 在内部存储上保存文件
- 在外部存储上保存文件
- 查询空闲空间
- 删除文件
- 使用内部存储
- 使用外部存储
安卓的文件系统与其他平台上的基于磁盘的文件系统类似。这节课描述怎样使用安卓文件系统,使用 File
API 读写文件。
一个 File
对象适合读写从开始到结束的顺序的大量数据。例如,对图像文件 或网络上交换的任何东西特别有用。
这节课演示怎样在你的应用中执行基本的与文件相关的任务。这节课假设你熟悉 Linux 文件系统的基础知识和 java.io
中的标准的文件输入/输出 API。
选择内部或外部存储
所有安卓设备有两个文件存储区域:“内部”和“外部”存储。这些名字来自安卓初期,那时候大部分设备提供了内置的非易失性的内存(内部存储),另加一个可移除的存储介质,比如一个微型 SD 卡(外部存储)。一些设备把永久存储空间划分为“内部”和“外部”分区,所以即使没有可移除的存储介质,也总是有两个存储空间,不管外部存储是可移除的还是不可移除的 API 的行为都一样。下面是每个存储空间的资料总结列表。
内部存储:
- 总是可用。
- 这里存储的文件默认只有你的应用可以访问。
- 当用户卸载你的应用时,系统删除你的应用在内部存储上的所有文件。
当你想确保不管是用户还是其他应用都不能访问你的文件时,内部存储是最好的。
外部存储:
- 并不总是可用,因为用户可能把外部存储作为 USB 存储挂载,在有些情况下外部存储会从设备上移除。
- 它时全球可读的,所有这里存储的文件可能不受你的控制地被读取。
- 当用户卸载你的应用时,系统删除你的应用存储在通过
getExternalFilesDir()
获取的目录中的文件。
外部存储是存放不需要存取限制的文件和你想和其他应用共享或允许用户通过电脑访问的文件的最好的地方。
技巧:尽管应用默认安装到内部存储,你可以在清单文件中指定 android:installLocation
属性让你的应用可以安装到外部存储。当 APK 很大并且外部存储空间比内部存储大时,用户喜欢用这个选项。更多信息,参见应用安装位置。
获取外部存储权限
为了写入外部存储,你必须在你的清单文件中请求WRITE_EXTERNAL_STORAGE
权限:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
警告:目前,所有应用不需要特别的权限就能读外部存储。但是,这在未来的发布中可能会改变。如果你的应用需要读外部存储(但不需要写),那你需要声明READ_EXTERNAL_STORAGE
权限。为了让你的应用一直像期望的那样工作,你应该在发生变化前在现在就声明这个权限。
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ... </manifest>
但是,如果你的应用使用 WRITE_EXTERNAL_STORAGE
权限,那它就隐式地拥有了读外部存储的权限。
在内部存储上保存文件不需要任何权限。你的应用总是拥有读写它的内部存储目录中的文件的权限。
在内部存储上保存文件
当保存一个文件到内部存储时,你可以通过调用下面两个方法中的一个来获得合适的目录作为一个 File
:
getFilesDir()
- 为你的应用返回一个表示一个内部目录的
File
。 getCacheDir()
- 为你的应用的临时缓存文件返回一个表示一个内部目录的
File
。务必删除不再需要的文件。务必在任何时候都为你使用的内存设置一个合理的上限,比如 1MB。如果系统可用存储空间过低,它可能不经警告就删除你的缓存文件。
为了在这些目录中的某个创建一个新文件,你可以使用 File()
构造方法,传入上面的方法指定你的内部存储目录的File
。例如:
File file = new File(context.getFilesDir(), filename);
作为选择,你可以调用 openFileOutput()
获得一个FileOutputStream
向你的内部目录写入一个文件。例如,这里是怎样向一个文件写入文本:
String filename = "myfile"; String string = "Hello world!"; FileOutputStream outputStream; try { outputStream = openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(string.getBytes()); outputStream.close(); } catch (Exception e) { e.printStackTrace(); }
或者,你想缓存一些文件,你应该使用 createTempFile()
。例如,下面的方法从一个URL
中提取文件名并使用这个名称在你的应用的内部缓存目录中创建一个文件:
public File getTempFile(Context context, String url) { File file; try { String fileName = Uri.parse(url).getLastPathSegment(); file = File.createTempFile(fileName, null, context.getCacheDir()); catch (IOException e) { // 当创建文件时发生错误 } return file; }
注意:你的应用的内部存储目录在安卓文件系统的一个专用位置用你的应用的包名指定。技术上,如果你把文件模式设为可读,另一个应用可以读你的内部文件。但是,其他应用还需要知道你的应用的包名和文件名。其他应用不能浏览你的内部目录并且没有读或写的访问权除非你显式地把文件设为可读或可写。所以只要你为你的内部存储的文件使用MODE_PRIVATE
,那其他应用就永远不能访问它们。
在外部存储上保存文件
因为外部存储可能不可用—比如用户把存储挂载到 PC 上或者移除了提供外部存储的 SD 卡—在使用外部存储前你应该总是验证它是否可用。你可以调用 getExternalStorageState()
查询外部存储状态。如果返回的状态是MEDIA_MOUNTED
,那你可以读写你的文件。例如,下面的方法在确定存储是否可用时非常有用:
/* 检查外部存储是否可用于读或写 */ public boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } /* 检查外部存储是否至少可用于读 */ public boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; }
尽管外部存储可被用户或其他应用修改,你可能在这里保存两类文件:
- 公有文件
- 其他应用和用户可以随意使用的文件。当用户卸载你的应用,这些文件对用户仍保持可用。
例如,你的应用拍的照片或其他下载的文件。
- 私有文件
- 理应属于你的应用的并且当用户卸载你的应用时需要删除的文件。尽管这些文件在外部存储上对于用户和其他应用技术上是可访问的,但这些文件实际上并不向你的应用外的用户提供值。当用户卸载你的应用,系统删除你的应用的外部私有目录中的所有文件。
例如,你的应用下载的额外的资源或临时媒体文件。
如果你想在外部存储上保存公有文件,使用 getExternalStoragePublicDirectory()
方法取得一个表示外部存储上的合适的目录的File
。这个方法有一个参数指定你想保存的文件的类型,让它与其他公有文件在逻辑上组织在一起,比如DIRECTORY_MUSIC
或DIRECTORY_PICTURES
。例如:
public File getAlbumStorageDir(String albumName) { // 取得用户的公有图片目录。 File file = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; }
如果你想保存你的应用的私有文件,你可以调用 getExternalFilesDir()
并传入一个表示目录类型的名称,获得合适的目录。用这种方式创建的每个目录都加入一个父目录,这个父目录包含你的应用的所有外部存储文件,当用户卸载你的应用时系统删除这个父目录。
例如,你可以用下面的方法为个人相册创建一个目录:
public File getAlbumStorageDir(Context context, String albumName) { // 取得应用的私有图片目录。 File file = new File(context.getExternalFilesDir( Environment.DIRECTORY_PICTURES), albumName); if (!file.mkdirs()) { Log.e(LOG_TAG, "Directory not created"); } return file; }
如果前面定义的子目录的名称都不适合你的文件,你可以调用 getExternalFilesDir()
并传入null
。这返回你的应用在外部存储上的私有目录的根目录。
记住 getExternalFilesDir()
创建的目录在用户卸载你的应用时会被删除。如果你的文件在用户卸载你的应用后仍保持可用-比如你的应用是个照相机用户想保留照片,你应该使用getExternalStoragePublicDirectory()
。
不管你是为共享文件使用 getExternalStoragePublicDirectory()
还是为你的应用的私有文件使用getExternalFilesDir()
,重要的是你应该使用 API 常量提供的目录名称,比如DIRECTORY_PICTURES
。这些文件名称确保系统正确地处理这些文件。例如,DIRECTORY_RINGTONES
中保存的文件会被系统媒体扫描器归类为铃声而不是音乐。
查询空闲空间
如果你提前知道需要保存多少数据,你可以调用 getFreeSpace()
或getTotalSpace()
查明是否有足够的可用空间,而不引起一个IOException
。这些方法分别取得存储卷的当前可用空间和总空间。这些信息也用于避免存储卷的使用空间超过某个阈值。
但是,系统并不保证你可以写入和 getFreeSpace()
所表示的一样多的字节。如果返回的数值比你想保存的数据大几 MB,或者文件系统的使用空间未满 90%,那就可能安全写入。否则,你可能不应该写入存储。
注意:你并不一定要在保存文件前检查可用空间。你可以立刻写入文件,然后捕捉 IOException
。如果你并不确切知道需要多少空间,你可能需要这样做。例如,如果在你保存文件前,改变了文件的编码,把一个 PNG 图像转成了 JPEG,你事先不知道文件的大小。
删除文件
你应该总是删除你不再需要的文件。最直接的方式是取得一个打开的文件引用然后调用它自身的 delete()
。
myFile.delete();
如果文件保存在内部存储上,你可以调用 Context
的deleteFile()
,让它定位并删除一个文件:
myContext.deleteFile(fileName);
注意:当用户卸载你的应用时,安卓系统删除以下内容:
- 你在内部存储上保存的所有文件。
- 你在外部存储上使用
getExternalFilesDir()
保存的所有文件。
但是,你需要定期手工删除所有使用 getCacheDir()
创建的缓存文件以及其他你不在需要的文件。
安卓训练-开始-保存数据-保存文件