在 targetSdkVersion 31
中,将下载的网络视频保存到手机相册中涉及几个关键步骤。由于 Android 12(API 级别 31)引入了更多的隐私和安全限制,特别是作用域存储(Scoped Storage),因此你需要遵循这些限制来保存文件。
以下是将下载的网络视频保存到手机相册的步骤:
- 请求必要的权限 :
- 对于网络下载,你可能需要
INTERNET
权限。 - 对于保存到外部存储,你需要考虑作用域存储的限制。如果你的应用面向的是 Android 10(API 级别 29)及以上版本,并且你的应用没有声明
requestLegacyExternalStorage
属性为true
,则你需要使用MediaStore
API 来保存文件,并且不需要WRITE_EXTERNAL_STORAGE
权限。
- 对于网络下载,你可能需要
- 下载视频文件 :
- 使用适当的网络库(如 OkHttp, Retrofit, Volley 等)从网络下载视频文件。
- 将下载的视频文件保存到应用的私有目录(例如,使用
getExternalFilesDir(null)
或getCacheDir()
)。
- 使用 MediaStore 保存视频到相册 :
- 创建一个
ContentValues
对象来存储视频文件的元数据。 - 使用
MediaStore.Video.Media.getExternalContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
获取外部存储中视频内容的 URI。 - 调用
ContentResolver.insert()
方法来创建一个新的视频条目。 - 使用
ContentResolver.openOutputStream()
获取输出流,并将下载的视频文件数据写入该输出流。
- 创建一个
- 更新媒体数据库 :
- 在视频文件写入后,你可能需要通知媒体扫描器扫描该文件,以便它出现在相册中。这可以通过发送一个
Intent
来实现,使用MediaScannerConnection.SCAN_FILE_ACTION
。
- 在视频文件写入后,你可能需要通知媒体扫描器扫描该文件,以便它出现在相册中。这可以通过发送一个
下面是一个简化的代码示例,展示了如何实现这一过程:
java
// 假设你已经从网络下载了视频,并且将其保存在了 privateDir 路径下
File videoFile = new File(privateDir, "downloaded_video.mp4");
// 使用 MediaStore API 保存视频到外部存储
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DISPLAY_NAME, videoFile.getName());
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);
Uri collection = MediaStore.Video.Media.getExternalContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri videoUri = getContentResolver().insert(collection, values);
try (OutputStream outputStream = getContentResolver().openOutputStream(videoUri)) {
Files.copy(videoFile.toPath(), outputStream);
} catch (IOException e) {
e.printStackTrace();
}
// 通知媒体扫描器扫描新文件
MediaScannerConnection.scanFile(this,
new String[]{videoFile.toString()},
new String[]{"video/*"},
null);
请注意,由于作用域存储的限制,你可能无法直接将文件保存到公共目录(如 Environment.DIRECTORY_MOVIES
)。如果你的应用没有请求 MANAGE_EXTERNAL_STORAGE
权限,或者用户没有授予该权限,你可能需要将文件保存到应用的私有目录,并通过 MediaStore
创建一个指向该文件的引用。
此外,始终确保你的应用遵循最新的 Android 存储最佳实践,并考虑到用户隐私和数据安全。
另外
在 Android 10(API 级别 29)及以上版本,由于作用域存储(Scoped Storage)的引入,直接获取文件在外部存储的完整路径变得不再直接可行。相反,您应该使用 ContentResolver
和 ContentValues
将文件保存到 MediaStore
,并通过返回的 Uri
来引用它。这样,视频文件就可以被其他应用(如相册)访问,而不需要知道其实际存储路径。
以下是一个示例,展示了如何将下载的视频保存到 MediaStore
并获取其 Uri
:
java
private Uri saveVideoToMediaStore(Context context, File videoFile) throws IOException {
// 创建一个 ContentValues 对象来存储视频文件的元数据
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DISPLAY_NAME, videoFile.getName());
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES);
// 获取外部存储中视频内容的 Uri
Uri collection = MediaStore.Video.Media.getExternalContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
// 使用 ContentResolver 插入一个新的视频条目到 MediaStore
Uri videoUri = context.getContentResolver().insert(collection, values);
// 使用 ContentResolver 打开一个输出流来写入视频文件
try (OutputStream outputStream = context.getContentResolver().openOutputStream(videoUri)) {
Files.copy(videoFile.toPath(), outputStream);
}
// 通知媒体扫描器扫描新文件,使其出现在相册中
MediaScannerConnection.scanFile(context,
new String[]{videoFile.getAbsolutePath()},
new String[]{"video/*"},
null);
return videoUri;
}
在这个示例中,videoUri
就是您需要的 Uri
,它代表了保存在 MediaStore
中的视频文件。您可以使用这个 Uri
来在其他地方引用该视频,例如在一个 Intent
中传递给相册应用。
请注意,由于作用域存储的限制,您无法直接从这个 Uri
获取文件的完整路径。如果您需要这个路径用于某些特定的操作(比如在其他应用中分享文件),您可能需要使用 FileProvider
来创建一个临时访问权限的 Uri
。
如果您确实需要获取视频文件的路径用于某些特殊情况(比如备份到云存储),您可以使用 ContentResolver
和 openFileDescriptor
方法来获取一个 ParcelFileDescriptor
,然后从中获取文件描述符,并最终转换为 File
对象。但请注意,这样做可能会违反 Android 的隐私政策,因此应该谨慎使用,并确保您的应用遵循了所有的最佳实践和隐私指南。
如果您的应用需要兼容低版本的 Android(低于 API 级别 29),您可能需要采用不同的方法来保存视频到外部存储,因为作用域存储(Scoped Storage)在这些版本上不可用。在这种情况下,您可能需要请求 WRITE_EXTERNAL_STORAGE
权限,并将文件直接保存到公共目录(如 /sdcard/DCIM/Camera/
)中。
以下是一个兼容低版本的示例,展示如何保存视频到外部存储并获取其路径:
java
private String saveVideoToExternalStorage(Context context, File videoFile) {
// 确保有写入外部存储的权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// 请求写入外部存储的权限
ActivityCompat.requestPermissions(
(Activity) context,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
// 等待用户授权
return null;
}
} else {
// 对于低于 Android 10 的版本,您可以在 AndroidManifest.xml 中声明权限
}
// 获取外部存储的目录
File externalStorageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
if (!externalStorageDir.exists()) {
if (!externalStorageDir.mkdirs()) {
Log.e("VideoSave", "Failed to create directory");
return null;
}
}
// 将视频文件复制到外部存储的目录
File destinationFile = new File(externalStorageDir, videoFile.getName());
try {
Files.copy(videoFile.toPath(), destinationFile.toPath());
return destinationFile.getAbsolutePath();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
在这个示例中,我们首先检查应用是否有写入外部存储的权限。如果没有,我们会请求这个权限。一旦获得权限,我们就将视频文件复制到外部存储的 /sdcard/Movies/
目录下,并返回文件的绝对路径。
请注意,从 Android 10(API 级别 29)开始,推荐的做法是使用 MediaStore
API,因为它提供了更好的隐私控制和兼容性。如果您的应用需要支持 Android 10 及更高版本,强烈建议使用 MediaStore
方法。如果您的应用需要兼容低版本,并且您确定您的用户主要使用这些低版本设备,那么您可以使用上述方法。但请记住,随着时间的推移,低版本设备的用户群体可能会逐渐减少,因此最好尽可能使用最新的 API 和实践。