Android: ¿Cómo obtener un URI de archivo de un URI de contenido?


133

En mi aplicación, el usuario debe seleccionar un archivo de audio que la aplicación maneja. El problema es que para que la aplicación haga lo que quiero que haga con los archivos de audio, necesito que el URI esté en formato de archivo. Cuando uso el reproductor de música nativo de Android para buscar el archivo de audio en la aplicación, el URI es un URI de contenido, que se ve así:

content://media/external/audio/media/710

Sin embargo, usando la popular aplicación de administración de archivos Astro, obtengo lo siguiente:

file:///sdcard/media/audio/ringtones/GetupGetOut.mp3

Este último es mucho más accesible para mí para trabajar, pero, por supuesto, quiero que la aplicación tenga funcionalidad con el archivo de audio que el usuario elija, independientemente del programa que use para navegar por su colección. Entonces mi pregunta es, ¿hay alguna forma de convertir el content://estilo URI en un file://URI? De lo contrario, ¿qué me recomendarías para resolver este problema? Aquí está el código que llama al selector, como referencia:

Intent ringIntent = new Intent();
ringIntent.setType("audio/mp3");
ringIntent.setAction(Intent.ACTION_GET_CONTENT);
ringIntent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(ringIntent, "Select Ringtone"), SELECT_RINGTONE);

Hago lo siguiente con el URI de contenido:

m_ringerPath = m_ringtoneUri.getPath();
File file = new File(m_ringerPath);

Luego haga algunas cosas de FileInputStream con dicho archivo.


1
¿Qué llamadas estás usando que no les gustan los URI de contenido?
Phil Lello

1
Hay una gran cantidad de contenido Uris donde no se puede obtener la ruta del archivo, porque no todo el contenido Uris tiene rutas de archivo. No uses rutas de archivo.
Mooing Duck

Respuestas:


153

Solo use getContentResolver().openInputStream(uri)para obtener un InputStreamde un URI.

http://developer.android.com/reference/android/content/ContentResolver.html#openInputStream(android.net.Uri)


12
Verifique el esquema del URI que le devolvió la actividad del selector. Si es uri.getScheme.equals ("contenido"), ábralo con un solucionador de contenido. Si uri.Scheme.equals ("archivo"), ábralo utilizando los métodos de archivo normales. De cualquier manera, terminarás con un InputStream que puedes procesar usando un código común.
Jason LeBrun

16
En realidad, acabo de volver a leer los documentos para getContentResolver (). OpenInputStream (), y funciona automáticamente para esquemas de "contenido" o "archivo", por lo que no necesita verificar el esquema ... si puede hacerlo de forma segura suponga que siempre va a ser content: // o file: // luego openInputStream () siempre funcionará.
Jason LeBrun

57
¿Hay alguna manera de obtener el archivo en lugar del InputStream (del contenido: ...)?
AlikElzin-kilaka

44
@kilaka Puedes obtener la ruta del archivo pero es doloroso. Ver stackoverflow.com/a/20559418/294855
Danyal Aytekin

77
Esta respuesta es insuficiente para alguien que está utilizando una API de código cerrado que se basa en archivos en lugar de FileStreams, pero que quiere usar el sistema operativo para permitir que el usuario seleccione el archivo. La respuesta a la que @DanyalAytekin hizo referencia fue exactamente lo que necesitaba (y, de hecho, pude recortar mucho de la grasa porque sé exactamente con qué tipo de archivos estoy trabajando).
monkey0506

45

Esta es una vieja respuesta con una forma desaprobada y hacky de superar algunos puntos débiles específicos de resolución de contenido. Tómelo con algunos granos de sal enormes y use la API openInputStream adecuada si es posible.

Puede usar Content Resolver para obtener una file://ruta desde el content://URI:

String filePath = null;
Uri _uri = data.getData();
Log.d("","URI = "+ _uri);                                       
if (_uri != null && "content".equals(_uri.getScheme())) {
    Cursor cursor = this.getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
    cursor.moveToFirst();   
    filePath = cursor.getString(0);
    cursor.close();
} else {
    filePath = _uri.getPath();
}
Log.d("","Chosen path = "+ filePath);

1
Gracias, esto funcionó perfectamente. No pude usar un InputStream como sugiere la respuesta aceptada.
señora

44
Esto funciona solo para archivos locales, por ejemplo, no funciona para Google Drive
azul mientras que el

1
A veces funciona, a veces devuelve file: /// storage / emulated / 0 / ... que no existe.
Reza Mohammadi

1
¿ android.provider.MediaStore.Images.ImageColumns.DATASiempre se garantiza que la columna "_data" ( ) existe si el esquema lo es content://?
Edward Falk

2
Este es un importante antipatrón. Algunos proveedores de contenido proporcionan esa columna, pero no se garantiza que tenga acceso de lectura / escritura Filecuando intente omitir ContentResolver. Utilice los métodos de ContentResolver para operar en content://uris, este es el enfoque oficial, alentado por los ingenieros de Google.
user1643723

9

Si tiene un Uri de contenido content://com.externalstorage..., puede usar este método para obtener la ruta absoluta de una carpeta o archivo en Android 19 o superior .

public static String getPath(final Context context, final Uri uri) {
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        System.out.println("getPath() uri: " + uri.toString());
        System.out.println("getPath() uri authority: " + uri.getAuthority());
        System.out.println("getPath() uri path: " + uri.getPath());

        // ExternalStorageProvider
        if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
    }
    return null;
}

Puede verificar que cada parte de Uri está usando println. Los valores devueltos para mi tarjeta SD y la memoria principal del dispositivo se enumeran a continuación. Puede acceder y eliminar si el archivo está en la memoria pero no pude eliminar el archivo de la tarjeta SD usando este método, solo leí o abrí la imagen usando esta ruta absoluta. Si ha encontrado una solución para eliminar usando este método, por favor comparta. TARJETA SD

getPath() uri: content://com.android.externalstorage.documents/tree/612E-B7BF%3A/document/612E-B7BF%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/612E-B7BF:/document/612E-B7BF:
getPath() docId: 612E-B7BF:, split: 1, type: 612E-B7BF

MEMORIA PRINCIPAL

getPath() uri: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/primary:/document/primary:
getPath() docId: primary:, split: 1, type: primary

Si desea obtener Uri file:///después de usar el camino

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri

No creo que sea la forma correcta de hacerlo en una aplicación. tristemente lo estoy usando en un proyecto
rápido

@Bondax Sí, debería trabajar con Content Uris en lugar de rutas de archivos o File Uris. Así se introduce el marco de acceso al almacenamiento. Pero, si desea obtener el archivo uri, esta es la forma más correcta de otras respuestas, ya que utiliza la clase DocumentsContract. Si revisa las muestras de Google en Github, verá que también se usan en esta clase para obtener subcarpetas de una carpeta.
Tracio

Y la clase DocumentFile también es una nueva adición en la api 19 y cómo usa uris de SAF. La forma correcta es usar una ruta estándar para su aplicación y pedirle al usuario que otorgue permiso para una carpeta a través de SAF ui, guarde la cadena Uri en las preferencias compartidas y, cuando sea necesario, acceda a la carpeta con objetos DocumentFile
Thracian

7

Intentar manejar el URI con contenido: // esquema llamando ContentResolver.query()no es una buena solución. En HTC Desire con 4.2.2, puede obtener NULL como resultado de la consulta.

¿Por qué no utilizar ContentResolver en su lugar? https://stackoverflow.com/a/29141800/3205334


Pero a veces solo necesitamos el camino. Realmente no tenemos que cargar el archivo en la memoria.
Kimi Chiu

2
"La ruta" no sirve de nada si no tiene derechos de acceso. Por ejemplo, si una aplicación le proporciona una content://uri, correspondiente al archivo en su directorio interno privado, no podrá usar esa uri con FileAPI en nuevas versiones de Android. ContentResolver está diseñado para superar este tipo de limitaciones de seguridad. Si recibió el uri de ContentResolver, puede esperar que funcione.
user1643723

5

Las respuestas inspiradas son Jason LaBrun y Darth Raven . Probar enfoques ya respondidos me llevó a la siguiente solución que puede cubrir principalmente casos nulos de cursor y conversión de contenido: // a archivo: //

Para convertir el archivo, lea y escriba el archivo de uri obtenida

public static Uri getFilePathFromUri(Uri uri) throws IOException {
    String fileName = getFileName(uri);
    File file = new File(myContext.getExternalCacheDir(), fileName);
    file.createNewFile();
    try (OutputStream outputStream = new FileOutputStream(file);
         InputStream inputStream = myContext.getContentResolver().openInputStream(uri)) {
        FileUtil.copyStream(inputStream, outputStream); //Simply reads input to output stream
        outputStream.flush();
    }
    return Uri.fromFile(file);
}

Para obtener el uso del nombre de archivo, cubrirá el caso de cursor nulo

public static String getFileName(Uri uri) {
    String fileName = getFileNameFromCursor(uri);
    if (fileName == null) {
        String fileExtension = getFileExtension(uri);
        fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
    } else if (!fileName.contains(".")) {
        String fileExtension = getFileExtension(uri);
        fileName = fileName + "." + fileExtension;
    }
    return fileName;
}

Hay una buena opción para convertir de tipo mime a extensión de archivo

 public static String getFileExtension(Uri uri) {
    String fileType = myContext.getContentResolver().getType(uri);
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
}

Cursor para obtener el nombre del archivo

public static String getFileNameFromCursor(Uri uri) {
    Cursor fileCursor = myContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    String fileName = null;
    if (fileCursor != null && fileCursor.moveToFirst()) {
        int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (cIndex != -1) {
            fileName = fileCursor.getString(cIndex);
        }
    }
    return fileName;
}

Gracias, he estado mirando esto por una semana. No me gusta tener que copiar un archivo para esto, pero funciona.
justdan0227

3

Bueno, llego un poco tarde para responder, pero mi código está probado

comprobar esquema de uri:

 byte[] videoBytes;

if (uri.getScheme().equals("content")){
        InputStream iStream =   context.getContentResolver().openInputStream(uri);
            videoBytes = getBytes(iStream);
        }else{
            File file = new File(uri.getPath());
            FileInputStream fileInputStream = new FileInputStream(file);     
            videoBytes = getBytes(fileInputStream);
        }

En la respuesta anterior, convertí la uri del video en una matriz de bytes, pero eso no está relacionado con la pregunta, simplemente copié mi código completo para mostrar el uso de FileInputStreamy InputStreamya que ambos funcionan igual en mi código.

Usé el contexto variable que es getActivity () en mi Fragmento y en Activity simplemente es ActivityName.this

context=getActivity(); // en Fragmento

context=ActivityName.this;// en actividad


Sé que no está relacionado con la pregunta, pero ¿cómo usarías la byte[] videoBytes;? La mayoría de las respuestas solo muestran cómo usar InputStreamcon una imagen.
KRK
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.