Android文件系统跟其他平台基于磁盘的文件系统类似,这篇教程描述了如何使用文件相关的API在Android文件系统上进行读写操作。
File对象适合读写大量的流式数据,如图片文件或其他文件的网络传输。
这篇教程将演示如何在App中执行基本的文件操作,并假设读者对Linux文件系统和Java标准的文件输入输出有一定基础。
选择内部存储还是外部存储
所有的Android设备都将文件存储区域分为两部分:内部存储和外部存储。这种命名来源于早期的Android系统,当时大部分的设备都有一个内置的不可变的内存(内部存储),另外还有一个可移除的存储介质,如SD卡(外部存储),后来即使设备没有可移除的存储介质了,依然习惯性地将永久存储空间划分为“内部”和“外部”,并且无论外部存储是否可以移除,这两部分存储空间的API行为都是一样的。下面分别总结每个存储空间的特点:
内部存储
1.总是可用的
2.内部存储空间内的文件默认只有你的App可以访问
3.当用户卸载了你的App,系统从内部存储空间中移除所有你的App相关的文件
当你希望用户和其他App都不能访问你的文件时,内部存储是最好的选择。
外部存储
1.并非总是可用的,因为用户可能将外部存储作为USB存储,某些情况下甚至会从设备上移除外部存储
2.它是大家都可以访问的,存储在这里的文件可以被其他应用程序访问
3.当用户卸载了你的App,系统仅仅会移除存储在通过 getExternalFilesDir()获取到的路径中的该App相关的文件
当你的文件不需要访问限制,或者你想将文件分享给其他的App,或者允许用户通过电脑来访问它,那么外部存储是最好的选择。。
小贴士
尽管App默认是被安装到内部存储空间中,但是你可以通过在AndroidManifest中指定android:installLocation属性来使App安装到外部存储空间。当APK文件很大且外部空间比内部空间大时,用户或许会喜欢这样做。如需要了解更多信息,请参考App InstallLocation。
获取外部存储的权限
为了在外部存储空间中写入数据,需要在AndroidManifest文件中加入权限: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对象。需要对该文件目录设置一个合理的大小,如1M,并且确保在不需要的时候删除里面的文件。如果系统存储空间不足时,可能会在没有警告的条件下删除缓存的文件。
如果要在内部存储目录下创建一个文件,可以使用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;
}
注意:App的内部存储目录位于Android文件系统中通过应用包名决定的特定位置,从技术上讲,如果你的文件设置为可读,其他应用可以读取你的内部文件,但是,其他应用需要知道你应用的包名和文件名。如果你的文件没有设置为可读写,那么其他应用是不能访问该App的内部目录并读写你的文件的。因为只要使用了MODE_PRIVATE的模式,其他应用就不能访问。
保存文件到外部存储空间
因为外部存储空间有时是不可用的,比如将外部存储空间连接到PC或者SD卡被移除的时候,所以应该在访问它的时候检查它的可用性。通常可以使用getExternalStorgeState()来查询外部存储空间的状态,如果状态为MEDIA_MOUNTED,那么就可以读写文件。下面是检查外部存储空间是否可用的有效方法:
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
/* Checks if external storage is available 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;
}
尽管外部存储可以被用户和其他应用访问修改,有两种类型的文件可以保存在这里:
Public files这类文件对于用户或其他应用都是可访问的,当用户卸载了你的App时,这些文件依然保留并可被用户访问。例如,你的App所拍摄的照片或下载的文件。
Private files这类文件仅属于你的App,并且会随着App的卸载而被删除。因为这些文件存储在外部,因此在技术上这些文件是可以被用户和其他应用访问的,但这对于你的App之外的用户是没有什么价值的。当用户卸载掉App时,系统会删除该App所属的外部私有目录下的所有文件。例如App下载的额外资源文件或临时性的多媒体文件。
如果想在外部存储空间上保存公有文件,可以使用getExternalStoragePublicDirectory()方法来获取一个代表外部空间合适目录的File对象。这个方法需要一个参数来指定你所保存文件的类型,以便于跟其他公有文件分类。该参数类型有DIRECTORY_MUSIC或DIRECTORY_PICTURES等。例如:
public File getAlbumStorageDir(String albumName) {
// Get the directory 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;
}
如果想保存对于App私有的文件,可以调用getExternalFilesDir()来获取合适的文件目录,并传入一个指定文件类型的参数。通过这种方式创建的目录都会被添加到封装了该App所有外部存储文件的目录下,并且会在用户卸载App时被系统删除。下面例子可以用来创建一个个人相册的目录:
public File getAlbumStorageDir(Context context, String albumName) {
// Get the directory 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,它会返回外部存储空间上该App的私有目录的根目录。
请记住,通过getExternalFilesDir()方法创建的目录会随着App的卸载被删除。如果想在App被卸载后仍然可以使用这些文件,比如一个拍照的App,我们想在卸载后依然保存照片,那么就应该使用getExternalStoragePublicDirectory()。
无论是使用getExternalStoragePublicDirectory()来保存共享文件还是使用getExternalFilesDir()保存App私有的文件,重要的是尽可能使用API常量提供的目录名称,如DIRECTORY_PICTURES,这些目录名称能保证系统正确地对待里面的文件。例如,以DIRECTORY_RINGTONES类型存储的文件会被系统识别为铃声而不是音乐。
查询剩余空间如果你事先知道要保存的文件的大小,可以通过调用getFreeSpace()或getTotalSpace()来判断是否有足够的空间,从而避免发生IOException。这些方法可以获得当前可用的空间以及总容量的大小,这些信息对于在一定阈值以上的存储空间上存储文件是很有用的。
然而,系统并不能保证可以写入getFreeSpace()大小的文件,如果剩余空间比你想要存储的文件大几M,或者存储空间使用还不到90%,那么继续存储文件一般是安全的,否则最好不要再写入文件。
注意:其实并非强制在保存文件之前一定要对剩余空间进行查询。你可以直接尝试写入文件,然后捕捉IOException,如果你事先不知道到底需要多少存储空间的时候可以这样做。例如,当你把PNG图片转换为JPEG时,事先并不知道生成的图片大小是多少。
删除文件你应该在不再需要的时候删除文件,最直接的方法是通过调用文件的delete()方法。
myFile.delete();
如果文件是保存在内部存储空间上的,还可以调用Context的deleteFile()方法来定位和删除文件。
myContext.deleteFile(fileName);
注意:当用户卸载了App,Android系统会删除如下文件:
1.所有保存在内部存储空间的文件
2.所有保存在通过getExternalFilesDir()获取的外部存储空间目录的文件
然而,你应该定期地删除所有通过getCacheDir()创建的缓存文件以及那些不再使用的文件。