为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

烙印99 2023-03-09 15:29:31
这是新的 Android Q Scoped Storage的链接。根据这个 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 whateverval name = "example.png" // I have a nameval picturesDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!// Make sure the directory "Android/data/com.mypackage.etc/files/Pictures" existsif (!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 中的答案同样可以接受。
查看完整描述

5 回答

?
Cats萌萌

TA贡献1805条经验 获得超9个赞

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


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

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


爪哇:


@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

    }

}


查看完整回答
反对 回复 2023-03-09
?
慕虎7371278

TA贡献1802条经验 获得超4个赞

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我创建了教程



查看完整回答
反对 回复 2023-03-09
?
浮云间

TA贡献1829条经验 获得超4个赞

这是我经常使用的。你可以尝试一下。


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

    }


}


查看完整回答
反对 回复 2023-03-09
?
守候你守候我

TA贡献1802条经验 获得超10个赞

如果有人正在寻找如何将照片保存到 DCIM 文件夹中,以一种稍后将出现在 Google Photos 中的方式:(基于: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从文件复制到流的标准方法在哪里。


查看完整回答
反对 回复 2023-03-09
?
慕森王

TA贡献1777条经验 获得超3个赞

这是我的 2022 版本,此版本也在 Samsung S22 Phone 上的 Emulator SDK 27 和 30 中进行了测试。


长:博士


对于 SDK < 29 你需要按照这里的代码,并且在成功拍照后几乎不需要添加代码。您可以在savePictureQ(...)下面的我的功能中看到


否则,如果您是 SDK >= 29,只需从函数的MediaStore.EXTRA_OUTPUTextras处传递 URIcontentResolver.insert(...)


因为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.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)

                }

            }

        }

    }

}

对于 SDK < 29,我遵循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 文件,确保名称与您放置在该文件<meta-data>中<provider>和内部的名称相同:


<?xml version="1.0" encoding="utf-8"?>

<paths>

    <external-files-path

        name="camera_take"

        path="Pictures" />

</paths>


查看完整回答
反对 回复 2023-03-09
  • 5 回答
  • 0 关注
  • 167 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信