The Android Developer Challenge is back! Submit your idea before December 2.

Cómo abrir archivos usando el marco de trabajo de acceso al almacenamiento

Android 4.4 (nivel de API 19) introduce el marco de trabajo de acceso a almacenamiento (SAF). El SAF permite a los usuarios examinar y abrir documentos, imágenes y otros archivos en todos sus proveedores preferidos de almacenamiento de documentos. Una IU estándar y fácil de usar les permite explorar archivos y accesos recientes de manera uniforme en apps y proveedores.

Los servicios de almacenamiento local o en la nube pueden participar en este ecosistema implementando un DocumentsProvider que encapsule sus servicios. Las apps cliente que necesiten acceso a documentos de un proveedor pueden integrarse con el SAF mediante unas pocas líneas de código.

El SAF incluye lo siguiente:

  • Proveedor de documentos: Un proveedor de contenido que permite a un servicio de almacenamiento (como Google Drive) revelar los archivos que administra. Se implementa un proveedor de documento como una subclase de la clase DocumentsProvider. El esquema de proveedor de documentos se basa en una jerarquía de archivos tradicional, aunque la forma en que el proveedor de documentos almacena físicamente los datos depende de ti. La plataforma Android incluye varios proveedores de documentos integrados, como descargas, imágenes y videos.
  • App cliente: Una aplicación personalizada que invoca la intent ACTION_OPEN_DOCUMENT o ACTION_CREATE_DOCUMENT y recibe los archivos devueltos por los proveedores de documentos.
  • Selector: Una IU del sistema que les permite a los usuarios acceder a documentos de todos los proveedores que cumplen los criterios de búsqueda de la app cliente.

Algunas de las funciones que ofrece el SAF son las siguientes:

  • Permite que los usuarios exploren contenido de todos los proveedores de contenido, no solo de una aplicación individual.
  • Permite que tu aplicación tenga acceso persistente y a largo plazo a documentos de un proveedor. Mediante este acceso, los usuarios pueden agregar, editar, guardar y borrar archivos en el proveedor.
  • Admite varias cuentas de usuario y raíces transitorias, como proveedores de almacenamiento USB, que solo aparecen si la unidad está conectada.

Descripción general

El SAF se centra en un proveedor de contenido que es una subclase de la clase DocumentsProvider. En un proveedor de documentos, los datos se estructuran como una jerarquía de archivos tradicional:

modelo de datos

Figura 1: Modelo de datos del proveedor de documentos Una raíz apunta a un solo documento, que luego se distribuye a todo el árbol.

Ten en cuenta lo siguiente:

  • Cada proveedor de documentos informa de una o más "raíces", que son puntos de partida para explorar un árbol de documentos. Cada raíz tiene un único COLUMN_ROOT_ID y apunta a un documento (un directorio) que representa el contenido que incluye esa raíz. Las raíces son dinámicas por diseño para admitir casos de uso como cuentas múltiples, dispositivos de almacenamiento USB transitorios o inicios o cierres de sesión por parte de usuarios.
  • En cada raíz, hay un solo documento. Ese documento hace referencia de 1 a N documentos, cada uno de los cuales a la vez puede hacer referencia de 1 a N documentos.
  • Cada backend de almacenamiento presenta archivos y directorios individuales haciendo referencia a ellos con un único COLUMN_DOCUMENT_ID. Los ID de los documentos deben ser únicos y no deben cambiar una vez que se emiten, ya que se usan para asignar URI persistentes en los reinicios del dispositivo.
  • Los documentos pueden ser un archivo que se abra (con un tipo de MIME específico) o un directorio que contenga documentos adicionales (con el tipo de MIME MIME_TYPE_DIR).
  • Cada documento puede tener diferentes capacidades, como se describe en COLUMN_FLAGS. Por ejemplo: FLAG_SUPPORTS_WRITE, FLAG_SUPPORTS_DELETE y FLAG_SUPPORTS_THUMBNAIL. El mismo COLUMN_DOCUMENT_ID puede incluirse en varios directorios.

Flujo de control

Como se indicó anteriormente, el modelo de datos del proveedor de documentos se basa en una jerarquía de archivos tradicional. Sin embargo, puedes almacenar físicamente tus datos como lo desees, siempre y cuando puedas acceder a ellos utilizando la API de DocumentsProvider. Por ejemplo, podrías usar almacenamiento en la nube basado en etiquetas para tus datos.

La figura 2 muestra un ejemplo de cómo una app de fotos podría usar el SAF para acceder a datos almacenados:

app

Figura 2: Flujo del marco de trabajo de acceso a almacenamiento

Ten en cuenta lo siguiente:

  • En el SAF, los proveedores y los clientes no interactúan directamente. Un cliente solicita permiso para interactuar con archivos (es decir, para leer, editar, crear o borrar archivos).
  • La interacción comienza cuando una aplicación (en este ejemplo, una de fotos) envía la intent ACTION_OPEN_DOCUMENT o ACTION_CREATE_DOCUMENT. La intent puede incluir filtros para refinar aún más los criterios; por ejemplo, "mostrarme todos los archivos que se puedan abrir y que tengan el tipo de MIME 'imagen'".
  • Una vez que se envía la intent, el selector del sistema se dirige a cada proveedor registrado y le muestra al usuario las raíces de contenido coincidentes.
  • El selector les proporciona a los usuarios una interfaz estándar para acceder a documentos, aunque los proveedores de documentos subyacentes sean muy diferentes. Por ejemplo, la figura 2 muestra un proveedor de Google Drive, un proveedor de USB y un proveedor de nube.

La figura 3 muestra un selector en el que un usuario que busca imágenes seleccionó una cuenta de Google Drive. También muestra todas las raíces disponibles para la app cliente.

selector

Figura 3: Selector

Cuando el usuario selecciona Google Drive, se muestran las imágenes (como en la figura 4). A partir de este punto, el usuario puede interactuar con ellas de las formas que admitan el proveedor y la app cliente.

selector

Figura 4: Imágenes

Cómo escribir una app cliente

En Android 4.3 y versiones anteriores, si quieres que tu aplicación muestre un archivo de otra app, debe invocar una intent como ACTION_PICK o ACTION_GET_CONTENT. El usuario debe seleccionar una sola aplicación desde la que se pueda seleccionar un archivo, y la aplicación seleccionada debe proporcionar una interfaz de usuario para que el usuario explore y seleccione entre los archivos disponibles.

En Android 4.4 y versiones posteriores, tienes la opción adicional de usar la intent ACTION_OPEN_DOCUMENT, que muestra la IU de un selector controlado por el sistema que le permite al usuario explorar todos los archivos disponibles de otras aplicaciones. Desde esta IU individual, el usuario puede seleccionar un archivo de cualquiera de las aplicaciones admitidas.

No se pretende que ACTION_OPEN_DOCUMENT sea un reemplazo de ACTION_GET_CONTENT. El que debes usar depende de las necesidades de tu aplicación:

  • Usa ACTION_GET_CONTENT si quieres que tu aplicación simplemente lea o importe datos. Con este enfoque, la aplicación importa una copia de los datos, como un archivo de imagen.
  • Usa ACTION_OPEN_DOCUMENT si quieres que tu app tenga acceso persistente y a largo plazo a documentos de un proveedor. Un ejemplo sería una aplicación de edición de fotos que les permitiera a los usuarios editar imágenes almacenadas en un proveedor de documentos.

Esta sección describe cómo escribir apps cliente en función de las intents ACTION_OPEN_DOCUMENT y ACTION_CREATE_DOCUMENT.

El siguiente fragmento de código usa ACTION_OPEN_DOCUMENT para buscar proveedores de documentos que contienen archivos de imagen:

Kotlin

private const val READ_REQUEST_CODE: Int = 42
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
fun performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as a
        // file (as opposed to a list of contacts or timezones)
        addCategory(Intent.CATEGORY_OPENABLE)

        // Filter to show only images, using the image MIME data type.
        // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
        // To search for all documents available via installed storage providers,
        // it would be "*/*".
        type = "image/*"
    }

    startActivityForResult(intent, READ_REQUEST_CODE)
}

Java

private static final int READ_REQUEST_CODE = 42;
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
public void performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones)
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only images, using the image MIME data type.
    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
    // To search for all documents available via installed storage providers,
    // it would be "*/*".
    intent.setType("image/*");

    startActivityForResult(intent, READ_REQUEST_CODE);
}

Ten en cuenta lo siguiente:

  • Cuando la aplicación envía la intent ACTION_OPEN_DOCUMENT, lanza un selector que muestra todos los proveedores de documentos coincidentes.
  • Agregar la categoría CATEGORY_OPENABLE a la intent filtra los resultados para mostrar solo documentos que se pueden abrir, como archivos de imagen.
  • La instrucción intent.setType("image/*") continúa con el filtrado para mostrar solo documentos que tienen el tipo de MIME imagen.

Resultados del proceso

Después de que el usuario elige un documento en el selector, se llama a onActivityResult(). El parámetro resultData contiene el URI que apunta al elemento seleccionado. Extrae el URI usando getData(). Una vez que lo tengas, podrás usarlo para recuperar el documento que quiera el usuario. Por ejemplo:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        resultData?.data?.also { uri ->
            Log.i(TAG, "Uri: $uri")
            showImage(uri)
        }
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            Log.i(TAG, "Uri: " + uri.toString());
            showImage(uri);
        }
    }
}

Cómo explorar metadatos de documentos

Una vez que tienes el URI de un documento, obtienes acceso a sus metadatos. Este fragmento de código captura y registra los metadatos de un documento especificado por el URI:

Kotlin

fun dumpImageMetaData(uri: Uri) {

    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since we want
    // all fields for one document.
    val cursor: Cursor? = contentResolver.query( uri, null, null, null, null, null)

    cursor?.use {
        // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (it.moveToFirst()) {

            // Note it's called "Display Name".  This is
            // provider-specific, and might not necessarily be the file name.
            val displayName: String =
                    it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
            Log.i(TAG, "Display Name: $displayName")

            val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE)
            // If the size is unknown, the value stored is null.  But since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "unpredictable".  So as
            // a rule, check if it's null before assigning to an int.  This will
            // happen often:  The storage API allows for remote files, whose
            // size might not be locally known.
            val size: String = if (!it.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                it.getString(sizeIndex)
            } else {
                "Unknown"
            }
            Log.i(TAG, "Size: $size")
        }
    }
}

Java

public void dumpImageMetaData(Uri uri) {

    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since we want
    // all fields for one document.
    Cursor cursor = getActivity().getContentResolver()
            .query(uri, null, null, null, null, null);

    try {
    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
    // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name".  This is
            // provider-specific, and might not necessarily be the file name.
            String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);

            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
            // If the size is unknown, the value stored is null.  But since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "unpredictable".  So as
            // a rule, check if it's null before assigning to an int.  This will
            // happen often:  The storage API allows for remote files, whose
            // size might not be locally known.
            String size = null;
            if (!cursor.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                size = cursor.getString(sizeIndex);
            } else {
                size = "Unknown";
            }
            Log.i(TAG, "Size: " + size);
        }
    } finally {
        cursor.close();
    }
}

Cómo abrir un documento

Una vez que tienes el URI de un documento, puedes abrirlo o hacer lo que quieras con él.

Mapa de bits

Aquí te mostramos un ejemplo de cómo podrías abrir un Bitmap:

Kotlin

@Throws(IOException::class)
private fun getBitmapFromUri(uri: Uri): Bitmap {
    val parcelFileDescriptor: ParcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r")
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
    val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
    parcelFileDescriptor.close()
    return image
}

Java

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}

Ten en cuenta que no debes hacer esto en el subproceso de la IU. Hazlo en segundo plano usando AsyncTask. Una vez que abres el mapa de bits, puedes mostrarlo en una ImageView.

Cómo obtener un InputStream

Aquí te mostramos un ejemplo de cómo obtener un InputStream del URI. En este fragmento de código, las líneas del archivo se leen en una string:

Kotlin

@Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
    val stringBuilder = StringBuilder()
    contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            var line: String? = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
        }
    }
    return stringBuilder.toString()
}

Java

private String readTextFromUri(Uri uri) throws IOException {
    StringBuilder stringBuilder = new StringBuilder();
    try (InputStream inputStream =
            getContentResolver().openInputStream(uri);
            BufferedReader reader = new BufferedReader(
            new InputStreamReader(Objects.requireNonNull(inputStream)))) {
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
    }
    return stringBuilder.toString();
}

Cómo crear un documento nuevo

Tu aplicación puede crear un documento nuevo en un proveedor de documentos usando la intent ACTION_CREATE_DOCUMENT. Para crear un archivo, le das a la intent un tipo de MIME y un nombre de archivo, y la lanzas con un código de solicitud único. No necesitas hacer nada más:

Kotlin

// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");

// Unique request code.
private const val WRITE_REQUEST_CODE: Int = 43
...
private fun createFile(mimeType: String, fileName: String) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as
        // a file (as opposed to a list of contacts or timezones).
        addCategory(Intent.CATEGORY_OPENABLE)

        // Create a file with the requested MIME type.
        type = mimeType
        putExtra(Intent.EXTRA_TITLE, fileName)
    }

    startActivityForResult(intent, WRITE_REQUEST_CODE)
}

Java

// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");

// Unique request code.
private static final int WRITE_REQUEST_CODE = 43;
...
private void createFile(String mimeType, String fileName) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);

    // Filter to only show results that can be "opened", such as
    // a file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Create a file with the requested MIME type.
    intent.setType(mimeType);
    intent.putExtra(Intent.EXTRA_TITLE, fileName);
    startActivityForResult(intent, WRITE_REQUEST_CODE);
}

Una vez que creas un documento nuevo, puedes obtener su URI en onActivityResult(), de modo que puedas continuar escribiendo en él.

Cómo borrar un documento

Si tienes el URI de un documento y el objeto Document.COLUMN_FLAGS del documento contiene SUPPORTS_DELETE, puedes borrar el documento en cuestión. Por ejemplo:

Kotlin

DocumentsContract.deleteDocument(contentResolver, uri)

Java

DocumentsContract.deleteDocument(getContentResolver(), uri);

Cómo editar un documento

Puedes usar el SAF para editar un documento de texto. El fragmento activa la intent ACTION_OPEN_DOCUMENT y usa la categoría CATEGORY_OPENABLE para mostrar solo los documentos que se pueden abrir. Continúa filtrando para mostrar únicamente archivos de texto:

Kotlin

private const val EDIT_REQUEST_CODE: Int = 44
/**
 * Open a file for writing and append some text to it.
 */
private fun editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        // Filter to only show results that can be "opened", such as a
        // file (as opposed to a list of contacts or timezones).
        addCategory(Intent.CATEGORY_OPENABLE)

        // Filter to show only text files.
        type = "text/plain"
    }

    startActivityForResult(intent, EDIT_REQUEST_CODE)
}

Java

private static final int EDIT_REQUEST_CODE = 44;
/**
 * Open a file for writing and append some text to it.
 */
 private void editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only text files.
    intent.setType("text/plain");

    startActivityForResult(intent, EDIT_REQUEST_CODE);
}

A continuación, desde onActivityResult() (consulta Resultados del proceso) puedes llamar a código para realizar la edición. El siguiente fragmento de código obtiene un FileOutputStream del ContentResolver. De forma predeterminada, usa el modo de escritura. Se recomienda solicitar la menor cantidad de acceso que necesites de modo que no solicites acceso de lectura o escritura cuando lo único que necesitas sea escritura:

Kotlin

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            // use{} lets the document provider know you're done by automatically closing the stream
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten by MyCloud at ${System.currentTimeMillis()}\n").toByteArray()
                )
            }
        }
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

Java

private void alterDocument(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getActivity().getContentResolver().
                openFileDescriptor(uri, "w");
        FileOutputStream fileOutputStream =
                new FileOutputStream(pfd.getFileDescriptor());
        fileOutputStream.write(("Overwritten by MyCloud at " +
                System.currentTimeMillis() + "\n").getBytes());
        // Let the document provider know you're done by closing the stream.
        fileOutputStream.close();
        pfd.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Permisos persistentes

Cuando tu aplicación abre un archivo para leer o escribir, el sistema le otorga a tu aplicación un permiso de URI para ese archivo, que dura hasta que se reinicia el dispositivo del usuario. Pero supón que tu aplicación edita fotos y quieres que los usuarios puedan acceder a las últimas 5 imágenes que editaron directamente desde tu aplicación. Si se reinició el dispositivo del usuario, tendrías que enviar de regreso al usuario al selector del sistema para buscar los archivos, y esto, obviamente, no es lo ideal.

Para evitar que esto ocurra, puedes conservar los permisos que el sistema le concede a tu aplicación. De hecho, tu aplicación "toma" el permiso de URI persistente que ofrece el sistema. Esto le permite al usuario acceder de forma continua a los archivos a través de tu aplicación, aunque se haya reiniciado el dispositivo:

Kotlin

val takeFlags: Int = intent.flags and
        (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
// Check for the freshest data.
contentResolver.takePersistableUriPermission(uri, takeFlags)

Java

final int takeFlags = intent.getFlags()
            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);

Hay un paso final. Es posible que los URI recientes a los que haya accedido tu app ya no sean válidos; otra aplicación puede haber borrado o modificado un documento. Por eso, siempre debes llamar a getContentResolver().takePersistableUriPermission() para comprobar los datos más actualizados.

Cómo abrir archivos virtuales

Android 7.0 incorpora el concepto de archivos virtuales al marco de trabajo de acceso al almacenamiento. Si bien los archivos virtuales no tienen una representación binaria, tu app cliente puede abrir su contenido coaccionándolo en un tipo de archivo diferente o viendo esos archivos usando una intent ACTION_VIEW.

Para abrir archivos virtuales, tu app cliente necesita incluir una lógica especial para manejarlos. Si quieres obtener una representación en bytes del archivo (por ejemplo, para obtener una vista previa del archivo), debes solicitarle un tipo de MIME alternativo al proveedor de documentos.

Para obtener un URI de un documento virtual en tu app, primero crea una Intent a fin de abrir la IU del selector de archivos, como el código que se mostró anteriormente en Cómo buscar documentos.

Después de que el usuario hace una selección, el sistema llama al método onActivityResult(), como se mostró anteriormente en Resultados del proceso. Tu aplicación puede recuperar la URI del archivo y luego determinar si el archivo es virtual utilizando un método similar al siguiente fragmento de código.

Kotlin

private fun isVirtualFile(uri: Uri): Boolean {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false
    }

    val cursor: Cursor? = contentResolver.query(
            uri,
            arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
            null,
            null,
            null
    )

    val flags: Int = cursor?.use {
        if (cursor.moveToFirst()) {
            cursor.getInt(0)
        } else {
            0
        }
    } ?: 0

    return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
}

Java

private boolean isVirtualFile(Uri uri) {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false;
    }

    Cursor cursor = getContentResolver().query(
        uri,
        new String[] { DocumentsContract.Document.COLUMN_FLAGS },
        null, null, null);

    int flags = 0;
    if (cursor.moveToFirst()) {
        flags = cursor.getInt(0);
    }
    cursor.close();

    return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}

Después de verificar que el archivo sea virtual, podrás coaccionarlo para que se convierta en un tipo de MIME alternativo, como un archivo de imagen. El siguiente fragmento de código muestra cómo comprobar si se puede presentar un archivo virtual como una imagen y, si es así, obtiene una transmisión de entrada del archivo virtual.

Kotlin

@Throws(IOException::class)
private fun getInputStreamForVirtualFile(uri: Uri, mimeTypeFilter: String): InputStream {

    val openableMimeTypes: Array<String>? = contentResolver.getStreamTypes(uri, mimeTypeFilter)

    return if (openableMimeTypes?.isNotEmpty() == true) {
        contentResolver
                .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
                .createInputStream()
    } else {
        throw FileNotFoundException()
    }
}

Java


private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
    throws IOException {

    ContentResolver resolver = getContentResolver();

    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);

    if (openableMimeTypes == null ||
        openableMimeTypes.length &lt; 1) {
        throw new FileNotFoundException();
    }

    return resolver
        .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
        .createInputStream();
}

Para obtener más información acerca de los archivos virtuales y cómo manejarlos en la app cliente del marco de trabajo de acceso al almacenamiento, mira el video Archivos virtuales en el marco de trabajo de acceso al almacenamiento.

Para ver un ejemplo de código relacionado con esta página, consulta los siguientes recursos:

Para ver videos relacionados con esta página, consulta:

Para obtener más información relacionada, consulta: