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

Posted

技术标签:

【中文标题】如何使用 MediaStore 在 Android Q 中保存图像?【英文标题】:How to save an image in Android Q using MediaStore? 【发布时间】:2019-11-16 04:08:01 【问题描述】:

这里是新的 android Q Scoped Storage 的链接。

根据this Android Developers Best Practices Blog,storing shared media files(我的情况)应该使用MediaStore 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 的答案同样可以接受。

提前致谢!


编辑

感谢 PerracoLabs

这真的很有帮助!

但还有 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- 即使我以png 格式压缩位图,图像仍保存为jpg

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

3- 在低于 Android Q 的设备上,relativeLocation 位会导致异常。 加上“Android版本检查”if语句后,图片直接保存在Pictures文件夹的根目录下。

再一次谢谢你。


编辑 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 更改为名称。

MediaStore.MediaColumns.DATA 已被弃用。

提前致谢!

【问题讨论】:

在我添加之前,我还在文件名中获取了这些随机数:put(MediaStore.MediaColumns.DISPLAY_NAME, name + ".mp3") 在这里查看我的答案***.com/a/68110559/6039240 【参考方案1】:

尝试下一个方法。如果文件夹不存在,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;
    

科特林:

@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
    

【讨论】:

非常感谢,这确实有帮助,但还有 3 点。请检查我编辑的问题 这个问题是只出现在 Android Q 还是所有 Android 版本?考虑到我的答案中的代码适用于 Android Q。对于旧版本的 Android,您需要照常创建路径 + 文件。 哦,这是在 Android Pie 上测试的,不是 Q代码? 是的,对于比 Q 更早的版本,您需要使用原始代码。您的代码仅在 Q 中被弃用,但在旧版本中有效。 如何检查文件是否已经存在?如果是,则只显示位图。【参考方案2】:
private void saveImage(Bitmap bitmap, @NonNull String name) throws IOException 
    OutputStream fos;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
        ContentResolver resolver = getContentResolver();
        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name + ".jpg");
        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg");
        contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
        Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
        fos = resolver.openOutputStream(Objects.requireNonNull(imageUri));
     else 
        String imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
        File image = new File(imagesDir, name + ".jpg");
        fos = new FileOutputStream(image);
    
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
    Objects.requireNonNull(fos).close();

图像将存储在图片文件夹@根级别

看直播https://youtu.be/695HqaiwzQ0我创建的教程

【讨论】:

但是如何检查图像是否已经保存!没有人谈论它! 您可以使用此代码检查存在的文件if(isFilePresent()) // call save function here else // Toast file exist // private boolean isFilePresent() File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), name + ".jpg");return file.exists(); @RachitVohera 您的回答是正确的,但您之前的评论不是 - getExternalStoragePublicDirectory 在 Android 10 中已弃用。这就是您在回答中使用 if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.Q) 的原因。 嘿,如果我想使用一个意图打开这个带有画廊的图像文件呢?我通过了内容 URI,但仍然找不到文件 运行媒体扫描以添加新媒体【参考方案3】:

这是我一直使用的。你可以试试看。

 private void saveImageToStorage() throws IOException 

    OutputStream imageOutStream;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 

        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DISPLAY_NAME, "image_screenshot.jpg");
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
        Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

        imageOutStream = getContentResolver().openOutputStream(uri);
     else 
        String imagePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
        File image = new File(imagePath, "image_screenshotjpg");
        imageOutStream = new FileOutputStream(image);
    

    try 
        bitmapObject.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
     finally 
        imageOutStream.close();
    


【讨论】:

我尝试使用这种方法,但是,当我在 Android Q 上保存文件后立即尝试FileProvider.getUriForFile() 时得到IllegalArgumentException: Failed to find configured root that contains /Pictures/2020-12-17_170659_photo.jpg。在清单中,FileProvider provider 设置正确.使用 MediaStore 时是否无法在保存 Uri 后获取它(插入到表中)? 也许有必要用 getExternalFilesDir() 保存我的图像,然后调用FileProvider.getUriForFile() 如何检查if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.Q) 条件文件是否已经存在它保存了filename.jpg然后filename(1).jpg然后继续所以我想检查文件是否存在。 @SomeoneSomewhere 你有什么解决办法吗?保存图像后如何获取绝对文件路径? (安卓 11)【参考方案4】:

如果有人正在研究如何将照片保存到 DCIM 文件夹中,这种方式稍后会出现在 Google 相册中: (基于:https://github.com/yasirkula/UnityNativeGallery/blob/670d9e2b8328f7796dd95d29dd80fadd8935b804/JAR%20Source/NativeGallery.java#L73-L96)

ContentValue values = new ContentValues();
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
values.put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.MediaColumns.IS_PENDING, true);

Uri uri = context.getContentResolver().insert(externalContentUri, values);

if (uri != null) 
    try 
        if (WriteFileToStream(originalFile, context.getContentResolver().openOutputStream(uri))) 
            values.put(MediaStore.MediaColumns.IS_PENDING, false);
            context.getContentResolver().update(uri, values, null, null);
        
     catch (Exception e) 
        context.getContentResolver().delete( uri, null, null );
    

WriteFileToStream 是从文件复制到流的标准方法。

【讨论】:

【参考方案5】:

这是我 2022 年的版本,此版本已在模拟器 SDK 27 和 30 中进行了测试,也在三星 S22 手机上进行了测试。

TL:DR

SDK here,拍照成功后只需添加少量代码。您可以在下面的savePictureQ(...) 函数中看到

否则,如果您是 SDK >= 29,只需从 contentResolver.insert(...) 函数传递 MediaStore.EXTRA_OUTPUT 的 URI 即可


由于startActivityForResult(Intent) 已经弃用了我使用registerForActivityResult(...) 的版本

private val cameraLauncher =
    registerForActivityResult(ActivityResultContracts.StartActivityForResult()) 
        if (it.resultCode == Activity.RESULT_OK) 
            val name: String = viewModel.savePictureQ()
            if (name != "") requireActivity().applicationContext.deleteFile(name)
            val cr = requireContext().contentResolver
            val uri = viewModel.getTargetUri()
            if (uri != null) 
                val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
                    val source = ImageDecoder.createSource(cr, uri)
                    ImageDecoder.decodeBitmap(source)
                 else MediaStore.Images.Media.getBitmap(cr, uri)
                val resized = Bitmap.createScaledBitmap(bitmap, 512, 512, true)
            
        
    

我在另一个名为 Repository.kt 的文件中调用 Intent,我还使用假 viewModel 来调用 Repository 代码。 这是我如何调用我的 viewModel 代码

private lateinit var viewModel: MenuViewModel
override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View 
    viewModel = MenuViewModel(Injection.provideRepository(requireContext()))
    ...


private fun permissionCheck() 
    val granted = PackageManager.PERMISSION_GRANTED
    val permissions = arrayOf(
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.CAMERA
    )
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) 
        if (ActivityCompat.checkSelfPermission(
                requireContext(),
                permissions[0]
            ) != granted && ActivityCompat.checkSelfPermission(
                requireContext(),
                permissions[1]
            ) != granted && ActivityCompat.checkSelfPermission(
                requireContext(),
                permissions[2]
            ) != granted
        ) ActivityCompat.requestPermissions(
            requireActivity(), permissions, MainActivity.REQUEST_CODE_PERMISSION
        ) else MainActivity.accepted = true

     else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
        if (ActivityCompat.checkSelfPermission(
                requireContext(),
                permissions[2]
            ) != granted && ActivityCompat.checkSelfPermission(
                requireContext(),
                Manifest.permission.ACCESS_MEDIA_LOCATION
            ) != granted
        ) ActivityCompat.requestPermissions(
            requireActivity(),
            arrayOf(permissions[2], Manifest.permission.ACCESS_MEDIA_LOCATION),
            MainActivity.REQUEST_CODE_PERMISSION
        ) else MainActivity.accepted = true
    


override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
    ...
    bind.fromCamera.setOnClickListener 
        permissionCheck()
        if (`permission granted check`) 
            viewModel.getCameraIntent(cameraLauncher)
        
    
    ...

在我的假 viewModel 中:

class MenuViewModel(private val repository: IRepository) 
    fun getTargetUri() = repository.getTargetUri()
    fun getCameraIntent(launcher: ActivityResultLauncher<Intent>) =
        repository.createTakePictureIntent(launcher)
    fun savePictureQ(): String = repository.savePictureQ()

在我的存储库代码中:

class Repository private constructor(private val context: Context) : IRepository 

    companion object 
        @Volatile
        private var INSTANCE: IRepository? = null

        fun getInstance(context: Context) = INSTANCE ?: synchronized(this) 
            INSTANCE ?: Repository(context).apply  INSTANCE = this 
        
    

    private var currentPath = ""
    private var targetUri: Uri? = null

    private fun createImageFile(): File   // create temporary file for SDK < 29
        val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
        val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        return File.createTempFile(timestamp, ".jpg", storageDir)
            .apply  currentPath = absolutePath 
    

    override fun savePictureQ() : String   // Saving picture and added to Gallery for SDK < 29
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) 
            val f = File(currentPath)
            val cr = context.contentResolver
            val bitmap = BitmapFactory.decodeFile(currentPath)
            val path = "$Environment.DIRECTORY_PICTURES$File.separatorPoCkDetection"
            val values = createContentValues(f.name, path)
            var uri: Uri? = null
            try 
                uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)!!
                val os = cr.openOutputStream(uri)
                try 
                    val result = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os)
                    if (!result) throw Exception()
                 catch (e: Exception) 
                    e.printStackTrace()
                    throw e
                 finally 
                    os?.close()
                    targetUri = uri
                
                f.delete()
                if (f.exists()) 
                    f.canonicalFile.delete()
                    if (f.exists()) return f.name
                
             catch (e: Exception) 
                e.printStackTrace()
                uri?.let 
                    cr.delete(it, null, null)
                
            
        
        return ""
    

    override fun getTargetUri(): Uri? = targetUri

    private fun createContentValues(title: String, path: String): ContentValues =
        ContentValues().apply 
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 
                put(MediaStore.MediaColumns.TITLE, "$title.jpg")
                put(MediaStore.MediaColumns.DISPLAY_NAME, "$title.jpg")
                put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
                put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis())
                put(MediaStore.MediaColumns.DATE_TAKEN, System.currentTimeMillis())
                put(MediaStore.MediaColumns.RELATIVE_PATH, path)
             else 
                put(MediaStore.Images.Media.TITLE, "$title.jpg")
                put(MediaStore.Images.Media.DISPLAY_NAME, "$title.jpg")
                put(MediaStore.Images.Media.MIME_TYPE, "image/jpg")
                put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
                put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
            
        

    override fun createTakePictureIntent(launcher: ActivityResultLauncher<Intent>) 
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also  takePictureIntent ->
            takePictureIntent.resolveActivity(context.packageManager).also 
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) 
                    val photoFile: File? = try 
                        createImageFile()
                     catch (e: IOException) 
                        e.printStackTrace()
                        null
                    
                    photoFile?.also 
                        val photoURI =
                            FileProvider.getUriForFile(context, "com.your.package.name", it)
                        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                        launcher.launch(takePictureIntent)
                    
                 else 
                    val timestamp =
                        SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
                    val path = "$Environment.DIRECTORY_PICTURES$File.separatorPoCkDetection"
                    val values = createContentValues(timestamp, path)
                    val photoURI = context.contentResolver.insert(
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values
                    )
                    targetUri = photoURI
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    launcher.launch(takePictureIntent)
                
            
        
    

对于 SDK Google Developer 的代码

这就是我的清单在遵循代码后的样子:

<application ...>
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.your.package.name"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/camera_paths" />
    </provider>
</application>

创建名为 xml 的新 res 文件夹,然后创建新的 xml 文件,确保名称与您在 &lt;meta-data&gt;&lt;provider&gt; 中放置的名称相同:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="camera_take"
        path="Pictures" />
</paths>

【讨论】:

以上是关于如何使用 MediaStore 在 Android Q 中保存图像?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何使用 MediaStore 类(ANDROID)获取所有包含图像的文件夹?

无法在 android 中使用 MediaPlayer 播放 MediaStore.Audio 歌曲

Android:通过 MTP 连接时如何检测 MediaStore 的变化

Android MediaStore Crop 功能,如何让选择箭头始终可见?

Qt 和 Android Gallery - 使用 QAndroidJniObject 的 MediaStore

如何通过lua脚本及时更新Android MediaStore?