转载请注明出处:Android学习路线(二十八)保存文件
Android使用了一个类似其它平台的基于磁盘的文件系统。本课将介绍怎样使用android的文件APIS来在这种文件系统中读写文件。
一个File
对象适用于顺序读写大块数据,而不适用于随机存取。比如它适用于文件或者其它通过网络交换的数据。
本课将想您介绍怎样在app中处理主要的文件操作。在此之前,你须要对Linux文件系统以及标准的java文件apis有所了解。
选择内部或外部存储
全部的Android设备都由两个存储区域:“内部”和“外部”存储。这两个名字来源于早期的android版本号。当时大多数设备提供“内部构建”(built-in)不可拆卸的内部存储,以及可拆卸的存储媒介,比如微型SD卡。一些设备也将持久化存储设备分为“内部”和“外部”两个部分。因此即使是没有可拆卸媒介的设备也会有两个存储区域,而API的行为与设备是否有可拆卸存储介质无关。以下总结了每一个存储区域的相关要点。
内部存储:
- 不论什么时候都可用。
- 默认情况下仅仅有在你的app中才干訪问。
- 当用户卸载你的app。系统将移除该应用在内部存储中的全部文件。
当你确定用户和其它app都不能訪问到的情况下,内部存储是最佳选择。
外部存储:
- 它并非不论什么时候都可用,由于用户可以像U盘一样使用它,同一时候它也可能随时被从设备上移除。
- 它能被不论什么人訪问,因此存储在这里的文件可以被其它app读取。
- 当用户卸载你的app。系统仅仅会在你将文件保存在getExternalFilesDir() 中时才会在这里移除你app中的文件。
当你不须要控制文件的訪问权限,同一时候你希望将文件和其它应用共享或者可以在电脑中訪问到。那么外部存储是最好的选择。
提示: 即使是apps默认会被安装在内部存储中,你也能够在你的app中的manifest文件里指定android:installLocation
属性。让你的app被安装在外部存储中。当apk的大小非常大时,用户会非常希望这么做,他们会有一个远大于内部存储的外部存储空间。很多其它信息,请參阅App
Install Location。
获取外部存储权限
要可以在外部存储中写文件,你必须在manifest文件里申请 WRITE_EXTERNAL_STORAGE
权限:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
注意: 眼下,全部的应用都有权限读取外部存储中的文件。然而,在之后的版本号中将会改变。
假设你的app须要读取外部存储中的文件(可是不须要写入)。你须要声明READ_EXTERNAL_STORAGE
权限。这样来保证你的app可以像预期那样工作。
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ... </manifest>
然而,假设你的app使用了WRITE_EXTERNAL_STORAGE
权限,那么它就同一时候拥有了读写权限。
保存文件到内部存储中并不须要权限。
你的app一直拥有在内部存储文件夹下的读写权限。
保存一个文件到内部存储中
当保存文件到内部存储中时,你能够通过以下这两种方式获取一个合适的File对象:
getFilesDir()
- 返回一个表示你的app在内部存储中的文件夹的
File
对象。 getCacheDir()
- 返回一个表示你的app在内部存储中的暂时文件夹的
File
对象。确保在你不须要该缓存文件时将其删掉,同一时候要指定一个合理的缓存文件限制大小。由于当系统缺少执行空间时会在不发出警告的情况下将该文件夹的文件删除。
要在上面两个文件夹之中的一个中创建一个文件,你能够使用 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的内部存储文件夹是由你的app包名指定。从技术上来说,假设你把这个文件的模式设置为可读,那么其它app就行訪问你的app的内部文件了。然而,这些其它的app必须知道你的app的包名以及内部文件的名字。
假设你不特别地自定内部文件的可读或可写模式,那么别的应用也没有权限操作你的app中的内部文件。
保存一个文件到外部存储
因为外部存储能够不可用——比如当用户将其移除——你须要在訪问它之前推断它是否可用。你能够通过调用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; }
即使外部存储中的文件能够被用户或者其它app改动,对于以下两种类型的文件相同有合适的情况被保存在这里:
- 公共文件
- 须要被全部app或者用户訪问到的文件。当你卸载你的app。你希望用户留下这些文件。
比如,你的app拍下的照片或者其它下载文件。
- 私有文件
- 文件隶属于你的app,而且应道在用户写在你的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; }
假设你想要在外部存储中保存私有文件,那么你能够通过调用getExternalFilesDir()
方法来获取合适的文件文件夹对象,同一时候传入一个名字来指定文件夹的类型。每一个通过这样的方式调用获得的文件夹,都会在应用被卸载时被清除。
比如。以下是怎样创建一个独立的相冊文件夹:
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卸载时被删除。假设这些文件须要在应用卸载后被保存,当你的应用再次安装时可以訪问到,你须要调用getExternalStoragePublicDirectory()
方法。
无论你使用的是可分享的getExternalStoragePublicDirectory()
方法。或者是私有的getExternalFilesDir()
方法,使用系统所提供的文件夹名称(如 DIRECTORY_PICTURES
)都非常重要。
这些文件夹名称将确保这些文件在系统中会被依照这些方式对待。
比如。保存在DIRECTORY_RINGTONES
中的文件将会被系统识别为手机铃声而非音乐。
查询剩余空间
假设你知道你要保存的数据的大小,这样你就能够推断出是否有足够的空间避免导致 IOException
异常。可通过调用getFreeSpace()
或者 getTotalSpace()
方法来获取剩余空间的大小。
这两个方法各自返回此时的可用空间大侠和存储卷中总共的空间大小。这种方法相同能够在開始就避免占满整个存储空间。
然而。系统并不能确保你可以写入通过getFreeSpace()
方法返回的数据大小。假设它返回的大小比你要写入的数据大小大几兆,或者文件系统还有90%的剩余空间。那么运行保存是安全的。否则,你不应该存储这些数据。
提示: 在存储文件前检查可用空间不是必须操作。你能够直接保存文件,然后捕获IOException
异常。你能够在你不知道要保存的数据有多大时使用这样的方法。比如,你在保存文件前将PNG转变成JPEG,改变了文件的编码,你提前并不知道文件的大小。
删除一个文件
你要记得在文件不须要被使用时将其删除。最直接的删除方法调用文件对象的delete()
方法删除它本身。
myFile.delete();
假设文件被保存在内部存储中。你相同能够使用Context定位然后调用deleteFile()
方法删除文件:
myContext.deleteFile(fileName);
提示: 当用户卸载你的app,系统将会删除以下内容:
- 全部你的app保存在内部存储中的数据
全部通过调用getExternalFilesDir()
方法保存在外部存储中的数据。
然而,你手动删除通过getCacheDir()
方法生成的文件,当这些文件不再须要被使用到时。