Android R中使用MediaStore API下载并保存图片/视频至共享文件夹的完整实现示例
完整实现:Android R(compileSdk 30)下通过MediaStore保存网络图片/视频到共享Downloads目录
正好适配你提到的场景——Android R环境下替代废弃的getExternalStoragePublicDirectory,用MediaStore API把网络资源存到共享的Downloads文件夹。下面是完整的可运行方案:
一、权限准备
首先在AndroidManifest.xml里配置必要权限:
<!-- 网络下载必须 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 仅Android 10及以下需要写入权限,Android R+无需此权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
二、完整代码实现
我整理了一个工具类,包含网络下载、MediaStore写入的完整逻辑,同时处理了异常和流关闭的细节:
MediaStore文件保存工具类
import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.net.Uri; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; public class MediaStoreSaveHelper { private static final String TAG = "MediaStoreSaveHelper"; /** * 从网络下载文件并保存到Downloads共享目录 * @param context 上下文 * @param fileUrl 资源URL * @param fileName 保存的文件名(需带后缀,比如"bulldog.jpg") * @param mimeType 文件MIME类型,比如"image/jpeg"、"video/mp4" * @return 保存成功的Uri,失败返回null */ public static Uri saveFileToDownloads(Context context, String fileUrl, String fileName, String mimeType) { ContentResolver resolver = context.getContentResolver(); ContentValues contentValues = new ContentValues(); // 设置文件基本信息 contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); // 指定保存到Downloads目录 contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS); // 根据文件类型选择对应的ContentUri(图片/视频通用Downloads的Uri也可以) Uri contentUri; if (mimeType.startsWith("image/")) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if (mimeType.startsWith("video/")) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else { contentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI; } Uri uri = null; OutputStream outputStream = null; InputStream inputStream = null; HttpURLConnection connection = null; try { // 插入文件记录,获取可写入的Uri uri = resolver.insert(contentUri, contentValues); if (uri == null) { Log.e(TAG, "Failed to create new MediaStore record"); return null; } // 打开输出流准备写入 outputStream = resolver.openOutputStream(uri); if (outputStream == null) { Log.e(TAG, "Failed to open output stream for Uri: " + uri); return null; } // 发起网络请求获取输入流 URL url = new URL(fileUrl); connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(10000); connection.setReadTimeout(10000); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { Log.e(TAG, "Server returned error code: " + connection.getResponseCode()); return null; } inputStream = new BufferedInputStream(connection.getInputStream()); // 流复制操作 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); } outputStream.flush(); Log.d(TAG, "File saved successfully to: " + uri); } catch (IOException e) { Log.e(TAG, "Error saving file", e); // 如果写入失败,删除已插入的空记录 if (uri != null) { resolver.delete(uri, null, null); uri = null; } } finally { // 关闭所有流和连接 try { if (outputStream != null) outputStream.close(); if (inputStream != null) inputStream.close(); if (connection != null) connection.disconnect(); } catch (IOException e) { Log.e(TAG, "Error closing resources", e); } } return uri; } }
在Activity中调用示例
注意:网络操作不能在主线程执行,这里用AsyncTask(如果是Java 11+推荐用ExecutorService,或者Kotlin协程):
import android.os.AsyncTask; import android.os.Bundle; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; public class SaveFileActivity extends AppCompatActivity { private static final String IMAGE_URL = "https://cdn.pixabay.com/photo/2020/04/21/06/41/bulldog-5071407_1280.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_save_file); // 触发保存操作(比如点击按钮时调用) new SaveFileTask().execute(); } private class SaveFileTask extends AsyncTask<Void, Void, Uri> { @Override protected Uri doInBackground(Void... voids) { // 调用工具类保存图片到Downloads目录 return MediaStoreSaveHelper.saveFileToDownloads( SaveFileActivity.this, IMAGE_URL, "bulldog_download.jpg", // 自定义文件名 "image/jpeg" // MIME类型 ); } @Override protected void onPostExecute(Uri uri) { super.onPostExecute(uri); if (uri != null) { Toast.makeText(SaveFileActivity.this, "图片已保存到Downloads", Toast.LENGTH_LONG).show(); } else { Toast.makeText(SaveFileActivity.this, "保存失败", Toast.LENGTH_SHORT).show(); } } } }
三、关键细节说明
- 文件类型适配:代码根据MIME类型自动选择对应的
ContentUri,确保图片/视频都能被系统媒体库正确识别 - 异常处理:如果写入失败,会自动删除MediaStore中插入的空记录,避免产生无效文件
- 权限兼容:针对Android 10及以下保留了
WRITE_EXTERNAL_STORAGE权限配置,Android R+无需此权限即可写入共享目录 - 线程安全:网络操作和文件写入都在后台线程执行,避免阻塞主线程导致ANR
内容的提问来源于stack exchange,提问作者Kanaiya Katarmal
相关产品推荐
相关产品推荐

