尝试将 PDF 文件保存在下载目录中,但在 Android Q 后
getExternalStoragePublicDirectory
被完全弃用后,无法将文件保存在 DCIM 或 Pictures 文件夹之外的任何其他位置,因为在尝试保存文件时抛出以下异常在那里。
IllegalArgumentException:不允许主目录下载 内容://媒体/外部/图像/媒体;允许的目录是 [DCIM, 图片]
有以下代码。
private fun saveFile(input: ByteArray) {
val fileName = "myFile.pdf"
val outputStream = if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(directory, fileName)
FileOutputStream(file)
} else {
val resolver = context.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, "images/*")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let {
resolver.openOutputStream(it)
}
}
outputStream?.use { stream ->
stream.write(input)
}
}
显然,当将路径更改为 DIRECTORY_DCIM 时,一切都会按预期工作,但由于要求,文件应像以前一样保存到下载中。 将不胜感激任何帮助。
未设置正确的文件保存 Uri,对于下载,应该是
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
blackapps感谢您的指点。
当我需要的时候,我就这样做。
清单.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<application
android:name=".BaseApp"
android:requestLegacyExternalStorage="true"
IFileManager.kt
interface IFileManager {
fun copyFile(sourceLocation: File, targetLocation: File)
fun copyFileToDownload(filePath: String, fileName: String, fileType: String)
}
文件管理器.kt
class FileManager @Inject constructor(
private val context: BaseApp
) : IFileManager {
override fun copyFile(sourceLocation: File, targetLocation: File) {
CoroutineScope(Dispatchers.IO).launch {
runCatching {
try {
val inSTR = FileInputStream(sourceLocation)
val out = FileOutputStream(targetLocation)
val buf = ByteArray(4096)
var len = 0
while (true) {
val read = inSTR.read(buf)
if (read == -1) {
break
}
out.write(buf, 0, read)
len += read
}
inSTR.close()
out.close()
Log.d("copyFile", "runCatching OK")
} catch (e: Exception) {
Log.d("copyFile", "Exception ${e.message}")
}
}
}
}
@RequiresApi(Build.VERSION_CODES.Q)
override fun copyFileToDownload(filePath: String, fileName: String, fileType: String) {
try {
val contentResolver: ContentResolver = context.contentResolver
val values = ContentValues()
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
values.put(MediaStore.MediaColumns.MIME_TYPE, fileType)
values.put(MediaStore.MediaColumns.TITLE, fileName)
values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000)
values.put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis())
values.put(MediaStore.MediaColumns.IS_PENDING, 1)
val mediaUri = contentResolver.insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
values
)
val bytes: ByteArray = Files.readAllBytes(Paths.get(filePath))
contentResolver.openOutputStream(mediaUri!!).use { out ->
out!!.write(bytes)
}
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
values.put(
MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_DOWNLOADS
)
contentResolver.update(mediaUri, values, null, null)
Log.d("copyFileToDownload", "OK: ${values.get(MediaStore.MediaColumns.RELATIVE_PATH)}")
}catch (e: Exception){
Log.d("copyFileToDownload", "Exception: ${e.message}")
}
}
}
MainActivity.kt
@AndroidEntryPoint
open class MainActivity : ComponentActivity() {
@Inject
lateinit var iFileManager: IFileManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savePdf(path = "your file path")
}
private fun savePdf(path: String){
val result = path.split("/")
val fileName = result[result.size - 1]
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
iFileManager.copyFileToDownload(
filePath = path,
fileName = fileName,
fileType = "application/pdf"
)
}else{
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS+"/$fileName")
iFileManager.copyFile(
sourceLocation = File(path),
targetLocation = dir
)
}
}
}
在 Android 11 上,应用程序无法再访问外部存储中任何其他应用程序的专用、特定于应用程序的目录中的文件。为了保护用户隐私,在运行 Android 11 或更高版本的设备上,系统进一步限制您的应用对其他应用私有目录的访问。
请求 MANAGE_EXTERNAL_STORAGE 权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage"/>