Android中Bitmap转PNG后无法在Downloads目录找到文件
问题描述
开发一款设备端人像背景移除应用,已生成ARGB_8888格式的去背景Bitmap,尝试转PNG下载到设备。日志显示下载流程完成,但图库及Downloads目录找不到文件,且已成功申请存储权限。
相关代码
下载按钮点击事件
downloadbtn.setOnClickListener { lifecycleScope.launch(Dispatchers.Default) { Log.v("BeforByteStream", "Inside download onclick coroutine") requestRuntimePermissions() // Permission already granted, proceed with downloading downloadToStorage(outputBitmapdnld) } }
Bitmap转PNG并下载逻辑
private fun downloadToStorage(outputBitmapdnld: Bitmap) { val bytes = ByteArrayOutputStream().use { stream -> outputBitmapdnld.compress(Bitmap.CompressFormat.PNG, 100, stream) stream.toByteArray() } Log.v("AfterByteStream", "Successfully converted to Bytestream!") if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { Log.v("InsideIf", "External storage is available!") val directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) Log.v("DirectoryAccess", "Directory successfully accessed!") val fileName = "bgremove69.png" val file = File(directory, fileName) if (!file.exists()) { file.createNewFile() } FileOutputStream(file).use { fos -> fos.write(bytes) } Log.v("FileWritten", "File is directly downloaded to folder supposedly...") } else { Toast.makeText(this@ImageStaging, "External storage is not available", Toast.LENGTH_SHORT).show() } }
解决方案
1. 修复权限申请时机问题
requestRuntimePermissions()是异步操作,协程中直接调用downloadToStorage会导致权限未通过就执行下载。需等待权限申请结果返回后再执行下载逻辑:
downloadbtn.setOnClickListener { lifecycleScope.launch { Log.v("BeforByteStream", "Inside download onclick coroutine") // 等待权限申请结果 val isGranted = requestRuntimePermissions() if (isGranted) { downloadToStorage(outputBitmapdnld) } else { Toast.makeText(this@ImageStaging, "存储权限未授予,无法下载", Toast.LENGTH_SHORT).show() } } }
注:需确保requestRuntimePermissions()返回Boolean类型的权限申请结果,可通过registerForActivityResult实现权限请求的回调返回。
2. 通知媒体库扫描新文件
Android 10及以上,直接写入公共目录的文件不会自动被媒体库索引,需发送广播通知扫描。在downloadToStorage方法的文件写入完成后添加:
// 通知媒体库扫描新文件 val mediaScanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) val contentUri = Uri.fromFile(file) mediaScanIntent.data = contentUri sendBroadcast(mediaScanIntent)
3. 适配Android 10+的Scoped Storage(推荐方案)
Android 10开始引入Scoped Storage,直接操作Environment.getExternalStoragePublicDirectory在部分设备上会被限制,改用MediaStore写入公共Downloads目录,无需手动通知媒体库,且兼容新存储政策:
private fun downloadToStorage(outputBitmapdnld: Bitmap) { val fileName = "bgremove69.png" val contentValues = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, fileName) put(MediaStore.Images.Media.MIME_TYPE, "image/png") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) put(MediaStore.Images.Media.IS_PENDING, 1) } else { put(MediaStore.Images.Media.DATA, "${Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)}/$fileName") } } val contentResolver = contentResolver val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) uri?.let { contentResolver.openOutputStream(it).use { outputStream -> outputBitmapdnld.compress(Bitmap.CompressFormat.PNG, 100, outputStream) } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { contentValues.clear() contentValues.put(MediaStore.Images.Media.IS_PENDING, 0) contentResolver.update(it, contentValues, null, null) } Log.v("FileWritten", "文件已成功保存到Downloads目录") Toast.makeText(this@ImageStaging, "下载完成", Toast.LENGTH_SHORT).show() } ?: run { Toast.makeText(this@ImageStaging, "保存文件失败", Toast.LENGTH_SHORT).show() } }
4. 验证文件是否真的写入
在文件写入后添加日志打印文件绝对路径及大小,确认文件是否存在:
Log.v("FileInfo", "文件路径:${file.absolutePath},文件大小:${file.length()}字节")
也可通过adb命令直接查看:
adb shell ls /storage/emulated/0/Download/bgremove69.png
内容的提问来源于stack exchange,提问作者Rudranil Bhattacharjee
相关产品推荐
相关产品推荐

