如何使用 MediaStore 在 Android Q 中保存图像? [英] How to save an image in Android Q using MediaStore?

查看:43
本文介绍了如何使用 MediaStore 在 Android Q 中保存图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

根据 这个 Android 开发者最佳实践博客存储共享媒体文件(这是我的情况)应该使用MediaStore API.

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

这是我在 Kotlin 中的试验:

val bitmap = getImageBitmap()//我有一个来自函数或回调或其他任何东西的位图val name = "example.png"//我有一个名字val PicturesDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!//确保目录Android/data/com.mypackage.etc/files/Pictures"存在如果 (!picturesDirectory.exists()) {图片目录.mkdirs()}尝试 {val out = FileOutputStream(File(picturesDirectory, name))bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)out.flush()关闭()} catch(e: 异常) {//处理错误}

结果是我的图像保存在这里 Android/data/com.mypackage.etc/files/Pictures/example.png 如最佳实践博客中描述的 Storing app-内部文件

<小时>

我的问题是:

如何使用 MediaStore API 保存图片?

<小时>

Java 中的答案同样可以接受.

提前致谢!

<小时>

编辑

感谢 PerracoLabs

那真的很有帮助!

但还有 3 点.

这是我的代码:

val name = "Myimage"val relativeLocation = Environment.DIRECTORY_PICTURES + File.pathSeparator + "AppName"val contentValues = ContentValues().apply {放置(MediaStore.Images.ImageColumns.DISPLAY_NAME,名称)放置(MediaStore.MediaColumns.MIME_TYPE,图像/png")//没有这部分会导致调用Failed to create new MediaStore record"异常(下面的uri为空)如果 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {放置(MediaStore.Images.ImageColumns.RELATIVE_PATH,relativeLocation)}}val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URIvar 流:输出流?= 空var uri:乌里?= 空尝试 {uri = contentResolver.insert(contentUri, contentValues)如果(uri == null){throw IOException("无法创建新的 MediaStore 记录.")}流 = contentResolver.openOutputStream(uri)如果(流==空){throw IOException("获取输出流失败.")}if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, 流)){throw IOException("无法保存位图.")}Snackbar.make(mCoordinator, R.string.image_saved_success, Snackbar.LENGTH_INDEFINITE).setAction("Open") {val 意图 = 意图()intent.type = "图像/*"intent.action = Intent.ACTION_VIEWintent.data = contentUristartActivity(Intent.createChooser(intent, "Select Gallery App"))}.展示()} catch(e: IOException) {如果(uri!= null){contentResolver.delete(uri, null, null)}抛出 IOException(e)}最后 {流? .close()}

1- 保存的图像没有得到正确的名称Myimage.png"

我尝试使用Myimage"和Myimage.PNG",但都没有奏效.

图像的名称总是由数字组成,例如:

1563468625314.jpg

这就引出了第二个问题:

2- 即使我以 png 格式压缩位图,图像也保存为 jpg.

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

3-relativeLocation 位在低于 Android Q 的设备上导致异常.用Android 版本检查"if 语句包围后,图像直接保存在Pictures 文件夹的根目录中.

再次感谢.

<小时>

编辑 2

改为:

uri = contentResolver.insert(contentUri, contentValues)如果(uri == null){throw IOException("无法创建新的 MediaStore 记录.")}val cursor = contentResolver.query(uri, null, null, null, null)DatabaseUtils.dumpCursor(cursor)光标!!.close()流 = contentResolver.openOutputStream(uri)

这是日志

I/System.out:>>>>>转储光标 android.content.ContentResolver$CursorWrapperInner@76da9d1I/System.out: 0 {I/System.out: _id=25417I/System.out: _data=/storage/emulated/0/Pictures/1563640732667.jpgI/System.out: _size=nullI/System.out: _display_name=MyimageI/System.out: mime_type=image/pngI/System.out:标题=1563640732667I/System.out: date_ added=1563640732I/System.out: is_hdr=nullI/System.out: date_modified=nullI/System.out: description=nullI/System.out: picasa_id=nullI/System.out: isprivate=nullI/System.out:纬度=空I/System.out:经度=空I/System.out: datetaken=nullI/System.out:方向=空I/System.out: mini_thumb_magic=nullI/System.out:bucket_id=-1617409521I/System.out:bucket_display_name=图片I/System.out:宽度=空I/System.out:高度=空I/System.out: is_hw_privacy=nullI/System.out: hw_voice_offset=nullI/System.out: is_hw_favorite=nullI/System.out: hw_image_refocus=nullI/System.out:album_sort_index=nullI/System.out:bucket_display_name_alias=nullI/System.out: is_hw_burst=0I/System.out: hw_rectify_offset=nullI/System.out: special_file_type=0I/System.out: special_file_offset=nullI/System.out: cam_perception=nullI/System.out: cam_exif_flag=null我/系统输出:}I/System.out:<<<<<

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

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

它仍然没有工作,这里是新的日志:

I/System.out:>>>>>转储光标 android.content.ContentResolver$CursorWrapperInner@51021a5I/System.out: 0 {I/System.out: _id=25418I/System.out: _data=/storage/emulated/0/Pictures/1563640934803.jpgI/System.out: _size=nullI/System.out: _display_name=MyimageI/System.out: mime_type=image/pngI/System.out: title=MyimageI/System.out: date_ added=1563640934I/System.out: is_hdr=nullI/System.out: date_modified=nullI/System.out: description=nullI/System.out: picasa_id=nullI/System.out: isprivate=nullI/System.out:纬度=空I/System.out:经度=空I/System.out: datetaken=nullI/System.out:方向=空I/System.out: mini_thumb_magic=nullI/System.out:bucket_id=-1617409521I/System.out:bucket_display_name=图片I/System.out:宽度=空I/System.out:高度=空I/System.out: is_hw_privacy=nullI/System.out: hw_voice_offset=nullI/System.out: is_hw_favorite=nullI/System.out: hw_image_refocus=nullI/System.out:album_sort_index=nullI/System.out:bucket_display_name_alias=nullI/System.out: is_hw_burst=0I/System.out: hw_rectify_offset=nullI/System.out: special_file_type=0I/System.out: special_file_offset=nullI/System.out: cam_perception=nullI/System.out: cam_exif_flag=null我/系统输出:}I/System.out:<<<<<

而且我无法将 date_ added 更改为名称.

并且 MediaStore.MediaColumns.DATA 已弃用.

提前致谢!

解决方案

尝试下一个方法.如果文件夹不存在,Android Q(及更高版本)已经负责创建文件夹.该示例经过硬编码以输出到 DCIM 文件夹中.如果您需要一个子文件夹,则将子文件夹名称附加如下:

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

考虑压缩格式应该和mime-type参数有关.例如,对于 JPEG 压缩格式,mime 类型将是图像/jpeg",等等.可能您还想将压缩质量作为参数传递,在此示例中硬编码为 95.

Java:

@NonNullpublic Uri saveBitmap(@NonNull 最终上下文上下文,@NonNull 最终位图位图,@NonNull 最终 Bitmap.CompressFormat 格式,@NonNull final String mimeType,@NonNull final String displayName) 抛出 IOException {最终的 ContentValues 值 = 新的 ContentValues();values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);最终 ContentResolver 解析器 = context.getContentResolver();uri uri = null;尝试 {最终的 Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;uri = resolver.insert(contentUri, values);如果(uri == null)throw new IOException(无法创建新的 MediaStore 记录.");尝试(最终的OutputStream流=resolver.openOutputStream(uri)){如果(流==空)throw new IOException(无法打开输出流.");如果(!位图.压缩(格式,95,流))throw new IOException(无法保存位图.");}返回uri;}捕获(IOException e){如果(uri!= null){//不要在 MediaStore 中留下孤立条目resolver.delete(uri, null, null);}扔e;}}

科特林:

@Throws(IOException::class)有趣的保存位图(上下文:上下文,位图:位图,格式:Bitmap.CompressFormat,mimeType:字符串,显示名称:字符串): 乌里{val 值 = ContentValues().apply {放置(MediaStore.MediaColumns.DISPLAY_NAME,显示名称)放置(MediaStore.MediaColumns.MIME_TYPE,mimeType)放置(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_DCIM)}val 解析器 = context.contentResolvervar uri:乌里?= 空尝试 {uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,值)?: throw IOException(无法创建新的 MediaStore 记录.")resolver.openOutputStream(uri)?.use {if (!bitmap.compress(format, 95, it))throw IOException(无法保存位图.")} ?: throw IOException(无法打开输出流.")返回 uri} catch (e: IOException) {uri?.let { orphanUri ->//不要在 MediaStore 中留下孤立条目resolver.delete(orphanUri, null, null)}扔e}}

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

@Throws(IOException::class)有趣的保存位图(上下文:上下文,位图:位图,格式:Bitmap.CompressFormat,mimeType:字符串,显示名称:字符串): 乌里{val 值 = ContentValues().apply {放置(MediaStore.MediaColumns.DISPLAY_NAME,显示名称)放置(MediaStore.MediaColumns.MIME_TYPE,mimeType)放置(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_DCIM)}var uri:乌里?= 空返回运行捕获{with(context.contentResolver) {插入(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,值)?.也{uri = it//保留 uri 引用,以便在失败时将其删除openOutputStream(it)?.use { stream ->如果(!位图.压缩(格式,95,流))throw IOException(无法保存位图.")} ?: throw IOException(无法打开输出流.")} ?: throw IOException(无法创建新的 MediaStore 记录.")}}.getOrElse {uri?.let { orphanUri ->//不要在 MediaStore 中留下孤立条目context.contentResolver.delete(orphanUri, null, null)}丢它}}

Here is a link to the new Android Q Scoped Storage.

According to this Android Developers Best Practices Blog, storing shared media files (which is my case) should be done using the MediaStore API.

Digging into the docs and I cannot find a relevant function.

Here is my trial in 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
}

The result is that my image is saved here Android/data/com.mypackage.etc/files/Pictures/example.png as described in the Best Practices Blog as Storing app-internal files


My question is:

How to save an image using the MediaStore API?


Answers in Java are equally acceptable.

Thanks in Advance!


EDIT

Thanks to PerracoLabs

That really helped!

But there are 3 more points.

Here is my code:

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- The image saved doesn't get its correct name "Myimage.png"

I tried using "Myimage" and "Myimage.PNG" but neither worked.

The image always gets a name made up of numbers like:

1563468625314.jpg

Which bring us to the second problem:

2- The image is saved as jpg even though I compress the bitmap in the format of png.

Not a big issue. Just curious why.

3- The relativeLocation bit causes an exception on Devices less than Android Q. After surrounding with the "Android Version Check" if statement, the images are saved directly in the root of the Pictures folder.

Another Thank you.


EDIT 2

Changed to:

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)

Here are the logs

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: <<<<<

I noticed the title to be matching the name so I tried adding:

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

It still didn't work and here are the new logs:

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: <<<<<

And I can't change date_added to a name.

And MediaStore.MediaColumns.DATA is deprecated.

Thanks in Advance!

解决方案

Try the next method. Android Q (and above) already takes care of creating the folders if they don’t exist. The example is hard-coded to output into the DCIM folder. If you need a sub-folder then append the sub-folder name as next:

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

Consider that the compress format should be related to the mime-type parameter. For example, with a JPEG compress format the mime-type would be "image/jpeg", and so on. Probably you may also want to pass the compress quality as a parameter, in this example is hardcoded to 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 variant, with a more functional style:

@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
    }
}

这篇关于如何使用 MediaStore 在 Android Q 中保存图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆