访问我的应用程序创建的外部存储中的图像

问题描述 投票:0回答:1

我需要一种方法来通过我的应用程序访问存储在外部存储中的图像。

我想访问这些图像而不请求用户

READ_EXTERNAL_STORAGE
READ_MEDIA_IMAGES

我正在使用

ACTION_GET_CONTENT
从用户处获取图像。

val launcher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()){
        val uri : String = it.data?.data?.toString()?:"null"
        if (uri != "null"){
            val mimeType = context.contentResolver.getType(uri.toUri()).toString()
            it.data?.data?.let {
                returnUri ->
                context.contentResolver.query(returnUri, null, null, null, null)
            }?.use { cursor ->
                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
                cursor.moveToFirst()
                val name = cursor.getString(nameIndex)
                val size = cursor.getLong(sizeIndex)
                image = image.copy(
                    path = uri, mimeType = mimeType.replace("image/",""), size = size,
                    name = name, uri = it.data?.data
                )
            }
        }else{
            image = image.copy(path = uri)
        }
    }

调用启动器获取结果

launcher.launch(Intent(Intent.ACTION_GET_CONTENT).setType("image/*"))

对图像执行所需的操作后,我使用以下方法保存图像。

fun saveFile(context : Context, file: File) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
            try {
                val values = ContentValues()
                val path = Environment.DIRECTORY_PICTURES + "/FolderName"
                values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.name)
                values.put(MediaStore.MediaColumns.MIME_TYPE, "image/*")
                values.put(MediaStore.MediaColumns.RELATIVE_PATH, path)
                val savedFile = context.contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values)
                val outputStream = savedFile?.let {
                    context.contentResolver.openOutputStream(it)
                }
                val fis = FileInputStream(file)
                var length : Int
                val buffer = ByteArray(8192)
                while (fis.read(buffer).also { length = it } > 0)
                    outputStream?.write(buffer, 0, length)
                Toast.makeText(context, "Picture saved to $path", Toast.LENGTH_SHORT).show()
                println("Picture : $path / $savedFile")
            }catch (e : IOException){
                Toast.makeText(
                    context,
                    "An error occured while saving the file",
                    Toast.LENGTH_SHORT
                ).show()
                e.printStackTrace()
            }catch (e : Exception) { e.printStackTrace() }
        }else{
            try {
                val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/FolderName"
                val image = File(dir, file.name)
                val fis = FileInputStream(file)
                val fos = FileOutputStream(image)
                var length : Int
                val buffer = ByteArray(8192)
                while (fis.read(buffer).also { length = it } > 0) {
                    fos.write(buffer, 0, length)
                }
            }catch (e : IOException){
                Toast.makeText(
                    context,
                    "An Error occurred while saving the file",
                    Toast.LENGTH_SHORT
                ).show()
                e.printStackTrace()
            }catch (e : Exception) { e.printStackTrace() }
        }
    }

psst ~ 所有这些操作都是在不请求任何权限的情况下执行的。

当我尝试从 contentResolver 访问图像时,它总是返回 0。

private fun loadImages() : List<Image> {

        val photos = mutableListOf<Image>()

        val collection = sdk29andUp {
            MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
        } ?: MediaStore.Images.Media.EXTERNAL_CONTENT_URI

        val projection = arrayOf(
            MediaStore.Images.Media._ID,
            MediaStore.Images.Media.DISPLAY_NAME,
            MediaStore.Images.Media.DATE_ADDED
        )

        contentResolver.query(
            collection, projection, null, null, null
        )?.use { cursor ->

            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
            val displayNameColumn =
                cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
            val dateAddedColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_ADDED)

            cursor.moveToFirst()
            while (cursor.moveToNext()) {
                val id = cursor.getLong(idColumn)
                val name = cursor.getString(displayNameColumn)
                val date = cursor.getLong(dateAddedColumn)

                val contentUri = ContentUris.withAppendedId(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    id
                )

                photos.add(
                    Image(
                        id = id, name = name, entryDate = date, contentUri = contentUri
                    ))

            }


        }
        return photos.toList()
    }

如果有人可以帮助我,我将非常感激。

编辑: 应用程序在 API 28 及之前崩溃,因此我必须请求这些 API 级别的权限,但这意味着 28 之后的 api 有一个解决方案

android android-jetpack-compose android-contentresolver android-external-storage
1个回答
0
投票

我能够通过非常直接的实施来修复它。发布代码希望对某人有所帮助。

此实现涵盖了从 21 到 33 的所有 API 级别

保存图像

/** import MediaStore.Images.Media to remove Redundant typing of MediaStore.Images.Media **/
private fun saveImage(file: File) {
        sdk29andUp {
            val collection = Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
            val path = Environment.DIRECTORY_PICTURES + "/" + getString(R.string.app_name)
            val values = ContentValues().apply {
                put(Media.DISPLAY_NAME, file.name)
                put(Media.SIZE, file.length())
                put(Media.RELATIVE_PATH, path)
            }

            contentResolver.insert(collection, values)?.also { uri ->
                contentResolver.openOutputStream(uri)?.use { outputStream ->
                    val fis = FileInputStream(file)
                    var length : Int
                    val buffer =  ByteArray(8192)
                    while (fis.read(buffer).also { length = it } > 0)
                        outputStream.write(buffer, 0, length)
                }
                println(uri)
            } ?: throw IOException("Error creating entry in mediaStore")
        } ?: saveImageBefore29(file)
    }

    private fun saveImageBefore29(file: File) {
        val resolver = contentResolver
        val collection = Media.EXTERNAL_CONTENT_URI
        val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + "/${getString(R.string.app_name)}"
        val directory = File(dir)
        if (!directory.exists())
            directory.mkdirs()
        val image = File(dir, file.name).also { println(it) }
        val values = ContentValues().apply {
            put(Media.DISPLAY_NAME, image.name)
            put(Media.SIZE, image.length())
            put(Media.MIME_TYPE, "image/png")
            put(Media.DATA, image.path)
        }
        resolver.insert(collection, values)?.also { uri ->
            contentResolver.openOutputStream(uri)?.use { outputStream ->
                val fis = FileInputStream(file)
                var length : Int
                val buffer = ByteArray(8192)
                while (fis.read(buffer).also { length = it } > 0)
                    outputStream.write(buffer, 0, length)
            }
        }

    }

sdk29andUp
功能->

inline fun <T> sdk29andUp(onSdk29: () -> T) : T? =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
        onSdk29() 
    else null

我想指出的一些事情是:

API 29 及以上: 在此函数之前创建要保存文件的文件夹会导致

IllegalArgumentException
,因为您的应用不拥有该文件夹。只需在
RELATIVE_PATH
中插入文件夹路径,解析器就会创建您的应用程序可以在其中自由写入的文件夹。

API 28 及以下: 您需要在这些 API 级别中自行创建文件夹,同时还向用户请求

READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

加载图像

private fun loadImages() : List<Image> {
        return sdk29andUp {

            val imagesList = mutableListOf<Image>()
            val collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
            val projection = arrayOf(
                MediaStore.Images.Media._ID,
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATE_MODIFIED
            )

            contentResolver.query(
                collection,
                projection,
                null, null,
                null
            )?.use { cursor ->
                val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
                val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
                val dateColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_MODIFIED)

                while (cursor.moveToNext()){
                    val id = cursor.getLong(idColumn)
                    val name = cursor.getString(nameColumn)
                    val date = cursor.getLong(dateColumn)

                    val contentUri = ContentUris.withAppendedId(collection, id).also { println("URI $it") }
                    imagesList += Image(
                        id = id, name = name, path = contentUri.path.toString(),
                        contentUri = contentUri, entryDate = date
                    )
                }
                imagesList
            }

        } ?: loadImagesBefore29()
    }

    private fun loadImagesBefore29() : List<Image> {
        val images = mutableListOf<Image>()
        val collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        val projection = arrayOf(
            MediaStore.Images.Media._ID,
            MediaStore.Images.Media.DISPLAY_NAME,
            MediaStore.Images.Media.DATE_TAKEN,
            MediaStore.Images.Media.DATA
        )

        val selection = "${MediaStore.Images.Media.DATA} like ? "
        val selectionArgs = arrayOf("%${getString(R.string.app_name)}%")


        contentResolver.query(
            collection,
            projection,
            selection,
            selectionArgs,
            null
        )?.use { cursor ->
            val idColumn = cursor.getColumnIndex(MediaStore.Images.Media._ID)
            val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
            val dateColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATE_TAKEN)
            val dataColumn = cursor.getColumnIndex(MediaStore.Images.Media.DATA)

            while (cursor.moveToNext()){
                val id = cursor.getLong(idColumn)
                val name = cursor.getString(nameColumn)
                val date = cursor.getLong(dateColumn)
                val data = cursor.getString(dataColumn)

                val contentUri = ContentUris.withAppendedId(
                    collection, id
                ).also { println("Uri $it") }

                images += Image(
                    id = id, name = name, path = data,
                    contentUri = contentUri, entryDate = date
                )

            }

        }
        return images
    }

在 API 级别 29 及以上加载图像时,如果您的应用程序没有授予

READ_EXTERNAL_STORAGE
READ_MEDIA_IMAGES
权限,ContentResolver 将仅在 MediaStore 中查询您的应用程序创建的图像/文件。

在 API 级别 28 及更低级别上,我正在查询特定文件夹,因为应用程序保存

READ_EXTERNAL_STORAGE
,因此如果未指定
selectionArgs
,contentResolver 将返回 MediaStore 中的所有图像。

希望我能够解释这一点并帮助别人。

© www.soinside.com 2019 - 2024. All rights reserved.