简体   繁体   English

如何使用MediaStore保存Android Q中的图片?

[英]How to save an image in Android Q using MediaStore?

Here is a link to the new Android Q Scoped Storage .这是新的 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.根据这个 Android 开发人员最佳实践博客storing shared media files (我的情况)应该使用MediaStore API 来完成。

Digging into the docs and I cannot find a relevant function.深入研究文档,我找不到相关的 function。

Here is my trial in Kotlin:这是我在 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结果是我的图像保存在此处Android/data/com.mypackage.etc/files/Pictures/example.png ,如最佳实践博客中所述, Storing app-internal files


My question is:我的问题是:

How to save an image using the MediaStore API?如何使用 MediaStore API 保存图像?


Answers in Java are equally acceptable. Java 中的答案同样可以接受。


EDIT编辑

Thanks to PerracoLabs感谢 PerracoLabs

That really helped!这真的很有帮助!

But there are 3 more points.但是还有3点。

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" 1- 保存的图像没有得到正确的名称“Myimage.png”

I tried using "Myimage" and "Myimage.PNG" but neither worked.我尝试使用“Myimage”和“Myimage.PNG”,但均无效。

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 . 2-图像保存为jpg即使我以png格式压缩 bitmap 。

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. 3- relativeLocation 位导致设备小于 Android Q. 与“Android 版本检查”if 语句包围后,图像直接保存在Pictures文件夹的根目录中。

Another Thank you.另一个谢谢。


EDIT 2编辑 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:我注意到title与名称匹配,所以我尝试添加:

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.而且我无法将date_added更改为名称。

And MediaStore.MediaColumns.DATA is deprecated.并且MediaStore.MediaColumns.DATA已弃用。

Thanks in Advance!提前致谢!

Try the next method.尝试下一个方法。 Android Q (and above) already takes care of creating the folders if they don't exist.如果文件夹不存在,Android Q(及更高版本)已经负责创建文件夹。 The example is hard-coded to output into the DCIM folder.该示例经过硬编码以输出到DCIM文件夹。 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.考虑压缩格式应该与mime-type参数有关。 For example, with a JPEG compress format the mime-type would be "image/jpeg", and so on.例如,对于 JPEG 压缩格式,mime 类型将是“image/jpeg”,依此类推。 Probably you may also want to pass the compress quality as a parameter, in this example is hardcoded to 95.可能您可能还希望将压缩质量作为参数传递,在此示例中硬编码为 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: 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
    }
}
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();
}

Image will store in Pictures Folder @ root level图像将存储在图片文件夹@根级别

see in live https://youtu.be/695HqaiwzQ0 i created tutorial在现场查看https://youtu.be/695HqaiwzQ0我创建的教程

This is what i always use.这是我一直使用的。 You can try it.你可以试试。

 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();
    }

}

If anyone is looking how to save a photo into the DCIM folder, in a way that will appear in Google Photos later: (based on: https://github.com/yasirkula/UnityNativeGallery/blob/670d9e2b8328f7796dd95d29dd80fadd8935b804/JAR%20Source/NativeGallery.java#L73-L96 )如果有人正在寻找如何将照片保存到 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 );
    }
}

Where WriteFileToStream is a standard method copying from file to stream.其中WriteFileToStream是从文件复制到流的标准方法。

Here is my version for 2022, this version was tested in Emulator SDK 27 and 30 also on Samsung S22 Phone.这是我的 2022 版本,该版本在模拟器 SDK 27 和 30 中也已在三星 S22 手机上进行了测试。

TL:DR TL:博士

For SDK < 29 you need following the code here , and need little add code after successfully take picture. SDK < 29 需要按照这里的代码,成功拍照后几乎不需要添加代码。 You can see at my savePictureQ(...) function below您可以在下面的savePictureQ(...)函数中看到

Otherwise, if you are SDK >= 29 just pass the URI at MediaStore.EXTRA_OUTPUT extras from contentResolver.insert(...) function否则,如果您是 SDK >= 29,只需从contentResolver.insert(...)函数传递MediaStore.EXTRA_OUTPUT extras 的 URI


Since startActivityForResult(Intent) already deprecated my version using registerForActivityResult(...)由于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)
            }
        }
    }

I call the Intent in another file named Repository.kt, I also using fake viewModel to call Repository code.我在另一个名为 Repository.kt 的文件中调用 Intent,我还使用假 viewModel 来调用 Repository 代码。 Here is how I call my viewModel code这是我如何调用我的 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)
        }
    }
    ...
}

in my fake viewModel:在我的假视图模型中:

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

in my repository code:在我的存储库代码中:

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.separator}PoCkDetection"
            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.separator}PoCkDetection"
                    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)
                }
            }
        }
    }
}

For SDK < 29 I follow this code from Google Developer对于 SDK < 29,我遵循Google Developer的此代码

this is how my manifest look after following the code:这就是我的清单在遵循代码后的样子:

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

make new res folder called xml, then make new xml file make sure the name same like you place on <meta-data> in <provider> and inside that file:创建名为 xml 的新 res 文件夹,然后创建新的 xml 文件,确保名称与您放置在<provider>中的<meta-data> > 和该文件内的名称相同:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="camera_take"
        path="Pictures" />
</paths>
   **You can use this too**

  private fun saveFileInternal(
        sourceFile: File,
        fileName: String?,
        fileType: FolderType,
        contentResolver: ContentResolver
    ): Boolean {
        var filename: String? = fileName
        return try {
            var selectedType = fileType
            val contentValues = ContentValues()
            val extension: String? = getFileExtension(sourceFile)
            var mimeType: String? = null
            if (extension != null) {
                mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
            }
            var uriToInsert: Uri? = null
            if ((fileType == FolderType.IMAGE || fileType == FolderType.VIDEO) && mimeType != null) {
                if (mimeType.startsWith("image")) {
                    selectedType = FolderType.IMAGE
                }
                if (mimeType.startsWith("video")) {
                    selectedType = FolderType.VIDEO
                }
            }
            when (selectedType) {
                FolderType.IMAGE -> {
                    if (filename == null) {
                        filename = generateFileName(0, extension)
                    }
                    uriToInsert =
                        MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
                    val dirDest = File(Environment.DIRECTORY_PICTURES, "folder")
                    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "$dirDest${separator}")
                    contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, filename)
                    contentValues.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
                }
                FolderType.VIDEO -> {
                    if (filename == null) {
                        filename = generateFileName(1, extension)
                    }
                    val dirDest = File(Environment.DIRECTORY_MOVIES, "folder")
                    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "$dirDest${separator}")
                    uriToInsert =
                        MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
                    contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, filename)
    
                }
                FolderType.DOWNLOAD -> {
                    if (filename == null) {
                        filename = sourceFile.name
                    }
                    val dirDest = File(Environment.DIRECTORY_DOWNLOADS, "folder")
                    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "$dirDest${separator}")
                    uriToInsert =
                        MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
                    contentValues.put(MediaStore.Downloads.DISPLAY_NAME, filename)
                }
                else -> {
                    if (filename == null) {
                        filename = sourceFile.name
                    }
                    val dirDest = File(Environment.DIRECTORY_MUSIC, "folder")
                    contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "$dirDest${separator}")
                    uriToInsert =
                        MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
                    contentValues.put(MediaStore.Audio.Media.DISPLAY_NAME, filename)
                }
            }
            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
            val dstUri: Uri = contentResolver.insert(uriToInsert!!, contentValues)!!
            val fileInputStream = FileInputStream(sourceFile)
            val outputStream: OutputStream = contentResolver.openOutputStream(dstUri)!!
    
            copyFile(fileInputStream, outputStream)
            fileInputStream.close()
            true
        } catch (e: java.lang.Exception) {
            Log.e("fileManager", e.message.toString())
            false
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM