选择内部或外部存储
所有Android设备都有两个文件存储区:“内部”和“外部”存储。这些名称来自Android的早期,大多数设备提供内置的非易失性存储器(内部存储),以及可移动存储介质,如microSD卡(外部存储)。现在,许多设备将永久存储空间划分为单独的“内部”和“外部”分区。因此,即使没有可移动存储介质,这两个存储空间也始终存在,无论外部存储是否可移动,API行为都是相同的。
由于外部存储可能是可移除的,因此这两个选项之间存在一些差异,如下所示。
内部存储器:
它始终可用。
内部存储器保存的文件只能由您的应用访问。
当用户卸载您的应用程序时,系统会从内部存储中删除所有应用程序的文件。
当您希望确保用户和其他应用程序都无法访问您的文件时,最好使用内部存储。
外部存储:
外部存储并不总是可用,因为用户可以将外部存储装载为USB存储器,并在某些情况下将其从设备中移除。
外部存储是全局可读的,因此保存在此处的文件可能会在您的控件之外读取。
当用户卸载您的应用程序时,只有将应用程序的文件保存在通过使用getExternalFilesDir()获取的目录时,系统才会从此处删除应用程序的文件。
对于不需要访问限制的文件以及要与其他应用程序共享或允许用户使用计算机访问的文件,外部存储是最佳位置。
将文件保存在内部存储上
您的应用程序的内部存储目录由您的应用程序包名称指定在Android文件系统的特殊位置,可以使用以下API访问。
注意:与外部存储目录不同,您的应用程序不需要任何系统权限来读取和写入这些方法返回的内部目录。
写一个文件
将文件保存到内部存储时,可以通过调用以下两种方法获取相应的目录,进一步操作File :
getFilesDir():返回File表示应用程序的内部目录。
getCacheDir():返回File表示应用程序临时缓存文件的内部目录。
要在其中一个目录中创建新文件,可以使用File()构造函数,传递给File上述方法提供的指定内部存储目录的方法。
例如:
File file = new File(context.getFilesDir(), filename);
或者,您可以调用openFileOutput()以获取FileOutputStream 写入内部目录中的文件的内容。
例如,以下是如何将一些文本写入文件:
String filename = "myfile";
String fileContents = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(fileContents.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
MODE_PRIVATE 将会创建文件(或替换具有相同名称的文件),并将其设为应用的私有文件。 其他可用模式包括:MODE_APPEND、MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE。
请注意,该openFileOutput()方法需要文件模式参数。传递 MODE_PRIVATE 将会创建文件(或替换具有相同名称的文件),并将其设为应用的私有文件。 其他可用模式包括:MODE_APPEND、MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE。
自 API 级别 17 以来,常量 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 已被弃用。从 Android N(7.0,API24) 开始,使用这些常量将会导致引发 SecurityException。这意味着,面向 Android N 和更高版本的应用无法按名称共享私有文件,尝试共享“file://”URI 将会导致引发 FileUriExposedException。 如果您的应用需要与其他应用共享私有文件,则可以将 FileProvider 与 FLAG_GRANT_READ_URI_PERMISSION配合使用
在Android 6.0(API级别23)及更低级别上,如果您将文件模式设置为全局可读,则其他应用程序可以读取您的内部文件。但是,其他应用必须知道您的应用包名称和文件名。除非您明确将文件设置为可读或可写,否则其他应用程序无法浏览您的内部目录并且没有读取或写入权限·因此,只要您在内部存储上使用MODE_PRIVATE标记您的文件,其他应用就永远无法访问它们。
写一个缓存文件
如果你需要缓存一些文件,你应该使用 createTempFile()。
例如,以下方法从URL中提取文件名,并在应用程序的内部缓存目录中创建具有该名称的文件:
private 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;
}
使用createTempFile()创建的文件放置在应用程序专用的缓存目录中。您应该定期删除不再需要的文件。
注意: 如果系统存储空间不足,可能会在没有警告的情况下删除缓存文件,因此请确保在读取之前检查缓存文件是否存在。
打开现有文件
要读取现有文件,请调用openFileInput(name),传递文件名。
您可以通过调用获取所有应用程序文件名的数组 fileList()。
注意:如果您需要在应用程序中打包一个可在安装时访问的文件,请将该文件保存在项目的res/raw/目录中。您可以使用openRawResource()传递资源ID 打开这些文件。此方法返回可用于读取文件的方法。您无法写入原始文件。
打开一个目录
您可以使用以下方法在内部文件系统上打开目录:
getFilesDir():返回File表示文件系统上与您的应用唯一关联的目录。
getDir(name, mode):在应用程序的唯一文件系统目录中创建新目录(或打开现有目录)。此新目录显示在提供的目录中getFilesDir()。
getCacheDir():返回File表示文件系统上与您的应用唯一关联的缓存目录。此目录适用于临时文件,应定期清理。如果磁盘空间不足,系统可能会删除那里的文件,因此请确保在读取之前检查缓存文件是否存在。
要在其中一个目录中创建新文件,可以使用 File()构造函数,传递File上述方法之一提供的指定内部存储目录的对象。
例如:
File directory = context.getFilesDir();
File file = new File(directory, filename);
将文件保存在外部存储上
使用外部存储非常适合您要与其他应用共享或允许用户使用计算机访问的文件。
在请求存储权限并验证存储可用后,您可以保存两种不同类型的文件:
公共文件:应该可供其他应用程序和用户免费使用的文件。当用户卸载您的应用时,这些文件应该仍然可供用户使用。例如,应用程序或其他下载文件捕获的照片应保存为公共文件。
私人文件:合法属于您的应用的文件,将在用户卸载您的应用时删除。虽然这些文件在技术上可由用户和其他应用程序访问,因为它们位于外部存储上,但它们不会为应用程序外的用户提供价值。
注意:如果用户卸下SD卡或将设备连接到计算机,外部存储可能会变得不可用。并且用户和具有READ_EXTERNAL_STORAGE 权限的其他应用程序仍然可以看到这些文件。因此,如果您的应用程序的功能取决于这些文件,或者您需要完全限制访问,则应将文件写入内部存储。
请求外部存储权限
要写入公共外部存储,您必须在清单文件中请求WRITE_EXTERNAL_STORAGE权限:
<uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” />
如果您的应用使用该WRITE_EXTERNAL_STORAGE权限,则它也隐式拥有读取外部存储的权限。
如果您的应用只需要读取外部存储(但不能写入),那么您需要声明 READ_EXTERNAL_STORAGE权限:
<uses-permission android:name = “android.permission.READ_EXTERNAL_STORAGE” />
从Android 4.4(API级别19)开始,在应用程序的私有外部存储目录中读取或写入文件 - 使用getExternalFilesDir() 访问, 不需要READ_EXTERNAL_STORAGE 或WRITE_EXTERNAL_STORAGE 权限。因此,如果您的应用支持Android 4.3(API级别18)及更低版本,并且您只想访问专用外部存储目录,则应通过添加maxSdkVersion 属性声明仅在较低版本的Android上请求权限 :
<uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE”
android:maxSdkVersion = “18” />
验证外部存储是否可用
由于外部存储可能不可用 - 例如当用户将存储装置安装到PC或已移除提供外部存储的SD卡时 - 您应始终在访问之前验证该卷是否可用。您可以通过调用来查询外部存储状态getExternalStorageState()。如果返回状态为MEDIA_MOUNTED,则可以读取和写入文件。如果是MEDIA_MOUNTED_READ_ONLY,则只能读取文件。
例如,以下方法可用于确定存储可用性:
/* 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()方法获取File表示外部存储上的相应目录。该方法接受一个参数,指定要保存的文件类型,以便可以使用其他公共文件(如DIRECTORY_MUSIC或) 对其进行逻辑组织DIRECTORY_PICTURES。例如:
public File getPublicAlbumStorageDir(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;
}
如果要从Media Scanner中隐藏文件,请在外部文件目录中包含一个名为.nomedia空文件(请注意文件名 中的点前缀)。这可以防止媒体扫描程序读取您的媒体文件,并通过MediaStore内容提供商将其提供给其他应用程序。
保存到外部私有目录
如果要将文件保存在应用程序专用且外部提供程序无法访问的外部存储上MediaStore,您可以通过调用getExternalFilesDir()并向其传递一个名称来获取一个目录,该目录仅由您的应用程序使用, 该名称指示您希望的目录类型。以这种方式创建的每个目录都会添加到父目录中,该目录封装了应用程序的所有外部存储文件,系统会在用户卸载应用程序时将其删除。
public File getPrivateAlbumStorageDir(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。这将返回外部存储上应用程序私有目录的根目录。
请记住,getExternalFilesDir() 创建一个在用户卸载应用程序时删除的目录。如果您保存的文件在用户卸载应用程序后仍然可用 - 例如当您的应用程序捕获照片并且用户应保留这些照片时 - 您应该将文件保存到公共目录。
在多个存储位置之间选择
有时,分配内部存储器分区以用作外部存储器的设备也提供SD卡插槽。这意味着该设备有两个不同的外部存储目录,因此您需要选择在将“私有”文件写入外部存储时使用哪个目录。
从Android 4.4(API级别19)开始,您可以通过调用访问这两个位置getExternalFilesDirs(),该位置 返回一个包含每个存储位置条目的File数组。数组中的第一个条目被视为主要外部存储,您应该使用该位置,除非它已满或不可用。
如果您的应用支持Android 4.3及更低版本,则应使用支持库的静态方法ContextCompat.getExternalFilesDirs()。这总是返回一个File数组,但如果设备运行的是Android 4.3及更低版本,那么它只包含一个主外部存储条目(如果有第二个存储位置,则无法在Android 4.3及更低版本上访问它)。
删除文件
您应该始终删除您的应用不再需要的文件。删除文件最直接的方法是调用File对象delete()方法。
myFile.delete();
如果文件保存在内部存储器上,您还可以通过调用Context的deleteFile()来查找和删除文件:
myContext.deleteFile(fileName );
注意:当用户卸载您的应用时,Android系统会删除以下内容:
您在内部存储上保存的所有文件。
使用getExternalFilesDir()保存在外部存储的所有文件
但是,您应该手动删除getCacheDir()定期创建的所有缓存文件, 并定期删除不再需要的其他文件。
Android 目录总结
($rootDir)
+- /data -> Environment.getDataDirectory()
| |
| | ($appDataDir)
| +- data/com.srain.cube.sample
| |
| | ($filesDir)
| +- files -> Context.getFilesDir() / Context.getFileStreamPath("")
| | |
| | +- file1 -> Context.getFileStreamPath("file1")
| | ($cacheDir)
| +- cache -> Context.getCacheDir()
| |
| +- app_$name ->(Context.getDir(String name, int mode)
|
| ($rootDir)
+- /storage/sdcard0 -> Environment.getExternalStorageDirectory()
| / Environment.getExternalStoragePublicDirectory("")
|
+- dir1 -> Environment.getExternalStoragePublicDirectory("dir1")
|
| ($appDataDir)
+- Andorid/data/com.srain.cube.sample
|
| ($filesDir)
+- files -> Context.getExternalFilesDir("")
| |
| +- file1 -> Context.getExternalFilesDir("file1")
| +- Music -> Context.getExternalFilesDir(Environment.Music);
| +- Picture -> ... Environment.Picture
| +- ...
|
| ($cacheDir)
+- cache -> Context.getExternalCacheDir()
|
+- ???