编写:kesenhoo - 原文:http://developer.android.com/training/basics/data-storage/files.html
Android使用与其他平台类似的基于磁盘的文件系统(disk-based file systems)。这节课会描述如何在Android文件系统上使用 File 的读写APIs。
File 对象非常适合用于读写流式顺序的数据。如图片文件或是网络中交换的数据。
这节课会演示在app中如何执行基本的文件操作任务。假定你已经对linux的文件系统与java.io中标准的I/O APIs有一定认识。
所有的Android设备都有两个文件存储区域:"internal" 与 "external" 存储。 那两个名称来自于早先的Android系统中,当时的大多设备都内置了不可变的内存(internal storage),然后再加上一个类似SD card(external storage)这样可以卸载的存储部件。后来有一些设备把"internal" 与 "external" 的部分都做成不可卸载的内置存储了,虽然如此,但是这一整块还是从逻辑上有被划分为"internal"与"external"的。只是现在不再以是否可以卸载来区分了。 下面列出了两者的区别:
Internal storage:
External storage:
Tip: 尽管app是默认被安装到internal storage的,你还是可以通过在程序的manifest文件中声明android:installLocation 属性来指定程序也可以被安装到external storage。当某个程序的安装文件很大且用户的external storage空间大于internal storage时,他们会倾向与将该程序安装到external storage。更多安装信息,请参考App Install Location。
为了写数据到external storage, 你必须在你的manifest文件中请求WRITE_EXTERNAL_STORAGE权限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
Caution:目前,所有的apps都可以在不指定某个专门的权限下做读external storage的动作。但是,这在以后的版本中会有所改变。如果你的app只需要读的权限(不是写), 那么你将需要声明 READ_EXTERNAL_STORAGE 权限。为了确保你的app能持续地正常工作,你需要现在就声明读权限。
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ... </manifest>
但是,如果你的程序有声明写的权限,那么就默认有了读的权限。
对于internal storage,你不需要声明任何权限,因为你的程序默认就有读写程序目录下的文件的权限。
当保存文件到internal storage时,你可以通过执行下面两个方法之一来获取合适的目录作为 FILE 的对象:
为了在那些目录下创建一个新的文件,你可以使用File() 构造器,如下:
File file = new File(context.getFilesDir(), filename);
同样,你也可以执行openFileOutput() 来获取一个 FileOutputStream 用于写文件到internal目录。如下:
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中抽取了一个文件名,然后再在程序的internal缓存目录下创建了一个以这个文件名命名的文件。
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;
}
Note: 你的app的internal storage 目录是以你的app的包名作为标识存放在Android文件系统的特定目录下[data/data/com.example.xx]。 从技术上讲,如果你设置文件为可读的,那么其他app就可以读取你的internal文件。然而,其他app需要知道你的包名与文件名。若是你没有设置为可读或者可写,其他app是没有办法读写的。因此只要你使用MODE_PRIVATE ,那么这些文件就不可能被其他app所访问。
因为external storage可能是不可用的,比如遇到SD卡被拔出等情况时。因此你应该在访问之前去检查其是否可用。你可以通过执行 getExternalStorageState()来查询external storage的状态。如果返回的状态是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;
}
尽管external storage对于用户与其他app是可修改的,那么你可能会保存下面两种类型的文件。
如果你想要保存文件为public形式的,请使用getExternalStoragePublicDirectory()方法来获取一个 File 对象来表示存储在external storage的目录。这个方法会需要你带有一个特定的参数来指定这些public的文件类型,以便于与其他public文件进行分类。参数类型包括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;
}
如果你想要保存文件为私有的方式,你可以通过执行getExternalFilesDir() 来获取相应的目录,并且传递一个指示文件类型的参数。每一个以这种方式创建的目录都会被添加到external storage封装你的app目录下的参数文件夹下(如下则是albumName)。这下面的文件会在用户卸载你的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在external storage下的private的根目录。
请记住,getExternalFilesDir() 方法会创建的目录会在app被卸载时被系统删除。如果你的文件想在app被删除时仍然保留,请使用getExternalStoragePublicDirectory().
不管你是使用 getExternalStoragePublicDirectory() 来存储可以共享的文件,还是使用 getExternalFilesDir() 来储存那些对与你的app来说是私有的文件,有一点很重要,那就是你要使用那些类似DIRECTORY_PICTURES
的API的常量。那些目录类型参数可以确保那些文件被系统正确的对待。例如,那些以DIRECTORY_RINGTONES
类型保存的文件就会被系统的media scanner认为是ringtone而不是音乐。
如果你事先知道你想要保存的文件大小,你可以通过执行getFreeSpace() or getTotalSpace() 来判断是否有足够的空间来保存文件,从而避免发生IOException。那些方法提供了当前可用的空间还有存储系统的总容量。
然而,系统并不能保证你可以写入通过getFreeSpace()
查询到的容量文件, 如果查询的剩余容量比你的文件大小多几MB,或者说文件系统使用率还不足90%,这样则可以继续进行写的操作,否则你最好不要写进去。
Note:你并没有被强制要求在写文件之前一定有要去检查剩余容量。你可以尝试先做写的动作,然后通过捕获 IOException 。这种做法仅适合于你事先并不知道你想要写的文件的确切大小。例如,如果在把PNG图片转换成JPEG之前,你并不知道最终生成的图片大小是多少。
你应该在不需要使用某些文件的时候,删除它。删除文件最直接的方法是直接执行文件的delete()
方法。
myFile.delete();
如果文件是保存在internal storage,你可以通过Context
来访问并通过执行deleteFile()
进行删除
myContext.deleteFile(fileName);
Note: 当用户卸载你的app时,android系统会删除以下文件:
- 所有保存到internal storage的文件。
- 所有使用getExternalFilesDir()方式保存在external storage的文件。
然而,通常来说,你应该手动删除所有通过 getCacheDir() 方式创建的缓存文件,以及那些不会再用到的文件。