我需要一种方法来通过我的应用程序访问存储在外部存储中的图像。
我想访问这些图像而不请求用户
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 有一个解决方案
我能够通过非常直接的实施来修复它。发布代码希望对某人有所帮助。
此实现涵盖了从 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 中的所有图像。
希望我能够解释这一点并帮助别人。