Almacenamiento de alcance: procesamiento de archivos todo en uno con MediaStore | Via Lakshyasukhralia | Septiembre de 2021
Si su aplicación apunta a Android 11, ya no podrá escribir archivos en ningún lugar del sistema de archivos. Por lo tanto, su aplicación necesita una nueva forma de manejar el almacenamiento de medios.
Hemos examinado muchos artículos que describen un método para almacenar archivos multimedia, pero cada artículo tiene una implementación diferente para almacenar archivos de diferentes tipos de mime, como Mediastore para imágenes, DownlaodManager para videos y Pdfs / SAF de Docs, etc.
Aunque hay varias formas de manejar el almacenamiento, queremos poder manejar la mayor cantidad de tipos de archivos bajo la misma implementación.Ingresar Tienda de medios interfaz.
Una generaciónnorte En nuestro caso, usamos Firebase para descargar medios de la web y almacenarlos en un directorio público que ya existe en el sistema de archivos de Android, como FOTOS, AUDIO, DOCUMENTOS, etc. De esta manera, incluso si se desinstala la aplicación, nuestros archivos permanecerán.
Manejo de autoridad
Verifique los permisos existentes y pregunte si son necesarios.No lo necesitamos en SDK ≥ 29 WRITE_EXTERNAL_STORAGE escribe en el directorio público.
private var readPermissionGranted = false
private var writePermissionGranted = falsefun updateOrRequestPermissions(
activity: Activity,
storagePermissionType: Int? = null
): Boolean
val (hasReadPermission, hasWritePermission, minSdk29) = checkExistingStoragePermission(
activity
)
readPermissionGranted = hasReadPermission
writePermissionGranted = hasWritePermission
fun checkExistingStoragePermission(activity: Activity): Triple<Boolean, Boolean, Boolean>
val hasReadPermission = ContextCompat.checkSelfPermission(
activity,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
val hasWritePermission = ContextCompat.checkSelfPermission(
activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
val minSdk29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
return Triple(hasReadPermission, hasWritePermission, minSdk29)
private fun requestStoragePermissions(activity: Activity, storagePermissionType: Int)
ActivityCompat.requestPermissions(
activity,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
storagePermissionType
)
Tipo de archivo.kt
Ayuda a describir los tipos MIME desde la extensión de archivo
enum class FileType(val ext: String, val mimeType: String)
JPG("jpg", "image/*"),
JPEG("jpeg", "image/*"),
PNG("png", "image/*"),
WEBP("webp", "image/*"),
TIFF("tiff", "image/*"),
TIF("tif", "image/*"),
GIF("gif", "image/*"),
MP4("mp4", "video/*"),
AVI("avi", "video/*"),
MP3("mp3", "audio/*"),
PDF("pdf", "application/pdf"),
Modelo de escritura de archivos
Una clase de datos que contiene toda la información necesaria para escribirla en el sistema de archivos posteriormente.
data class FileWriteModel(
var url: String,
val fileName: String,
val collection: Uri,
val mime: String
)
Función de llamada para preparar e iniciar la descarga
Obtenga el nombre del archivo de Firebase y configure su tipo MIME y la colección a la que pertenece y escríbalo en el sistema de archivos.
fun downloadFireBaseFiles(
context: Context,
url: String,
callback: FirebaseDownloadCallback?,
firebaseFilePath: String
) val fileName = getFileName(url, firebaseFilePath) val (mime, collection) = getFileProperties(fileName) val fileWriteModel = FileWriteModel(url, fileName, collection, mime)
writeToFileSystem(context, callback, fileWriteModel)
Obtener atributos de archivo
Obtenga los atributos de archivo de los diferentes tipos de archivos que se utilizarán en la llamada al método anterior
private fun getFileProperties(
url: String
): Pair<String, Uri>
return when url.contains(FileType.AVI.ext) -> val collection = sdk29AndUp
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
?: MediaStore.Video.Media.EXTERNAL_CONTENT_URI Pair(FileType.MP4.mimeType, collection)
//Audio
url.contains(FileType.MP3.ext) -> val collection = sdk29AndUp
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
?: MediaStore.Audio.Media.EXTERNAL_CONTENT_URI Pair(FileType.MP3.mimeType, collection)
url.contains(FileType.PDF.ext) -> val collection = sdk29AndUp
MediaStore.Downloads.EXTERNAL_CONTENT_URI
?: MediaStore.Files.getContentUri("external") Pair(FileType.PDF.mimeType, collection)
else -> Pair("", "".toUri())
Obtenga el nombre del archivo de la ruta de Firebase para usar en la llamada al método anterior
fun getFileName(url: String, fileName: String): String
return when
url.contains("image") ->
"$storageRef.child(url).name.png"
url.contains("video") ->
"$storageRef.child(url).name.mp4"
url.contains("audio") ->
"$storageRef.child(url).name.mp3"
url.contains("file") ->
"$storageRef.child(url).name.$fileName.substringAfter(".")"
else ->
fileName
Escribir en el sistema de archivos
Use la api de Mediastore para descargar y escribir en el sistema de archivos en sus respectivos directorios públicos
private fun writeToFileSystem(
context: Context,
callback: FirebaseDownloadCallback?,
fileWriteModel: FileWriteModel
) { val (url, fileName, collection, mime) = fileWriteModel val contentValues = ContentValues() contentValues.apply
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.MIME_TYPE, mime)
//Append DATA Column for SDK<29 required for Audio and Pdfs, also this prevents DISPLAY_NAME and TITLE overwrite
sdk29AndUp null ?: when (mime)
FileType.MP3.mimeType ->
val directory =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC)
contentValues.put(MediaStore.Audio.AudioColumns.DATA, "$directory/$fileName")
FileType.MP4.mimeType ->
val directory =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES)
contentValues.put(MediaStore.Video.VideoColumns.DATA, "$directory/$fileName")
FileType.PDF.mimeType ->
val directory =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
contentValues.put(MediaStore.Files.FileColumns.DATA, "$directory/$fileName")
val resolver: ContentResolver = context.contentResolver
val uriResolve: Uri? = resolver.insert(collection, contentValues) var inputStream: ByteArrayInputStream
val stream: OutputStream?
try uriResolve.path == null)
Toast.makeText(context, "Failed to create uri resolver!", Toast.LENGTH_SHORT).show()
throw IOException("Failed to create new MediaStore record.")
stream = resolver.openOutputStream(uriResolve)
storageRef.child(url).getBytes((1024 * 1024).toLong())
.addOnSuccessListener bytes: ByteArray? ->
try
var bytesRead: Int
inputStream = ByteArrayInputStream(bytes)
while (inputStream.read(bytes).also bytesRead = it > 0)
stream?.write(bytes, 0, bytesRead)
inputStream.close()
stream?.flush()
stream?.close()
callback?.onSuccess()
Toast.makeText(context, "Successfully downloaded", Toast.LENGTH_SHORT)
.show()
catch (e: IOException)
stream?.close()
e.printStackTrace()
Toast.makeText(context, "Failed to download!", Toast.LENGTH_SHORT).show()
.addOnFailureListener
callback?.onFailure()
catch (e: IOException)
e.printStackTrace()
}
Verificar archivos existentes
Compruebe si ya existe un archivo con un nombre de archivo coincidente en nuestro sistema de archivos
fun checkFileAlreadyExist(
url: String,
fileName: String = "",
activity: Context
): String val collection = MediaStore.Files.getContentUri("external")val projection = arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DISPLAY_NAME
)val sharedStorageFileList = mutableListOf<SharedStorageFile>()val selection = (MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
+ " OR "
+ MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_DOCUMENT)activity?.contentResolver?.query(
collection,
projection,
selection,
null,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
)?.use cursor ->
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
val displayNameColumn =
cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)while (cursor.moveToNext())
val id = cursor.getLong(idColumn)
val displayName = cursor.getString(displayNameColumn)
sharedStorageFileList.add(SharedStorageFile(id, displayName))
try
val fileUrlArray = url.split("/")
val storedFileName = fileUrlArray[fileUrlArray.size - 1]
val matchingFiles = sharedStorageFileList.filter it.name.contains(storedFileName)
if (matchingFiles.isNotEmpty()) return matchingFiles[].name
catch (e: Exception)
Timber.d("No matching file found!")
return ""