Android使用的文件系统和其他平台的基本磁盘的文件系统很相似。这里将要介绍如何使用File API在Android文件系统中读写文件。
File对象适合按顺序读写大量的数据。例如,适合图片文件或者其他在网络上交换的东西。
这里将要展示基本的文件相关的任务。这里假设你熟悉基本的Linux文件系统和标准的java.io中的输入输出。
选择内部或外部存储
所有的Android设备由两种存储位置:内部存储和外部存储。这些名字来源于早期的Android,大多数提供内置的不可拆解的存储器(internal storage),加上一个可移除的存储介质比如micro SD 卡(external storage)。有些设备把永久存储空间分为“内部”和“外部”,就算没有可移除的存储介质,总有两个存储空间不管外部存储是否存在API的行为是一样的。下面列出了两种存储空间的简介。
内部存储:
-
总是可用的
-
这里存储的文件默认是只能被你的app访问到。
-
当用户卸载了app,系统会把app所有的文件从内部存储上删除。
当你要确保无论是用户还是其他app都不能访问你的文件时,内部存储是最好的地方
外部存储:
-
不总是可用的,因为用户可能会把外部存储当作USB使用或者有时候会从设备上拿走。
-
谁都可以读,这里保存的文件可以总是被读,不在你的控制之中。
-
当用户卸载你的app时,系统只会删除getExternalFilesDir()文件夹中的文件。
如果文件没有访问权限,或者你向和其他app分享,或者可以让用户通过电脑访问,外部存储是最好的地方。
提示:虽然app是默认安装到内部存储上,但是你一个指定manifest中的android:installLocation让app了以安装到外部存储上。当APK很大或者外部存储大小比内部存储大小大时,用户很喜欢这个选择。更多信息,查看App Install Location。
申请外部存储权限
要在外部存储上写入,必须在manifest中申请WRITE_EXTERNAL_STORAGE权限:
1 <manifest ...> 2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 3 ... 4 </manifest>
注意:目前,不需要特殊权限,所有的app都有读外部存储的权限。但是,这个在未来的版本中可能会改变。如果app需要读外部存储(但是不需要写),你需要申明READ_EXTERNAL_STORAGE权。为了确保app按照预期的工作,在未来的版本修改之前,现在就要声明这个权限。
1 <manifest ...> 2 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 3 ... 4 </manifest>
但是,如果app使用的WRITE_EXTERNAL_STORAGE权限,隐藏的就也拥有了读取外部存储的权限。
在内部存储上保存文件不需要任何权限。你的程序在它的内部存储文件夹下一直都有读写文件的权限。
在内部存储上保存文件
当在内部存储上保存文件时,你可以通过File的下面两个方法获获得文件夹:
返回一个File,代表app的内部文件夹
返回一个File,代表app的临时cache文件的内部文件夹。确保在文件不需要的时候删除它们,并且在任何时候要限制使用的内存在一个合理的大小,比如1MB。当系统运行到存储不够时,可能不提示警告就会删除你的cache。
要在这些文件夹中创建一个新文件,你可以使用File()构造方法,传递由上面方法提供的指定内部文件夹的File,例如:
1 File file = new File(context.getFilesDir(), filename);
或者,你可以调用openFileOutput()来获得FileOutputStream可以在内部存储中写入文件。例如,这里时如何往文件中写文字:
1 String filename = "myfile"; 2 String string = "Hello world!"; 3 FileOutputStream outputStream; 4 5 try { 6 outputStream = openFileOutput(filename, Context.MODE_PRIVATE); 7 outputStream.write(string.getBytes()); 8 outputStream.close(); 9 } catch (Exception e) { 10 e.printStackTrace(); 11 }
或者,如果需要缓存一些文件,可以使用createTempFile()。例如,下面的方法从URL中抽取文件名,然后用那个文件名在app的内部缓存文件夹中创建一个文件:
1 public File getTempFile(Context context, String url) { 2 File file; 3 try { 4 String fileName = Uri.parse(url).getLastPathSegment(); 5 file = File.createTempFile(fileName, null, context.getCacheDir()); 6 catch (IOException e) { 7 // Error while creating file 8 } 9 return file; 10 }
注意:app的内部文件夹是由app的包名在Android文件系统的一个特殊位置指定的。技术上来说,其他app可以读取你的内部文件如果你设置文件模式为可读。但是,其他app需要知道你的app的包名和文件名。其他app不能浏览你的内部文件夹也不能读写除非你明确的设置文件为可读或可写。所以只要你在内部存储时文件使用MODE_PRIVATE模式,其他app就一直不能进入它们。
在外部存储上保存文件
由于外部存储可能不可用(比如用户把存储安装到了PC或者拿走了提供外部存储的SD卡),每次进入之前都要检查是否可用。可以通过getExternalStorageState()来查询外部存储状态。如果返回的状态是MEDIA_MOUNTED,那么可以读写文件。例如,下面的方法可以判断存储是否可用:
1 /* 查看外部存储是否可读可写 */ 2 public boolean isExternalStorageWritable() { 3 String state = Environment.getExternalStorageState(); 4 if (Environment.MEDIA_MOUNTED.equals(state)) { 5 return true; 6 } 7 return false; 8 } 9 10 /* 查看外部存储是否至少可读 */ 11 public boolean isExternalStorageReadable() { 12 String state = Environment.getExternalStorageState(); 13 if (Environment.MEDIA_MOUNTED.equals(state) || 14 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 15 return true; 16 } 17 return false; 18 }
虽然外部存储可以被用户和其他app修改,你可以在这里存储两类文件:
公有文件
可被其他app和用户自由使用的文件。当用户卸载app后,这些文件仍然可以被用户使用。
比如:你的app获取的照片,或者下载的文件。
私有文件
属于你的app的并且卸载时需要删除的文件。虽然因为是在外部存储上这些文件技术上来说是可以被用户和其他app进入的,但是这些文件对于你的app之外的用户没有任何实际意义。当用户卸载app时,系统会删除app外部存储文件夹下的所有文件。
比如:app另外下载的资源或者临时媒体文件。
如果需要在外部存储中保存公有文件,使用getExternalStoragePublicDirectory()方法来获得代表外部存储上的适合的文件夹的File。这个方法带有一个指定你要保存的文件类型的参数,这样可以在逻辑上和其他公有文件进行管理,比如DIRECTORY_MUSIC或者DIRECTORY_PICTURES。例:
1 public File getAlbumStorageDir(String albumName) { 2 // 获得用户公有图片文件夹. 3 File file = new File(Environment.getExternalStoragePublicDirectory( 4 Environment.DIRECTORY_PICTURES), albumName); 5 if (!file.mkdirs()) { 6 Log.e(LOG_TAG, "Directory not created"); 7 } 8 return file; 9 }
如果要保存app的私有文件,可以调用getExternalFilesDir()然后传递一个你喜欢的表示文件夹类型的名字来获得合适的文件夹。每个这样创建的文件夹会被加到一个父文件夹中,父文件夹包含了你的app的所有外部存储文件,它们会在用户卸载app时被系统删除。
例如,这里的方法可以用来创建一个私有的相册文件夹:
1 public File getAlbumStorageDir(Context context, String albumName) { 2 // 获得app私有图片文件夹. 3 File file = new File(context.getExternalFilesDir( 4 Environment.DIRECTORY_PICTURES), albumName); 5 if (!file.mkdirs()) { 6 Log.e(LOG_TAG, "Directory not created"); 7 } 8 return file; 9 }
如果还没有合适你文件的预定好的子文件名字,可以调用getExternalFilesDir()时传递一个null参数。这样会返回app在外部存储中私有文件夹的根文件夹。
记住getExternalFilesDir()是在一个用户卸载app后会删除的文件夹中创建的文件夹。如果你保存的文件是要在用户删除app后仍然保留的(比如你的app是一个相机 ,用户想要保留这些照片),你应该使用getExternalStoragePublicDirectory()。
不管是为可分享的文件使用getExternalStoragePublicDirectory()还是为app私有文件使用getExternalFilesDir(),使用API常量比如DIRECTORY_PICTURES作为文件夹名很重要。这些文件夹名确保了系统会合适的处理这些文件。例如,DIRECTORY_RINGTONES中的文件会被系统认为是铃声而不是音乐。
查询可用空间
如果提早知道保存了多少数据,可以知道是否有充足的空间可用,不会通过调用getFreeSpace()或getTotalSpace()导致一个IOException。这些方法分别可以提供存储卷轴上当前的可用空间个总空间。这些信息对于避免存储超过存储极限很有用。
然而,系统并不保证你可以写getFreeSpace()指示的那么多的字节。如果返回的数字比要存储的数据大几MB,或者文件系统存储量小于90%,那么存储会很安全。否则,可能不能向存储中写入。
注意:不需要在存储文件时查看可用空间,可以在try中写文件,然后catch IOException如果出现异常。当不知道需要多大空间时也可以这么做。例如,在存储之前把文件编码从PNG转换成JPEG,预先是不知道文件大小的。
删除文件
不在需要的文件要删除。最直接的删除文件的方式是调用已打开文件自己的delete()。
1 myFile.delete();
如果文件保存在内部存储上,也可以用Context来定位然后调用deleteFile()来删除文件:
1 myContext.deleteFile(fileName);
注意:当用户卸载app时,Android系统会删除下面的文件:
-
所有保存在内部存储上的文件
-
所有使用getExternalFilesDir()保存在外部存储上的文件。
但是,应该按照基本规则手动删除用getCacheDir()创建的缓存文件,和其他不再需要的文件。