5.2 保存文件
Android使用的文件系统,这是在其他平台上的基于磁盘的文件系统类似。这节课介绍了如何使用Android文件系统的File API 来读取和写入文件。
一个File对象适合以从头到尾非跳跃的方式读取或写入大量的数据。例如,它适合图像文件或任何在网络上交换的东西。
这节课在您的应用程序显示了如何执行基本的文件相关的任务。这节课假定您是熟悉Linux文件系统的基础知识和java.io中的标准文件输入/输出API 。
选择内部或外部存储
所有的Andr oid设备有两个文件存储区:“内部”和“外部”存储。这些名称来自Android的早期,那时大多数设备提供内置的非易失性存储器(内部存储器),加上一个可移动存储介质如微型SD卡(外部存储)。某些设备把永久存储空间划分为“内部”和“外部”分区,所以,即使没有可移动存储介质,都会有两个存储空间,并且不管外部存储是否可移动,API的行为都是相同的。下面的列表总结了每个存储空间的事实。
内部存储:
· 它总是可用的。
· 默认情况下,这里保存的文件只有您的应用程序能访问。
· 当用户卸载你的应用程序,系统将从内部存储删除你的应用程序的所有文件。
当你要确信无论是用户还是其他的应用程序都不可以访问您的文件的时候,内部存储是最好的选择。
外部存储:
· 它并不总是可用的,因为用户可以用USB存储设备作为外部存储,并在某些情况下,把它从装置中取出。
· 这是全局可读,所以保存在这里的文件可能会在你的控制之外被读取。
· 当用户卸载您的应用程序时,只有当你之前是用getExternalFilesDir()方法将你的应用程序的文件保存在目录,系统才会从这里删除它们。
不需要访问限制的文件和要与其它应用程序共享的文件,或者允许用户用电脑访问时,外部存储是最好的地方。
提示:虽然默认情况下应用程序安装到内部存储的,你可以在你的清单文件指定android
:
installLocation
属性,让您的应用程序可以安装在外部存储。当APK大小是非常大的并且用户有一个比内部存储大得多的外部存储时,他们偏向于这个选项。欲了解更多信息,请参阅应用程序安装位置。
获取外部存储的权限
要写入到外部存储,您必须在你的manifest文件中请求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对象:
返回一个表示您的应用程序的内部目录的File对象。
返回一个你的应用程序的临时缓存文件的内部目录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) {
// Error while creating file
}
return file;
}
注: 您的应用程序的内部存储目录由您的应用程序的包名指定在Android文件系统中一个特殊的位置。从技术上说,另一个应用程序可以读取你的内部文件,如果你设置了文件模式是可读的。然而,其他应用程序还需要知道你的应用程序包名和文件名。其他应用程序不能浏览您的内部目录也没有读或写访问权限,除非你明确地设置文件可以读写。所以只要你对内部存储中的文件使用MODE_PRIVATE,它们对其他的应用程序就从来不可用。
在外部存储上保存文件
由于外部存储可能不可用,例如,当用户安装存储到PC上或移除了提供外部存储的SD卡,你一定要在访问它前确认它可用。您可以调用getExternalStorageState()查询外部存储状态。如果返回的状态等于MEDIA_MOUNTED,那么你可以阅读和写入文件。例如,可以用下列方法来确定存储可用:
/* Checks if external storage is available for read andwrite */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage isavailable to at least read */
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) {
// Get thedirectory for the user's public pictures directory.
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) {
// Get thedirectory for the app's private pictures directory.
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(),使用像DIRECTORY_PICTURES这样的API常量作为目录名,对你都是相关重要的 。这些目录的名称,确保这些文件由系统妥善处理。例如,保存在DIRECTORY_RINGTONES的文件被系统媒体扫描仪归类为铃声,而不是音乐。
查询空闲空间
如果你提前知道你要保存多少数据,调用getFreeSpace的()或getTotalSpace()你可以知道是否有足够的空间可用,而不是引起一个IOException。这些方法分别提供了当前可用空间和存储容量总空间。此信息也有助于避免填充的存储量超过某个临界值。
然而,系统并不能保证你可以完全写入getFreeSpace()表示的字节数。如果返回的数字比您想要保存的数据的大小超过几MB,或者如果文件系统还不到90%,那么它进行保存可能是安全的。否则,你可能不应该写入到存储中。
注:在您保存你的文件之前,你并不需要检查可用空间。相反,你可以马上尝试写入文件,然后捕获一个IOException,如果它发生了。您可能需要这样做,如果你不知道你需要的空间究竟有多大。例如,如果你把文件转换为PNG图片或JPEG来保存之前,改变它的编码,那么你不会事先知道文件的大小。
删除文件
你应该总是删除您不再需要的文件。删除一个文件,最简单的方法是对打开的文件的引用本身调用delete()方法。
myFile.delete();
如果该文件被保存在内部存储,你还可以使用Context调用DeleteFile() 定位并删除一个文件:
myContext.deleteFile(fileName);
注:当用户卸载你的应用程序,Android系统将删除以下内容:
· 你保存在内部存储上的所有文件
· 你使用getExternalFilesDir()保存在外部存储上的所有文件。
然而,你应该手动删除所有定期用getCacheDir()创建的缓存文件 ,还定期删除不再需要的其他文件。