Android 存储空间操作
一、专属存储空间
1.1.内部存储空间
1.1.1 在专属存储空间写入一个文件:
String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
fos.write(fileContents.toByteArray());
}
1.1.2 读取在存储存储空间的文件:
FileInputStream fis = null;
try {
fis = this.openFileInput("demoFile");
InputStreamReader inputStreamReader = new InputStreamReader(fis, StandardCharsets.UTF_8);
String fileContents = null;
BufferedReader reader = new BufferedReader(inputStreamReader)
fileContents = reader.readLine();
Log.d("FileContents: ", fileContents);
} catch (IOException e) {
e.printStackTrace();
}
1.1.3 获取存储空间内的文件列表:
String[] fileList = this.fileList();
1.1.4 创建嵌套目录
File directory = this.getFilesDir();
File file = new File(directory, filename);
1.1.5 创建缓存文件
try {
File.createTempFile(filename, null, this.getCacheDir());
} catch (IOException e) {
e.printStackTrace();
}
1.1.6 访问缓存文件
File file = new File(this.getCacheDir(), filename);
1.1.7 移除缓存文件
File file = new File(this.getCacheDir(), filename);
file.delete();
this.deleteFile(cacheFileName);
1.2 外部存储空间
在 Android 4.4(API 级别 19)或更高版本中,应用无需请求任何与存储空间相关的权限即可访问外部存储空间中的应用专属目录。卸载应用后,系统会移除这些目录中存储的文件。
在搭载 Android 9(API 级别 28)或更低版本的设备上,只要您的应用具有适当的存储权限,就可以访问属于其他应用的应用专用文件。为了让用户更好地管理自己的文件并减少混乱,以 Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被授予了对外部存储空间的分区访问权限(即分区存储)。启用分区存储后,应用将无法访问属于其他应用的应用专属目录。
1.2.1 验证存储空间的可用性
private boolean isExternalStorageWritable() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
}
private boolean isExternalStorageReadable() {
return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ||
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY);
}
1.2.2 选择物理存储位置
除非该卷已满或者不可用,否则使用该卷;
File[] externalStorageVolumes =
ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
File primaryExternalStorage = externalStorageVolumes[0];
1.2.3 访问持久性文件
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
1.2.4 创建缓存文件
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
1.2.5 移除缓存文件
externalCacheFile.delete();
1.2.6 媒体内容
如果应用支持使用仅在您的应用内对用户有价值的媒体文件,最好将这些文件存储在外部存储空间的应用专属目录中:
@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (file == null || !file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
1.3 查询可用空间
getAllocatableByte() 方法可以查出设备可以为应用提供多少空间,getAllocatableBytes() 的返回值可能大于设备上的当前可用空间量。这是因为系统已识别出可以从其他应用的缓存目录中移除的文件。
如果有足够的空间保存您的应用数据,请调用 [allocateBytes() ](https://developer.android.google.cn/reference/android/os/storage/StorageManager?hl=zh-cn#allocateBytes(java.io.FileDescriptor, long))。否则,您的应用可以请求用户从设备移除一些文件或从设备移除所有缓存文件。
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;
StorageManager storageManager =
getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
storageManager.allocateBytes(
appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
Intent storageIntent = new Intent();
storageIntent.setAction(ACTION_MANAGE_STORAGE);
}
二、共享存储空间
Android 提供用于存储和访问以下类型的可共享数据的 API:
- 媒体内容:系统提供标准的公共目录来存储这些类型的文件,这样用户就可以将所有照片保存在一个公共位置,将所有音乐和音频文件保存在另一个公共位置,依此类推。您的应用可以使用此平台的
MediaStore API 访问此内容。 - 文档和其他文件:系统有一个特殊目录,用于包含其他文件类型,例如 PDF 文档和采用 EPUB 格式的图书。您的应用可以使用此平台的存储访问框架访问这些文件。
- 数据集:在 Android 11(API 级别 30)及更高版本中,系统会缓存多个应用可能使用的大型数据集。这些数据集可为机器学习和媒体播放等用例提供支持。应用可以使用
BlobStoreManager API 访问这些共享数据集。
系统会自动扫描外部存储卷,并将媒体文件添加到以下明确定义的集合中:
- 图片(包括照片和屏幕截图),存储在
DCIM/ 和 Pictures/ 目录中。系统将这些文件添加到 MediaStore.Images 表格中。 - 视频,存储在
DCIM/ 、Movies/ 和 Pictures/ 目录中。系统将这些文件添加到 MediaStore.Video 表格中。 - 音频文件,存储在
Alarms/ 、Audiobooks/ 、Music/ 、Notifications/ 、Podcasts/ 和 Ringtones/ 目录中。此外,系统还可以识别 Music/ 或 Movies/ 目录中的音频播放列表,以及 Recordings/ 目录中的录音。系统将这些文件添加到 MediaStore.Audio 表格中。录音目录在 Android 11(API 级别 30)及更低版本中不可用。 - 下载的文件,存储在
Download/ 目录中。在搭载 Android 10(API 级别 29)及更高版本的设备上,这些文件存储在 MediaStore.Downloads 表格中。此表格在 Android 9(API 级别 28)及更低版本中不可用。
媒体库还包含一个名为 MediaStore.Files 的集合。其内容取决于您的应用是否使用分区存储(适用于以 Android 10 或更高版本为目标平台的应用):
- 如果启用了分区存储,集合只会显示您的应用创建的照片、视频和音频文件。大多数开发者无需使用
MediaStore.Files 即可查看其他应用的媒体文件,但如果您有特定要求,则可以声明 READ_EXTERNAL_STORAGE 权限。不过,建议您使用 MediaStore API 打开您的应用尚未创建的文件。 - 如果分区存储不可用或未使用,集合将显示所有类型的媒体文件。
2.1 请求权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
请勿多此一举为搭载 Android 10 或更高版本的设备请求存储相关权限。您的应用可以提供明确定义的媒体集合,包括 MediaStore.Downloads 集合,而无需请求任何存储相关权限。例如,如果您正在开发一款相机应用,您无需请求存储相关权限,因为您的应用拥有您将写入媒体库的图片。
如需访问由其他应用创建的文件,必须满足以下所有条件:
如果您的应用在搭载 Android 9 或更低版本的设备上使用,或者您的应用暂时停用分区存储,您必须请求 READ_EXTERNAL_STORAGE 权限才能访问媒体文件。如果要修改媒体文件,您还必须请求 WRITE_EXTERNAL_STORAGE 权限。
如果您的应用以 Android 10(API 级别 29)或更高版本为目标平台,为了使您的应用从照片中检索未编辑的 Exif 元数据,您需要在应用的清单中声明 ACCESS_MEDIA_LOCATION 权限,然后在运行时请求此权限。
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
2.2 检查媒体库更新
如需更可靠地访问媒体文件,尤其是在应用缓存来自媒体库的 URI 或数据时,请检查媒体库版本与上次同步媒体数据时相比是否发生了变化。如需执行此更新检查,请调用 [getVersion() ](https://developer.android.google.cn/reference/android/provider/MediaStore?hl=zh-cn#getVersion(android.content.Context, java.lang.String))。返回的版本是一个唯一字符串,该字符串会在媒体库发生重大变化时随之变化。如果返回的版本与上次同步的版本不同,请重新扫描并重新同步应用的媒体缓存。
2.3 查询媒体集合
例子:查询时长超过5分钟的媒体,使用如下类似SQL的选择语句:
class Video {
private final Uri uri;
private final String name;
private final int duration;
private final int size;
public Video(Uri uri, String name, int duration, int size) {
this.uri = uri;
this.name = name;
this.duration = duration;
this.size = size;
}
}
List<Video> videoList = new ArrayList<Video>();
Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
}
String[] projection = new String[] {
MediaStore.Video.Media._ID,
MediaStore.Video.Media.DISPLAY_NAME,
MediaStore.Video.Media.DURATION,
MediaStore.Video.Media.SIZE
};
String selection = MediaStore.Video.Media.DURATION +
" >= ?";
String[] selectionArgs = new String[] {
String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
};
String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC";
try (Cursor cursor = getApplicationContext().getContentResolver().query(
collection,
projection,
selection,
selectionArgs,
sortOrder
)) {
int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
int nameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME);
int durationColumn =
cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION);
int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);
while (cursor.moveToNext()) {
long id = cursor.getLong(idColumn);
String name = cursor.getString(nameColumn);
int duration = cursor.getInt(durationColumn);
int size = cursor.getInt(sizeColumn);
Uri contentUri = ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id);
videoList.add(new Video(contentUri, name, duration, size));
}
}
|