You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 01:01:12