Android 10以下无需WRITE_STORAGE权限保存文件至下载文件夹
解决方案:Android全版本适配下载文件夹文件保存
首先明确:没有官方或第三方支持库能让Android 9及以下系统的MediaStore直接提供下载文件夹的URI,但可以通过以下几种方案实现适配,尽量减少权限弹窗对用户的打扰:
方案1:利用DownloadManager服务(推荐)
DownloadManager是系统内置服务,本身拥有写入公共下载文件夹的权限,因此在所有Android版本中,应用调用它保存文件到下载文件夹时,无需申请WRITE_EXTERNAL_STORAGE权限,也不会弹出授权弹窗。
实现思路
如果你的文件是本地已有的字节流或文件:
- 先将文件写入应用内部存储(无需权限);
- 通过FileProvider生成系统可访问的Content URI;
- 调用DownloadManager将文件复制到公共下载文件夹。
示例代码:
// 1. 将文件写入应用内部存储 val inputStream: InputStream = ... // 你的文件输入流 val internalTempFile = File(filesDir, "temp_file.ext") FileOutputStream(internalTempFile).use { output -> inputStream.copyTo(output) } // 2. 通过FileProvider生成Content URI(需提前配置) val contentUri = FileProvider.getUriForFile( context, "${packageName}.fileprovider", internalTempFile ) // 3. 调用DownloadManager完成保存 val downloadManager = getSystemService(DOWNLOAD_SERVICE) as DownloadManager val request = DownloadManager.Request(contentUri) .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "final_file.ext") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) // 可选:显示下载完成通知 downloadManager.enqueue(request) // 可选:清理内部存储的临时文件 internalTempFile.delete()
需要在AndroidManifest.xml中配置FileProvider:
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
并在res/xml/file_paths.xml中添加路径配置:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="internal_files" path="." /> </paths>
方案2:版本区分的权限适配
如果不想依赖DownloadManager,可以针对系统版本做逻辑拆分:
- Android 10及以上:直接使用MediaStore插入下载文件夹条目,无需权限;
- Android 9及以下:申请
WRITE_EXTERNAL_STORAGE权限,通过旧API获取下载文件夹路径并写入文件。
示例代码:
private const val PERMISSION_REQUEST_CODE = 1001 fun saveFileToDownloads(context: Context, fileName: String, content: ByteArray) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+:用MediaStore直接写入 val contentValues = ContentValues().apply { put(MediaStore.Downloads.DISPLAY_NAME, fileName) put(MediaStore.Downloads.MIME_TYPE, "application/octet-stream") put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) } val uri = context.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) uri?.let { context.contentResolver.openOutputStream(it).use { outputStream -> outputStream?.write(content) } } } else { // Android <10:先申请权限再写入 if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( context as Activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE ) return } val downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) val targetFile = File(downloadsDir, fileName) FileOutputStream(targetFile).use { it.write(content) } } }
方案3:引导用户使用SAF(存储访问框架)
对于Android <10的设备,也可以引导用户通过SAF选择下载文件夹,获取写入权限,这种方式无需申请WRITE_EXTERNAL_STORAGE,但需要用户手动操作一次。
示例代码:
private const val SAF_REQUEST_CODE = 1002 fun openSAFForDownloads(context: Context) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) // 默认定位到下载文件夹 val downloadsUri = DocumentsContract.buildDocumentUriUsingTree( MediaStore.Downloads.EXTERNAL_CONTENT_URI, DocumentsContract.getTreeDocumentId(MediaStore.Downloads.EXTERNAL_CONTENT_URI) ) intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, downloadsUri) (context as Activity).startActivityForResult(intent, SAF_REQUEST_CODE) } // 在Activity的onActivityResult中处理返回结果 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == SAF_REQUEST_CODE && resultCode == Activity.RESULT_OK) { data?.data?.let { treeUri -> // 持久化权限,避免重复请求 contentResolver.takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) // 写入文件 val downloadsDir = DocumentFile.fromTreeUri(this, treeUri) downloadsDir?.createFile("application/octet-stream", "final_file.ext")?.let { fileUri -> contentResolver.openOutputStream(fileUri.uri).use { it?.write(content) } } } } }
内容的提问来源于stack exchange,提问作者MichiBros
相关产品推荐
相关产品推荐

