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

huangapple go评论58阅读模式
英文:

Access Images In External Storage Created By My App

问题

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

我想在不要求用户授予READ_EXTERNAL_STORAGEREAD_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, "图片保存到 $path", Toast.LENGTH_SHORT).show()
                println("图片 : $path / $savedFile")
            }catch (e : IOException){
                Toast.makeText(
                    context,
                    "保存文件时出现错误",
                    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,
                    "保存文件时出现错误",
                    Toast.LENGTH_SHORT
                ).show()
                e.printStackTrace()
            }catch (e : Exception) { e.printStackTrace() }
        }
    }

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

当我尝试从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级别请求权限,但这意味着在API 28之后存在解决方案。

英文:

I need a method to access the images stored in external storage by my app.

I want to access those images without requesting READ_EXTERNAL_STORAGE or READ_MEDIA_IMAGES from the user

I'm using ACTION_GET_CONTENT to get the image from the user.

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

Calling the launcher for result

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

After performing the required actions on the image, I save the image using the following method.

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 ~ All of these actions are performed without requesting any permissions.

When I'm trying to access images from contentResolver, it always returns 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()
    }

If someone can help me with this, I would really appreciate that.

Edit : The app crashes on API 28 and Before, so I'll have to request permissions for those API levels, but it means there is a solution for api's after 28

答案1

得分: 0

我能够使用一个非常直接的实现来修复它。发布代码,希望它能帮助某人。

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

保存图像

/** 导入 MediaStore.Images.Media 以删除重复输入的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_STORAGEWRITE_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_STORAGEREAD_MEDIA_AUDIO 权限,ContentResolver只会查询MediaStore中你的应用程序创建的图像/文件。

在API级别28及以下,我正在查询一个特定的文件夹,因为应用程序持有 READ_EXTERNAL_STORAGE 权限,所以如果没有指定 selectionArgs,contentResolver将返回MediaStore中的所有图像。希望我能够解释清楚并帮助某人解决问题。

英文:

I was able to fix it with a very straight forward implementation. Posting the code in hopes that it will help someone.

This implementation covers all API levels from 21 to 33

Saving the image

/** 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 function ->

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

Some things I want to point out are:

API 29 and above:
Creating the folder you want to save the file in before this function would result in IllegalArgumentException as your app doesn't own that folder. Just insert the folder path in RELATIVE_PATH and resolver will create the folder in which your app can write freely.

API 28 and below:
You will need to create the folder yourself in these API levels while also requesting the READ_EXTERNAL_STORAGE & WRITE_EXTERNAL_STORAGE from the user.

Loading The Images

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
    }

When loading the images on API level 29 and above, if your app doesn't have READ_EXTERNAL_STORAGE or READ_MEDIA_AUDIO permission granted, ContentResolver will only query the MediaStore for images/files your app created.

On API level 28 and below, I'm querying a specific folder as the app holds READ_EXTERNAL_STORAGE so contentResolver will return all images in MediaStore if no selectionArgs are specified.

Hope I was able to explain this and help someone with this.

huangapple
  • 本文由 发表于 2023年1月9日 17:56:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75055593.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定