如何使用 MediaStore 在 Android Q 中保存图像?

2023-12-05

这是新 Android Q 的链接范围存储.

根据这个 Android 开发者最佳实践博客, storing shared media files(这是我的情况)应该使用媒体商店 API.

深入研究文档,我找不到相关的功能。

这是我在 Kotlin 中的试用:

val bitmap = getImageBitmap() // I have a bitmap from a function or callback or whatever
val name = "example.png" // I have a name

val picturesDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!

// Make sure the directory "Android/data/com.mypackage.etc/files/Pictures" exists
if (!picturesDirectory.exists()) {
    picturesDirectory.mkdirs()
}

try {
    val out = FileOutputStream(File(picturesDirectory, name))
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)

    out.flush()
    out.close()

} catch(e: Exception) {
    // handle the error
}

结果是我的图片保存在这里Android/data/com.mypackage.etc/files/Pictures/example.png正如最佳实践博客中所述Storing app-internal files


我的问题是:

如何使用 MediaStore API 保存图像? Java 中的答案同样可以接受。


EDIT

但还有3点。

这是我的代码:

val name = "Myimage"
val relativeLocation = Environment.DIRECTORY_PICTURES + File.pathSeparator + "AppName"

val contentValues  = ContentValues().apply {
    put(MediaStore.Images.ImageColumns.DISPLAY_NAME, name)
    put(MediaStore.MediaColumns.MIME_TYPE, "image/png")

    // without this part causes "Failed to create new MediaStore record" exception to be invoked (uri is null below)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        put(MediaStore.Images.ImageColumns.RELATIVE_PATH, relativeLocation)
    }
}

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

try {
    uri = contentResolver.insert(contentUri, contentValues)
    if (uri == null)
    {
        throw IOException("Failed to create new MediaStore record.")
    }

    stream = contentResolver.openOutputStream(uri)

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

    if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream))
    {
        throw IOException("Failed to save bitmap.")
    }


    Snackbar.make(mCoordinator, R.string.image_saved_success, Snackbar.LENGTH_INDEFINITE).setAction("Open") {
        val intent = Intent()
        intent.type = "image/*"
        intent.action = Intent.ACTION_VIEW
        intent.data = contentUri
        startActivity(Intent.createChooser(intent, "Select Gallery App"))
    }.show()

} catch(e: IOException) {
    if (uri != null)
    {
        contentResolver.delete(uri, null, null)
    }

    throw IOException(e)

}
finally {
    stream?.close()
}

1-保存的图像没有正确的名称“Myimage.png”

我尝试使用“Myimage”和“Myimage.PNG”,但都不起作用。

图像的名称始终由数字组成,例如:

1563468625314.jpg

这给我们带来了第二个问题:

2-图像另存为jpg即使我以以下格式压缩位图png.

不是什么大问题。只是好奇为什么。

3-relativeLocation 位会在低于 Android Q 的设备上导致异常。 在使用“Android Version Check”if语句后,图像将直接保存在根目录中Pictures folder.


EDIT 2

变成:

uri = contentResolver.insert(contentUri, contentValues)
if (uri == null)
{
    throw IOException("Failed to create new MediaStore record.")
}

val cursor = contentResolver.query(uri, null, null, null, null)
DatabaseUtils.dumpCursor(cursor)
cursor!!.close()

stream = contentResolver.openOutputStream(uri)

这是日志

I/System.out: >>>>> Dumping cursor android.content.ContentResolver$CursorWrapperInner@76da9d1
I/System.out: 0 {
I/System.out:    _id=25417
I/System.out:    _data=/storage/emulated/0/Pictures/1563640732667.jpg
I/System.out:    _size=null
I/System.out:    _display_name=Myimage
I/System.out:    mime_type=image/png
I/System.out:    title=1563640732667
I/System.out:    date_added=1563640732
I/System.out:    is_hdr=null
I/System.out:    date_modified=null
I/System.out:    description=null
I/System.out:    picasa_id=null
I/System.out:    isprivate=null
I/System.out:    latitude=null
I/System.out:    longitude=null
I/System.out:    datetaken=null
I/System.out:    orientation=null
I/System.out:    mini_thumb_magic=null
I/System.out:    bucket_id=-1617409521
I/System.out:    bucket_display_name=Pictures
I/System.out:    width=null
I/System.out:    height=null
I/System.out:    is_hw_privacy=null
I/System.out:    hw_voice_offset=null
I/System.out:    is_hw_favorite=null
I/System.out:    hw_image_refocus=null
I/System.out:    album_sort_index=null
I/System.out:    bucket_display_name_alias=null
I/System.out:    is_hw_burst=0
I/System.out:    hw_rectify_offset=null
I/System.out:    special_file_type=0
I/System.out:    special_file_offset=null
I/System.out:    cam_perception=null
I/System.out:    cam_exif_flag=null
I/System.out: }
I/System.out: <<<<<

我注意到title为了匹配名称,所以我尝试添加:

put(MediaStore.Images.ImageColumns.TITLE, name)

它仍然不起作用,这是新日志:

I/System.out: >>>>> Dumping cursor android.content.ContentResolver$CursorWrapperInner@51021a5
I/System.out: 0 {
I/System.out:    _id=25418
I/System.out:    _data=/storage/emulated/0/Pictures/1563640934803.jpg
I/System.out:    _size=null
I/System.out:    _display_name=Myimage
I/System.out:    mime_type=image/png
I/System.out:    title=Myimage
I/System.out:    date_added=1563640934
I/System.out:    is_hdr=null
I/System.out:    date_modified=null
I/System.out:    description=null
I/System.out:    picasa_id=null
I/System.out:    isprivate=null
I/System.out:    latitude=null
I/System.out:    longitude=null
I/System.out:    datetaken=null
I/System.out:    orientation=null
I/System.out:    mini_thumb_magic=null
I/System.out:    bucket_id=-1617409521
I/System.out:    bucket_display_name=Pictures
I/System.out:    width=null
I/System.out:    height=null
I/System.out:    is_hw_privacy=null
I/System.out:    hw_voice_offset=null
I/System.out:    is_hw_favorite=null
I/System.out:    hw_image_refocus=null
I/System.out:    album_sort_index=null
I/System.out:    bucket_display_name_alias=null
I/System.out:    is_hw_burst=0
I/System.out:    hw_rectify_offset=null
I/System.out:    special_file_type=0
I/System.out:    special_file_offset=null
I/System.out:    cam_perception=null
I/System.out:    cam_exif_flag=null
I/System.out: }
I/System.out: <<<<<

而我却无法改变date_added到一个名字。

And MediaStore.MediaColumns.DATA已弃用。


尝试下一个方法。 Android Q(及更高版本)已经负责创建文件夹(如果它们不存在)。该示例被硬编码为输出到DCIM文件夹。如果您需要子文件夹,请附加子文件夹名称,如下所示:

final String relativeLocation = Environment.DIRECTORY_DCIM + File.separator + “YourSubforderName”;

考虑压缩格式应该与mime-type参数相关。例如,对于 JPEG 压缩格式,mime 类型将为“image/jpeg”,依此类推。也许您可能还想将压缩质量作为参数传递,在本例中硬编码为 95。

Java:

@NonNull
public Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap,
                      @NonNull final Bitmap.CompressFormat format,
                      @NonNull final String mimeType,
                      @NonNull final String displayName) throws IOException {

    final ContentValues values = new ContentValues();
    values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
    values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
    values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);

    final ContentResolver resolver = context.getContentResolver();
    Uri uri = null;

    try {
        final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        uri = resolver.insert(contentUri, values);

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

        try (final OutputStream stream = resolver.openOutputStream(uri)) {
            if (stream == null)
                throw new IOException("Failed to open output stream.");
         
            if (!bitmap.compress(format, 95, stream))
                throw new IOException("Failed to save bitmap.");
        }

        return uri;
    }
    catch (IOException e) {

        if (uri != null) {
            // Don't leave an orphan entry in the MediaStore
            resolver.delete(uri, null, null);
        }

        throw e;
    }
}

Kotlin:

@Throws(IOException::class)
fun saveBitmap(
    context: Context, bitmap: Bitmap, format: Bitmap.CompressFormat,
    mimeType: String, displayName: String
): Uri {

    val values = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
    }

    val resolver = context.contentResolver
    var uri: Uri? = null

    try {
        uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            ?: throw IOException("Failed to create new MediaStore record.")

        resolver.openOutputStream(uri)?.use {
            if (!bitmap.compress(format, 95, it))
                throw IOException("Failed to save bitmap.")
        } ?: throw IOException("Failed to open output stream.")

        return uri

    } catch (e: IOException) {

        uri?.let { orphanUri ->
            // Don't leave an orphan entry in the MediaStore
            resolver.delete(orphanUri, null, null)
        }

        throw e
    }
}

Kotlin 变体,具有更实用的风格:

@Throws(IOException::class)
fun saveBitmap(
    context: Context, bitmap: Bitmap, format: Bitmap.CompressFormat,
    mimeType: String, displayName: String
): Uri {

    val values = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
        put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
    }

    var uri: Uri? = null

    return runCatching {
        with(context.contentResolver) {
            insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.also {
                uri = it // Keep uri reference so it can be removed on failure

                openOutputStream(it)?.use { stream ->
                    if (!bitmap.compress(format, 95, stream))
                        throw IOException("Failed to save bitmap.")
                } ?: throw IOException("Failed to open output stream.")

            } ?: throw IOException("Failed to create new MediaStore record.")
        }
    }.getOrElse {
        uri?.let { orphanUri ->
            // Don't leave an orphan entry in the MediaStore
            context.contentResolver.delete(orphanUri, null, null)
        }

        throw it
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 MediaStore 在 Android Q 中保存图像? 的相关文章

随机推荐

  • 为什么这个 jQuery 事件不会在 Gmail 中触发?

    我在将 jQuery 事件绑定到 gmail body 时遇到问题 body on click function event console log Entered function 访问 IMDB com 例如 并在 Google Chr
  • 使用 DOMPDF 和重定向创建 PDF

    在 PHP 项目中 我需要创建一个 PDF 文件 并在用户单击 提交 按钮时重定向到另一个页面 我已经成功使用创建了 pdf 文件DOMPDF PDF 创建在单独的文件中完成 PDFRecipt php 当用户单击主页上的按钮时 我已调用该
  • Lambda 表达式返回错误

    这是我的代码 SomeFunction m gt ViewData AllEmployees Where c gt c LeaderID m UserID 它返回此错误 并非所有代码路径都返回 lambda 表达式类型的值System Fu
  • SDK19 启动时相机2 库崩溃的 Android 应用程序

    我在我的应用程序中使用 androidx camera camera2 库 该库适用于 SDK 21 及更高版本 但我希望允许用户在没有camera2支持的情况下启动SDK 19的应用程序 我在代码中检查了 SDK 版本 但应用程序在启动时
  • Laravel 5:找不到“HTML”类

    我刚刚开始使用 Laravel 我处于控制器方法中 我说 return View make scrape data 然后在 scrape blade php 中我有 extends layouts master 最后 在 layouts m
  • 未收到 Firebase Cloud Messaging 的 Android 后台通知

    我搜索了很多有关应用程序在后台或关闭时的通知的信息 顺便说一句 我正在使用 Firebase 云消息传递 这对我不起作用 我使用了 Android 设置 当应用程序位于前台或手机未锁定时 会收到通知 安装后 令牌会正确打印并订阅该主题 当我
  • 如何缩放 HTML5 画布而不使其变得模糊?

    我使用以下标记创建了画布
  • 如何使 UIPickerView 组件环绕?

    我想在 UIPickerView 组件中显示一组连续数字 但让它像 Clock gt Timer 应用程序的秒组件一样环绕 我可以启用的唯一行为类似于计时器应用程序的小时组件 您只能在一个方向上滚动 将行数设置为较大的数字并使其以较高的值开
  • 在字符串中插入空格(Matlab)

    我有一根绳子 S ABACBADECAEF 如何在该字符串中的每 2 个字符之间插入一个空格 预期输出应该是 Out S AB AC BA DE CA EF 有几种方法可以做到这一点 所有这些方法都假设您的字符串长度是even 如果字符数量
  • Bash:简单变量赋值时“找不到命令”

    这是我的脚本的一个简单版本 显示失败 bin bash something false something else blahblah name file ext echo something echo something else ech
  • JPanel 的绘制背景

    我怎样才能告诉paint方法只在JPanel上绘制背景而不是在整个JFrame上 我的 JFrame 大小比 JPanel 大 当我尝试为 JPanel 绘制网格背景时 网格似乎被绘制在整个 JFrame 而不仅仅是 JPanel 上 这里
  • android - 按类型过滤 assetManager.list 文件

    我想从资产中的特定目录获取 html 文件的列表 有代码 gt gt private List
  • 语句“USE @dbname”不起作用,为什么?怎么做?

    我有这个 t sql 片段 DECLARE db name varchar 255 SET db name MY DATABASE assuming there is database called my database USE db n
  • 如何从本身触发 hx-get 调用的 SSE 事件中获取 htmx get 的 url?

    我正在将 django 与 django channels 和 htmx 一起使用 在某些情况下 我的 django 视图将向订阅相关频道的用户发送 SSE 事件 例如通知 其中一些事件 取决于事件名称 需要触发模式弹出窗口 例如电子商务订
  • HTML5 Canvas - 使用鼠标按锚点旋转

    我正在html5的canvas元素中进行开发 我有以下代码 它是可拖动且可调整大小的图像 我怎样才能将它变成可通过锚点旋转 我怎样才能通过锚点提供实时旋转 我看到了其他代码示例 但不知道如何实现它 采样器工作 http jsfiddle n
  • [0,1,2,3].map 工作正常,array.map 给出奇怪的结果

    我正在使用成帧器运动 并且我正在尝试实现交错 以便每个下一个孩子都有一些不错的延迟 有一行关键代码 当我替换时 0 1 2 3 map with recipes map突然间 所有的孩子都被视为一大块 他们不再摇摇欲坠 看看这个demo你一
  • 如何启用/禁用内核 kaslr、smep 和 smap

    我想知道如何从 Linux 内核启用或禁用这 3 个功能 kaslr smep smap 我读过我必须在内核命令行中添加一些内容才能启用此功能 我查看了 proc cmdline 我没有看到任何有关 smep 的信息 但是 当我询问 pro
  • 统计catch块中发生的异常数量

    我正在尝试收集发生异常的所有计数以及异常的名称ConcurrentHashMap这样我就应该知道这个异常发生了多少次 因此 在我的 catch 块中 我有一个映射 它将继续添加异常的名称和出现的总计数 下面是我的代码which I have
  • 如何强制 XDocument 以大写形式输出序言,同时保留缩进和格式?

    I want XDocument输出 XML 序言 例如 大写 这是我目前正在做的事情 但这似乎不起作用 XDocument doc new XDocument new XDeclaration 1 0 UTF 8 bla bla bla
  • 如何使用 MediaStore 在 Android Q 中保存图像?

    这是新 Android Q 的链接范围存储 根据这个 Android 开发者最佳实践博客 storing shared media files 这是我的情况 应该使用媒体商店 API 深入研究文档 我找不到相关的功能 这是我在 Kotlin