Android系统使用与其他平台基于磁盘的文件系统类似的文件系统,本节介绍在Android文件系统中使用File API接口读写文件的方法。
File对象用来读写大量数据。例如,图像文件或者在网络上交换的其他对象都可通过File对象进行存取。
本节假设开发者已经对linux文件系统和java.io中标准文件输入/输出API接口都有所了解,介绍了如何在应用软件中执行文件相关的基本操作。
所有的Android设备拥有两个文件存储区域:外部存储器和内部存储器。这两个区域名称来自Android早期,这时候大多数设备提供了内嵌的非易失存储(内部存储),以及一个可移除的存储媒介如微型SD卡(外部存储)。另一些设备将永久存储空间分为“内部”和“外部”分区,因此即使没有可移除存储媒介,这些设备也拥有两个存储空间。对于外部存储,无论是否可移除,其API接口行为都一样。以下列出了两个存储空间的特征:
内部存储器
内部存储器适用于不希望文件被其他用户或其他应用软件访问的情况。
外部存储器
外部存储器适用于没有访问限制或者期望与其他应用软件共享的文件。
技巧:虽然默认情况下应用软件都会安装在内部存储器,开发者可以通过在manifest文件中指定android:installLocation让应用软件安装到外部存储器。这种安装方式尤其适用于APK超大的应用软件,详细介绍可参见APP安装位置。
写外部存储器需要在manifest文件中请求WRITE_EXTERNAL_STORAGE权限
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
</manifest>
警告:当前的外部存储器的G读没有进行权限控制,但是这种状况在将来的版本发生变化。因此,在需要读外部存储器的应用软件声明READ_EXTERNAL_STORAGE权限,能够确保应用软件即使在将来版本发生变化时也能正常运行。
<manifest ...>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
...
</manifest>
然而,如果应用软件声明了WRITE_EXTERNAL_STORAGE权限,则它暗含了READ_EXTERNAL_STORAGE权限。
内部存储器的读写不需要权限,应用软件总是拥有对其内部存储目录中文件的读写权限。
向内部存储器保存文件时,开发者可以通过以下两种方法以文件的形式获取合适的目录:
getFilesDir() 返回代表应用软件的内部目录的文件
getCacheDir() 返回代表应用软件存储临时缓存文件目录的文件。开发者需要确保文件在不再需要时被删除,在任何时刻为内存指定合理大小,如1MB。当存储空间不足时,系统会在不发出告警的情况下删除缓存文件。
在上述目录中创建目录时,开发者使用上述方法获取到的内部存储目录作为参数调用File()构造函数,例如
File file =newFile(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 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;
}
虽然用户或者其他应用软件可以对外部存储器进行修改,在外部存储器保存的文件可以分为以下两类:
公共文件:
此类文件能够被用户或者其他应用软件自由访问,当本应用软件被卸载时,此类文件不受影响。如软件抓取的照片或者其他下载的文件。
私有文件:
此类文件为本应用软件独有,当软件被卸载时,这些文件会随之删除。虽然从技术角度位于外部存储器的私有文件能够被用户与其他软件访问,但是不具备任何实际意义。当软件被卸载时,外部私有目录下的文件都会被系统删除。如软件下载的额外资源文件或者临时媒体文件。
向外部存储器保存公共文件时,可以通过getExternalStoragePublicDirectory()方法获取一个代表外部存储器上合适目录的文件,其参数指明期望保存的文件类型从而更好地组织各种公共文件,如DIRECTORY_MUSIC或者DIRECTORY_PICTURES,例如:
public File getAlbumStorageDir(String albumName){
// Get the directory for the user's public pictures directory.
File file =newFile(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 the directory for the app's private pictures directory.
File file =newFile(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()){
Log.e(LOG_TAG,"Directory not created");
}
return file;
}
如果没有合适的预定义子目录名称,使用null作为参数调用getExternalFilesDir(),这样将会返回外部存储器上用于应用软件私有文件的根目录。
需要谨记的是getExternalFilesDir()创建的目录始终位于随着软件卸载而自动删除的目录。如果期望在软件卸载后仍然保留文件——如一个照相软件,用户希望保留这些照片——开发者应该使用getExternaStoragePublicDirectory()。
无论是使用getExtenalStoragePublicDirectory()获取共享文件的目录还是使用getExternalFilesDir()获取私有文件的目录,目录名称都应该API提供常量如DIRECTORY_PICTURES,这些常量保证了相关文件能够正确地被系统处理。例如,使用DIRECTORY_RINGTONES保证的文件被系统媒体扫描器归类为铃声而不是音乐。
如果事先已经知道需要存储的文件的大小,可以通过调用getFreeSpace()或者getTotalSpace()获取当前可用空间从而避免IOExecption异常。这两个方法返回存储卷中的当前可用空间和总空间,这些信息也可以用来限制存储大小保持在特定阈值以下。
但是,系统并不总是支持写入与getFreeSpace()返回大小的字节数。只有期望保存的文件大小比其返回值略小,或者文件系统占用低于90%时,写入操作才有可能成功,否则,写入操作有较大可能失败。
注意:开发者并不总是需要在保存文件前查询可用空间大小,只需使用try保存文件,catch有可能发生的IOException异常,尤其在写入数据大小不确定时,更需使用这种方法。例如,将PNG图像转换成JPEG文件保存时,事先不能知道文件的确切大小。
不再需要的文件总是应该被删除。最直接的方法是通过打开文件的引用调用delete()方法。
myFile.delete();
存储在内部存储器的文件,还通过Context的deleteFile()方法删除。
myContext.deleteFile(fileName);
注意:当用户卸载应用软件时,Android系统自动删除:
该应用存储在内部存储器的所有文件
该应用使用getExternalFileDir()保存在外部存储器的所有文件
但是,通过getCacheDir()创建的缓存文件及其他不再需要的文件必须由开发者自行删除。