保存位图时图像名称和类型错误 Android 8

Wrong image name and type when saving bitmap Android 8

我喜欢将位图保存为 PNG 或 JPG(均无效),但似乎使用的内容值无法按预期工作。

  1. 文件名不正确。
  2. 文件类型不正确。

我错过了什么? 适用于 Android 10,但不适用于 Android 8

fun Bitmap.save(context: Context) {
    val contentResolver = context.contentResolver

    val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "test.png")
        put(MediaStore.MediaColumns.TITLE, "test")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
            put(MediaStore.MediaColumns.IS_PENDING, 1)
        }
    }

    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    val uri = contentResolver.insert(contentUri, contentValues)
    if (uri != null) {
        try {
            contentResolver.openFileDescriptor(uri, "w", null)?.use {
                if (it.fileDescriptor != null) {
                    with(FileOutputStream(it.fileDescriptor)) {
                        compress(
                            Bitmap.CompressFormat.PNG,
                            DEFAULT_IMAGE_QUALITY,
                            this
                        )
                        flush()
                        close()
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentValues.clear()
            contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0)
            contentResolver.update(uri, contentValues, null, null)
        }

        MediaScannerConnection.scanFile(context, arrayOf(uri.toString()), null, null)
    }
    recycle()
}

实际文件名是1592205828045(一些时间戳)

实际文件类型是 jpg0B - 因为没有正确保存?

您正在创建文件,但您仍然需要向其中写入 Bitmap

fun Bitmap.save(context: Context) {

    ...

    val bitmap = this
    val maxImageQuality = 100

    val uri = contentResolver.insert(contentUri, contentValues)
    if (uri != null) {
        try {
            contentResolver.openFileDescriptor(uri, "w", null)?.use {
                if (it.fileDescriptor != null) {
                    with(FileOutputStream(it.fileDescriptor)) {
                        bitmap.compress(
                            Bitmap.CompressFormat.PNG,
                            maxImageQuality, this
                        )
                        flush()
                        close()
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

        // release pending status of the file
        contentValues.clear()
        contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
        contentResolver.update(uri, contentValues, null, null)

        // notify media scanner there's a new picture
        MediaScannerConnection.scanFile(context, arrayOf(uri.toString()), null, null)
    }
    // don't forget to recycle the bitmap when you don't need it any longer
    bitmap.recycle()
}

您必须维护 2 种将图像保存到共享存储的不同方式。这个 post 很好地涵盖了它。在较旧的 phone 中使用 Media Store API 会导致您所描述的问题。一些代码示例(在 Android 8、10 和 11 中测试)。

将这些添加到您的清单中

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<!--  File save functions handles this  -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"
    tools:ignore="ScopedStorage" />

为您的应用添加权限检查(未提供代码)

当您准备好位图时,调用这些函数之一(取决于应用程序当前 运行 上 phone 的 SDK 版本)

    //TODO - bitmap needs null check
      val bitmap = BitmapFactory.decodeFile(bitmapFile.canonicalPath)

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
         {
           saveBitmapPreQ(bitmap)
         } else {
           saveBitmapPostQ(bitmap)
         }

最后是 saveBitmapPreQsaveBitmapPostQ

的实现
@Suppress("DEPRECATION") // Check is preformed on function call
private fun saveBitmapPreQ(thisBitmap: Bitmap){
    Log.d("HOME_4", "in pre Q")
    val pictureDirectory = 
   File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), 
   "MyFolder")

    if (!pictureDirectory.exists()){
        pictureDirectory.mkdir()
    }

    val dateTimeStamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
    val name = "Image_$dateTimeStamp"

    val bitmapFile = File(pictureDirectory, "$name.png")

    try {
        val fileOutputStream = bitmapFile.outputStream()
        thisBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)
        fileOutputStream.flush()
        fileOutputStream.close()
    } catch (e: Exception) {
        Log.d("HOME_5", "Pre Q error $e")
    }

}

private fun saveBitmapPostQ(thisBitmap: Bitmap){
    Log.d("HOME_6", "in post Q")
    val dateTimeStamp = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
    val name = "Image_$dateTimeStamp"
    val relativePath = Environment.DIRECTORY_PICTURES + File.separator + "MyFolder"


    val contentValues = ContentValues().apply {
        put(MediaStore.Images.ImageColumns.DISPLAY_NAME, name)

        put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
        put(MediaStore.MediaColumns.TITLE, name)
        put(MediaStore.Images.ImageColumns.RELATIVE_PATH, relativePath)
    }

    val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    var outputStream: OutputStream? = null
    var uri: Uri? = null

    try {
        uri = contentResolver.insert(contentUri, contentValues)

        if (uri == null){
            throw IOException("Failed to create new MediaStore record.")
        }

        outputStream = contentResolver.openOutputStream(uri)

        if (outputStream == null){
            throw IOException("Failed to get output stream.")
        }

        if (!thisBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)){
            throw IOException("Failed to save bitmap.")
        }
    } catch (e: IOException){
        if (uri != null)
        {
            contentResolver.delete(uri, null, null)
        }

        throw IOException(e)
    }

    finally {
        outputStream?.close()
    }
}

我在其中留下了日志消息,以帮助您了解流程。在 saveBitmapPostQ 函数中,我采用了一些快捷方式。请阅读创建新文件标题下的post,了解如何进一步改进该功能。