Cómo crear un proveedor de contenido

Un proveedor de contenido administra el acceso a un repositorio central de datos. Implementas un como una o más clases en una aplicación para Android, junto con elementos en el archivo de manifiesto. Una de tus clases implementa una subclase de ContentProvider, que es la interfaz entre tu proveedor y otras aplicaciones.

Si bien el objetivo de los proveedores de contenido es poner los datos a disposición aplicaciones, puedes tener actividades en tu aplicación que permitan al usuario consultar y modificar los datos administrados por tu proveedor.

En esta página, se incluye el proceso básico para crear un proveedor de contenido y una lista de APIs que se pueden usar.

Antes de que empieces a crear

Antes de que empieces a crear un proveedor, haz lo siguiente:

  • Decide si necesitas un proveedor de contenido. Debes crear contenido si quieres proporcionar una o más de las siguientes funciones:
    • Te recomendamos que ofrezcas datos o archivos complejos para otras aplicaciones.
    • Quieres permitir que los usuarios copien datos complejos de tu app a otras apps.
    • Te recomendamos que proporciones sugerencias personalizadas usando el marco de trabajo de búsqueda.
    • Quieres exponer los datos de tu aplicación a widgets.
    • Quieres implementar AbstractThreadedSyncAdapter, CursorAdapter o CursorLoader .

    No necesitas un proveedor para usar bases de datos ni otros tipos de el almacenamiento persistente si el uso es completamente dentro de su propia aplicación y no necesitas ninguna de las funciones anteriores que se mencionan. En cambio, puedes usar uno de los sistemas de almacenamiento descritos en Descripción general del almacenamiento de datos y archivos.

  • Si aún no lo hiciste, lee Conceptos básicos sobre los proveedores de contenido para obtener más información sobre los proveedores y su funcionamiento.

A continuación, sigue estos pasos para crear un proveedor:

  1. Diseña el almacenamiento sin formato para tus datos. Un proveedor de contenido ofrece datos de dos maneras:
    Datos de archivos
    Los datos que normalmente se presentan en archivos, como fotos, audios o videos. Guarda los archivos en la carpeta privada de tu aplicación espacio. En respuesta a una solicitud de un archivo por parte de otra aplicación, tu el proveedor puede ofrecer un handle para el archivo.
    "Estructurados" datos
    Son datos que normalmente se encuentran en una base de datos, un array o una estructura similar. Almacena los datos en un formato que sea compatible con tablas de filas y columnas. Una fila Representa una entidad, como una persona o un elemento en el inventario. Una columna representa algunos datos de la entidad, como el nombre de la persona o el precio de un artículo. Una forma común de este tipo de datos es en una base de datos SQLite, pero puedes usar cualquier tipo y el almacenamiento persistente. Para obtener más información sobre los tipos de almacenamiento disponibles en el sistema Android, consulta la Diseña el almacenamiento de datos.
  2. Define una implementación concreta de la clase ContentProvider y los métodos requeridos. Esta clase es la interfaz entre tus datos y el resto de la Sistema Android Para obtener más información sobre esta clase, consulta el Implementa la sección de la clase ContentProvider.
  3. Define la cadena de autoridad, los URI de contenido y los nombres de columnas del proveedor. Si quieres la aplicación del proveedor para manejar intents, además de definir acciones de intents, datos adicionales y marcas. Define también los permisos que necesitas para las aplicaciones que para acceder a tus datos. Considera definir todos estos valores como constantes en un clase Contract independiente. Más adelante, puedes exponer esta clase a otros desarrolladores. Para ver más información sobre URI de contenido, consulta la Sección Diseña URI de contenido. Para obtener más información sobre los intents, consulta el Sección Intents y acceso a datos.
  4. Agrega otras piezas opcionales, como datos de muestra o una implementación de AbstractThreadedSyncAdapter que pueden sincronizar datos entre el proveedor y los datos basados en la nube.

Diseña el almacenamiento de datos

Un proveedor de contenido es la interfaz para los datos guardados en un formato estructurado. Antes de crear la interfaz, decide cómo almacenar los datos. Puedes almacenar los datos de cualquier forma que y, luego, diseñar la interfaz para leer y escribir los datos según sea necesario.

Estas son algunas de las tecnologías de almacenamiento de datos disponibles en Android:

  • Si trabajas con datos estructurados, considera una base de datos relacional como SQLite o un almacén de datos de clave-valor no relacional, como LevelDB. Si trabajas en con datos no estructurados, como medios de audio, imagen o video, considere almacenar el datos como archivos. Puedes mezclar y exponer diferentes tipos de almacenamiento. utilizando un único proveedor de contenido, si es necesario.
  • El sistema Android puede interactuar con la biblioteca de persistencias Room, que proporciona acceso a la API de base de datos SQLite que los proveedores de Android usar para almacenar datos orientados a tablas. Para crear una base de datos usando biblioteca, crear una instancia de una subclase de RoomDatabase, como se describe en Cómo guardar contenido en una base de datos local con Room.

    No necesitas usar una base de datos para implementar tu repositorio. Un proveedor aparece externamente como un conjunto de tablas, similar a una base de datos relacional, pero es Esto no es un requisito para la implementación interna del proveedor.

  • Para guardar datos de archivo, Android tiene una variedad de API orientadas a archivos. Para obtener más información sobre el almacenamiento de archivos, lee el Descripción general del almacenamiento de datos y archivos. Si estás diseñas un proveedor que ofrece datos relacionados con medios de comunicación, como música o videos, puedes tener un proveedor que combina archivos y datos de tablas.
  • En casos excepcionales, podrías beneficiarte de implementar más de un proveedor de contenido para en una sola aplicación. Por ejemplo, tal vez quieras compartir algunos datos con un widget usando un proveedor de contenido y exponer un conjunto diferente de datos para compartir con otros aplicaciones.
  • Para trabajar con datos basados en la red, usa clases en java.net y android.net También puedes sincronizar datos basados en la red con datos locales como una base de datos y, luego, ofrecer los datos como tablas o archivos.

Nota: Si realizas un cambio en tu repositorio que no retrocompatible, debes marcar el repositorio con una versión nueva de la fila. También debes aumentar el número de versión de tu app implementa el proveedor de contenido nuevo. Este cambio impide que el sistema regresa a una versión anterior para evitar que falle el sistema cuando intente reinstalar un que tiene un proveedor de contenido incompatible.

Consideraciones del diseño de datos

Aquí hay algunas sugerencias para diseñar la estructura de datos de tu proveedor:

  • Los datos de tabla siempre deben tener una "clave primaria" que el proveedor mantiene como un valor numérico único para cada fila. Puedes usar este valor para vincular la fila a elementos relacionados filas en otras tablas (lo que se usa como “clave externa”). Si bien puedes usar cualquier nombre para esta columna, usar BaseColumns._ID es la mejor opción, ya que vincular los resultados de una consulta al proveedor con una ListView requiere que una de las columnas recuperadas tenga el nombre _ID
  • Si quieres proporcionar imágenes de mapas de bits u otros datos muy grandes orientados a archivos, almacena los datos en un archivo y luego proporcionarlos indirectamente, en lugar de almacenarlos directamente en un desde una tabla de particiones. Si lo hace, debe indicarles a los usuarios de su proveedor que deben utilizar un ContentResolver para acceder a los datos.
  • Usa el tipo de datos de objeto binario grande (BLOB) para almacenar datos que varían en tamaño o tienen una estructura variable. Por ejemplo, puedes usar una columna BLOB para almacenar un búfer de protocolo o Estructura JSON:

    También puedes usar un BLOB para implementar una tabla independiente del esquema. En este tipo de tabla, se define una columna de clave primaria, una columna de tipo de MIME, y una o y más genéricas como BLOB. El significado de los datos en las columnas BLOB se indica por el valor de la columna de tipo de MIME. Esto te permite almacenar diferentes tipos de filas la misma tabla. Los “datos” del proveedor de contactos tabla ContactsContract.Data es un ejemplo de un esquema independiente desde una tabla de particiones.

Diseña URI de contenido

Un URI de contenido es un URI que identifica datos de un proveedor. Los URI de contenido incluyen el nombre simbólico de todo el proveedor (su autoridad) y una nombre que apunta a una tabla o archivo (una ruta). La parte de ID opcional apunta a una fila individual de una tabla. Cada método de acceso a los datos de ContentProvider tiene un URI de contenido como argumento. Esto te permite determinar la tabla, la fila o el archivo al que se accederá.

Para obtener más información sobre los URI de contenido, consulta Conceptos básicos sobre el proveedor de contenido

Diseña una autoridad

Un proveedor generalmente tiene una sola autoridad, que sirve como su nombre interno en Android. Para Evitar conflictos con otros proveedores, usar la propiedad del dominio de Internet (a la inversa) como base de la autoridad de tu proveedor. Porque esta recomendación también se aplica para Android nombres de paquetes, puedes definir la autoridad de tu proveedor como una extensión del nombre del paquete que contiene el proveedor.

Por ejemplo, si el nombre de tu paquete de Android es com.example.<appname>, proporciona a tu proveedor autoridad com.example.<appname>.provider.

Diseña la estructura de una ruta de acceso.

Los desarrolladores suelen crear URI de contenido a partir de la autoridad agregando rutas de acceso que apuntan a tablas individuales. Por ejemplo, si tienes dos tablas, table1 y table2, puedes combinarlas con la autoridad del ejemplo anterior para obtener la URI de contenido com.example.<appname>.provider/table1 y com.example.<appname>.provider/table2 Las rutas no se se limita a un solo segmento, y no es necesario que haya una tabla para cada nivel de la ruta.

Cómo controlar los IDs de URI de contenido

Por convención, los proveedores ofrecen acceso a una sola fila de una tabla mediante la aceptación de un URI de contenido con un valor de ID para la fila al final del URI. Además, por convención, los proveedores hacen coincidir ID de salida a la columna _ID de la tabla y realizar el acceso solicitado en el que coincida.

Esta convención facilita un patrón de diseño común para aplicaciones que acceden a un proveedor. La app realiza una consulta al proveedor y muestra el Cursor resultante en un ListView con un CursorAdapter. La definición de CursorAdapter requiere una de las columnas en el Cursor, _ID

Luego, el usuario elige una de las filas mostradas de la IU para observar o modificar la de datos no estructurados. La app obtiene la fila correspondiente del Cursor que respalda la ListView, obtiene el valor _ID para esta fila y lo agrega a el URI de contenido y envía la solicitud de acceso al proveedor. Luego, el proveedor puede hacer una consulta o una modificación con respecto a la fila exacta que eligió el usuario.

Patrones de URI de contenido

Para ayudarte a elegir qué acción realizar para un URI de contenido entrante, la API del proveedor incluye lo siguiente: la clase de conveniencia UriMatcher, que asigna patrones de URI de contenido a de números enteros. Puedes usar los valores de número entero en una sentencia switch que selecciona la acción deseada para los URI de contenido que coincidan con un patrón en particular.

Un patrón de URI de contenido compara URI de contenido usando caracteres comodín:

  • * coincide con una cadena de caracteres válidos de cualquier longitud.
  • # coincide con una cadena de caracteres numéricos de cualquier longitud.

Como ejemplo de diseño y programación de la manipulación de URI de contenido, considera un proveedor con el autoridad com.example.app.provider que reconoce los siguientes URI de contenido que apunta a las tablas:

  • content://com.example.app.provider/table1: Es una tabla llamada table1.
  • content://com.example.app.provider/table2/dataset1: una tabla llamada dataset1
  • content://com.example.app.provider/table2/dataset2: una tabla llamada dataset2
  • content://com.example.app.provider/table3: Es una tabla llamada table3.

El proveedor también reconoce estos URI de contenido si tienen un ID de fila anexado, como content://com.example.app.provider/table3/1 para la fila identificada por 1 en table3.

Los siguientes patrones de URI de contenido son posibles:

content://com.example.app.provider/*
Coincide con cualquier URI de contenido en el proveedor.
content://com.example.app.provider/table2/*
Coincide con un URI de contenido para las tablas dataset1 y dataset2, pero no coincide con los URI de contenido para table1 ni table3
content://com.example.app.provider/table3/#
Coincide con un URI de contenido para filas individuales en table3, como content://com.example.app.provider/table3/6 para la fila identificada por 6

En el siguiente fragmento de código, se muestra cómo funcionan los métodos de UriMatcher. Este código maneja los URIs de una tabla completa de manera diferente a los URI de un una sola fila mediante el patrón de URI de contenido content://<authority>/<path> para tablas y content://<authority>/<path>/<id> para las filas individuales.

El método addURI() asigna un autoridad y ruta de acceso a un valor de número entero. El método match() muestra el valor entero de un URI. Una sentencia switch decide entre consultar toda la tabla o consultar un solo registro.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

Otra clase, ContentUris, proporciona métodos prácticos para trabajar con la parte id de los URI de contenido Las clases Uri y Los Uri.Builder incluyen métodos de conveniencia para analizar los existentes. Uri y crear nuevos.

Implementa la clase ContentProvider

La instancia ContentProvider administra el acceso a un conjunto estructurado de datos mediante el manejo de solicitudes de otras aplicaciones. Todas las formas de acceso finalmente llama a ContentResolver, que luego llama a un elemento método de ContentProvider para obtener acceso.

Métodos obligatorios

La clase abstracta ContentProvider define seis métodos abstractos que que implementas como parte de tu subclase concreta. Todos estos métodos, excepto Una aplicación cliente llama a onCreate() que intenta acceder a tu proveedor de contenido.

query()
Recupera datos de tu proveedor. Usa los argumentos para seleccionar la tabla que quieres la consulta, las filas y columnas que se devolverán, y el orden de clasificación del resultado. Muestra los datos como un objeto Cursor.
insert()
Inserta una fila nueva en tu proveedor. Usa los argumentos para seleccionar en una tabla de destino y obtener los valores de columna que se usarán. Devuelve un URI de contenido para el fila recién insertada.
update()
Actualiza las filas existentes en tu proveedor. Usa los argumentos para seleccionar la tabla y las filas para actualizar y obtener los valores de columna actualizados. Muestra el número de filas actualizadas.
delete()
Borra filas de tu proveedor. Usa los argumentos para seleccionar la tabla y las filas a borrar. Muestra el número de filas borradas.
getType()
Devuelve el tipo de MIME correspondiente a un URI de contenido. Este método se describe en más detalle consulta la sección Cómo implementar los tipos de MIME del proveedor de contenido.
onCreate()
Inicializa tu proveedor. El sistema Android llama a este método inmediatamente después de crea tu proveedor. Tu proveedor no se crea hasta que se ContentResolver intenta acceder a ella.

Estos métodos tienen la misma firma que los métodos idénticos ContentResolver.

La implementación de estos métodos debe tener en cuenta lo siguiente:

  • Todos estos métodos, excepto onCreate() pueden llamarlos varios subprocesos a la vez, por lo que deben ser seguros para estos. Para aprender más información sobre varios subprocesos, consulta la Descripción general de los procesos y subprocesos.
  • Evita realizar operaciones extensas en onCreate(). Aplaza tareas de inicialización hasta que sean necesarias. La sección sobre cómo implementar el método onCreate() lo analiza con más detalle.
  • Si bien debes implementar estos métodos, tu código no necesita hacer nada, devuelva el tipo de datos esperado. Por ejemplo, puedes evitar que otras aplicaciones de insertar datos en algunas tablas ignorando la llamada a insert() y regreso 0.

Implementa el método query()

El El método ContentProvider.query() debe mostrar un objeto Cursor o, si falla, se arroja una Exception. Si estás usando una base de datos SQLite como tus datos almacenamiento, puedes mostrar el Cursor devuelto por uno de los Métodos query() de la clase SQLiteDatabase

Si la consulta no coincide con ninguna fila, muestra un Cursor. instancia cuyo método getCount() muestra 0. Muestra null solo si se produjo un error interno durante el proceso de consulta.

Si no estás usando una base de datos SQLite como almacenamiento de datos, usa una de las subclases concretas. de Cursor. Por ejemplo, la clase MatrixCursor implementa un cursor en el que cada fila es un array de instancias de Object. Con esta clase, Usa addRow() para agregar una fila nueva.

El sistema Android debe poder comunicar el Exception. más allá de los límites del proceso. Android puede hacer esto para las siguientes excepciones que resultan útiles a la hora de manejar errores en las consultas:

Implementa el método insert()

El método insert() agrega un nueva fila a la tabla adecuada con los valores de ContentValues argumento. Si el nombre de una columna no está en el argumento ContentValues, podrías querer proporcionar un valor predeterminado, ya sea en tu código de proveedor o en tu base de datos .

Este método muestra el URI de contenido para la nueva fila. Para construir esto, agrega el nuevo la clave primaria de la fila, por lo general, el valor _ID, al URI de contenido de la tabla mediante withAppendedId()

Implementa el método delete()

El método delete() no tiene que borrar filas de tu almacenamiento de datos. Si usas un adaptador de sincronización con tu proveedor, considera marcar una fila borrada con "delete" marca en lugar de quitar la fila por completo. El adaptador de sincronización puede verificar si hay filas borradas y quitarlas del servidor antes de borrarlas del proveedor

Cómo implementar el método update()

El método update() toma el mismo argumento ContentValues que usa insert() y las los mismos argumentos selection y selectionArgs que usa delete() y ContentProvider.query() Esto podría permitirte reutilizar el código entre estos métodos.

Cómo implementar el método onCreate()

El sistema Android llama a onCreate(). cuando inicia el proveedor. Realiza solo la inicialización de ejecución rápida tareas en este método y aplazar la creación de la base de datos y la carga de datos hasta que el proveedor recibe una solicitud de los datos. Si realizas tareas largas en onCreate(), ralentizas el inicio del proveedor. A su vez, esto ralentiza la respuesta del proveedor a otros aplicaciones.

Los dos fragmentos siguientes demuestran la interacción entre ContentProvider.onCreate() y Room.databaseBuilder() La primera Este fragmento muestra la implementación de ContentProvider.onCreate() donde el se compila el objeto de base de datos y se crean los controladores de los objetos de acceso a datos:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Implementa tipos de MIME ContentProvider

La clase ContentProvider tiene dos métodos para mostrar tipos de MIME:

getType()
Uno de los métodos obligatorios que implementas para cualquier proveedor.
getStreamTypes()
Es un método que se espera que implementes si tu proveedor ofrece archivos.

Tipos de MIME para tablas

El método getType() devuelve un String en formato MIME que describe el tipo de datos que muestra el contenido argumento de URI. El argumento Uri puede ser un patrón en lugar de un URI específico. En este caso, muestra el tipo de datos asociados con los URI de contenido que coincidan con el .

Para los tipos de datos comunes, como texto, HTML o JPEG, getType() devuelve el valor estándar Es el tipo de MIME de esos datos. Puedes encontrar una lista completa de estos tipos estándar en la Tipos de medios MIME de IANA sitio web.

Para los URI de contenido que apuntan a una o más filas de datos de tabla, Devoluciones por getType() Un tipo de MIME en formato MIME específico del proveedor de Android:

  • Parte tipográfica: vnd
  • Parte de subtipo:
    • Si el patrón del URI es para una sola fila: android.cursor.item/
    • Si el patrón del URI es para más de una fila: android.cursor.dir/
  • Parte específica del proveedor: vnd.<name>.<type>

    Proporcionas el <name> y la <type>. El valor <name> es único a nivel global, y el valor <type> es único para el URI correspondiente . Una buena opción para <name> es el nombre de tu empresa o alguna parte del nombre del paquete de Android de tu aplicación. Una buena opción para el <type> es una cadena que identifica la tabla asociada con el URI

Por ejemplo, si la autoridad de un proveedor es com.example.app.provider y expone una tabla llamada table1, el tipo de MIME para varias filas en table1 es el siguiente:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Para una sola fila de table1, el tipo de MIME es el siguiente:

vnd.android.cursor.item/vnd.com.example.provider.table1

Tipos de MIME para archivos

Si tu proveedor ofrece archivos, implementa getStreamTypes() El método devuelve un array String de tipos de MIME para los archivos de tu proveedor. para un URI de contenido determinado. Filtra los tipos de MIME que ofreces por tipo de MIME filter, de modo que devuelvas solo los tipos de MIME que el cliente quiere manejar.

Por ejemplo, considera un proveedor que ofrece imágenes en formato JPG, PNG y GIF. Si una aplicación llama a ContentResolver.getStreamTypes() con la cadena de filtro image/*, para algo que es una "imagen", Luego, el método ContentProvider.getStreamTypes() muestra el array:

{ "image/jpeg", "image/png", "image/gif"}

Si a la app solo le interesan los archivos JPG, puede llamar ContentResolver.getStreamTypes() por la cadena de filtro *\/jpeg. getStreamTypes() muestra lo siguiente:

{"image/jpeg"}

Si tu proveedor no ofrece ninguno de los tipos de MIME solicitados en la cadena de filtro, getStreamTypes() muestra null.

Cómo implementar una clase Contract

Una clase Contract es una clase public final que contiene definiciones de constantes para el URI, nombres de columnas, tipos de MIME y otros metadatos que pertenecen al proveedor. La clase establece un contrato entre el proveedor y otras aplicaciones garantizando que el proveedor se pueda acceder correctamente incluso si hay cambios en los valores reales de los URI, los nombres de las columnas etcétera.

Una clase Contract también ayuda a los desarrolladores porque suele tener nombres mnemotécnicos para sus constantes. por lo que es menos probable que los desarrolladores usen valores incorrectos para los nombres de las columnas o los URI. Ya que es un esta clase puede contener documentación Javadoc. Entornos de desarrollo integrados, como Android Studio puede autocompletar nombres de constantes desde la clase Contract y mostrar Javadoc para las constantes.

Los desarrolladores no pueden acceder al archivo de clase de la clase Contract desde tu aplicación, pero pueden compilarla estáticamente en su aplicación desde un archivo JAR que tú proporciones.

La clase ContactsContract y sus clases anidadas son ejemplos de lo siguiente: Contract.

Cómo implementar permisos del proveedor de contenido

Los permisos y el acceso para todos los aspectos del sistema Android se describen en detalle en Sugerencias de seguridad. La descripción general del almacenamiento de datos y archivos también describe la seguridad y los permisos vigentes para varios tipos de almacenamiento. En resumen, los puntos importantes son los siguientes:

  • De forma predeterminada, los archivos de datos guardados en el almacenamiento interno del dispositivo son privados para tu y el proveedor de servicios en la nube.
  • SQLiteDatabase bases de datos que creas son privadas para tu y el proveedor de servicios en la nube.
  • De forma predeterminada, los archivos de datos que guardas en el almacenamiento externo son públicos y legible para todo el mundo. No puedes usar un proveedor de contenido para restringir el acceso a archivos en externo, ya que otras aplicaciones pueden usar otras llamadas a la API para leerlos y escribirlos.
  • El método llama a abrir o crear archivos o bases de datos SQLite en la memoria interna de tu dispositivo. puede otorgar acceso de lectura y escritura a todas las demás aplicaciones. Si usar un archivo o una base de datos internos como repositorio de tu proveedor y le das “legible para todo el mundo” o que se puedan escribir en todo el mundo los permisos que estableces para tu proveedor en el manifiesto no protegen tus datos. El acceso predeterminado para archivos y bases de datos en el almacenamiento interno es "privado"; no cambies esto para el repositorio de tu proveedor.

Si quieres usar los permisos del proveedor de contenido para controlar el acceso a tus datos, entonces almacenar tus datos en archivos internos, bases de datos SQLite o en la nube, como en un servidor remoto, y mantener la privacidad de los archivos y las bases de datos.

Implementa permisos

Por defecto, todas las aplicaciones pueden leer de tu proveedor o escribir en él, incluso si los datos subyacentes se privada, ya que, de forma predeterminada, tu proveedor no tiene permisos establecidos. Para cambiar esto, establecer permisos para tu proveedor en tu archivo de manifiesto usando atributos o del elemento <provider>. Puedes establecer permisos que se apliquen a todo el proveedor a ciertas tablas, a ciertos registros o a los tres.

Defines permisos para tu proveedor con uno o más <permission> en tu archivo de manifiesto. Para que el permiso exclusivo de tu proveedor, usa el alcance de estilo Java para el atributo android:name. Por ejemplo, asigna un nombre al permiso de lectura com.example.app.provider.permission.READ_PROVIDER

La siguiente lista describe el alcance de los permisos del proveedor, empezando por el permisos que se aplican a todo el proveedor y que luego se vuelven más detallados. Los permisos más detallados tienen prioridad sobre los de mayor alcance.

Permiso individual de lectura y escritura a nivel del proveedor
Un permiso que controla el acceso de lectura y escritura a todo el proveedor, especificado con el atributo android:permission de la elemento <provider>.
Separar permisos de lectura y escritura a nivel del proveedor
Un permiso de lectura y un permiso de escritura para todo el proveedor. Tú los especificas con el android:readPermission y Atributos android:writePermission del elemento <provider>. Tienen prioridad sobre el permiso requerido por android:permission
Permiso a nivel de ruta de acceso
Permiso de lectura, escritura o lectura/escritura para un URI de contenido en tu proveedor. Tú especificas cada URI que quieres controlar con una Elemento secundario <path-permission> del elemento <provider>. Para cada URI de contenido que especifiques, puedes especificar una un permiso de lectura/escritura, un permiso de lectura, uno de escritura o los tres. Las funciones de lectura y los permisos de escritura tienen prioridad sobre el permiso de lectura y escritura. Además, el nivel de ruta el permiso tiene prioridad sobre los permisos a nivel del proveedor.
Permiso temporal
Un nivel de permiso que otorga acceso temporal a una aplicación, incluso si la aplicación no cuenta con los permisos que se requieren normalmente. La herramienta de acceso reduce la cantidad de permisos que una aplicación debe solicitar en su manifiesto. Cuando activas los permisos temporales, las únicas aplicaciones que necesitan los permisos permanentes de tu proveedor son aquellos que acceden de forma continua tus datos.

Por ejemplo, considera los permisos que necesitas si implementas un proveedor de correo electrónico quieres permitir que una aplicación externa de visualización de imágenes muestre archivos adjuntos de fotos de tu proveedor. Para otorgarle al visor de imágenes el acceso necesario sin solicitar permisos, puedes establecer permisos temporales para los URI de contenido para las fotos.

Diseña tu aplicación de correo electrónico para que, cuando el usuario quiera mostrar una foto, la app envíe un intent con el el URI de contenido de una foto y las marcas de permiso al visor de imágenes. El visor de imágenes puede luego consulta a tu proveedor de correo electrónico para recuperar la foto aunque el usuario no haya tener el permiso de lectura normal para tu proveedor.

Para activar los permisos temporales, configura Atributo android:grantUriPermissions del <provider> elemento o agrega uno o más <grant-uri-permission> elementos secundarios en tu <provider>. Llamada Context.revokeUriPermission() cada vez que quites la compatibilidad con un URI de contenido asociado con un permiso temporal de tu proveedor.

El valor del atributo determina la porción del proveedor a la que se puede acceder. Si el atributo se establece en "true", el sistema otorga restricciones permiso a todo el proveedor para anular los demás permisos necesarios según tus permisos a nivel de proveedor o a nivel de ruta de acceso.

Si esta marca está configurada en "false", agrega <grant-uri-permission> elementos secundarios en tu elemento <provider>. Cada elemento secundario especifica el URI de contenido o Los URI para los que se otorga acceso temporal.

Para delegar el acceso temporal a una aplicación, un intent debe contener la marca FLAG_GRANT_READ_URI_PERMISSION, el FLAG_GRANT_WRITE_URI_PERMISSION o ambas. Estos se establecen con el método setFlags().

Si el atributo android:grantUriPermissions no está presente, se supone que es "false"

El <provider> elemento

Al igual que los componentes Activity y Service, una subclase de ContentProvider se define en el archivo de manifiesto de su aplicación con el <provider>. El sistema Android obtiene la siguiente información de el elemento:

Autoridad (android:authorities)
Nombres simbólicos que identifican a todo el proveedor dentro del sistema. Esta se describe con más detalle en el Sección Diseña URI de contenido.
Nombre de clase del proveedor (android:name)
Es la clase que implementa ContentProvider. Esta clase es se describe con más detalle en el Implementa la sección de la clase ContentProvider.
Permisos
atributos que especifican los permisos que otras aplicaciones deben tener para acceder datos del proveedor:

Los permisos y sus atributos correspondientes se describen en más detalle en detalle en la Sección Implementa los permisos del proveedor de contenido.

Atributos de inicio y control
Estos atributos determinan cómo y cuándo el sistema Android inicia el proveedor, el características del proceso del proveedor y otras configuraciones del entorno de ejecución:
  • android:enabled: Es una marca que permite que el sistema inicie el proveedor.
  • android:exported: Marca que permite que otras aplicaciones usen este proveedor
  • android:initOrder: Es el orden en el que se inicia este proveedor. en relación con otros proveedores en el mismo proceso
  • android:multiProcess: Es una marca que permite que el sistema inicie el proveedor. en el mismo proceso que el cliente que realiza la llamada
  • android:process: Es el nombre del proceso en el que se ejecuta el proveedor.
  • android:syncable: marca que indica que los datos del proveedor se sincronizada con datos en un servidor

Estos atributos están completamente documentados en la guía del <provider>.

Atributos informativos
Un ícono y una etiqueta opcionales para el proveedor:
  • android:icon: Es un recurso de elementos de diseño que contiene un ícono para el proveedor. El ícono aparece junto a la etiqueta del proveedor en la lista de apps de Configuración > Apps > Todas.
  • android:label: Es una etiqueta informativa que describe el proveedor, su datos o ambos. La etiqueta aparecerá en la lista de apps de Configuración > Apps > Todas.

Estos atributos están completamente documentados en la guía del <provider>.

Nota: Si tu app se orienta a Android 11 o versiones posteriores, consulta la documentación de visibilidad de paquetes para las necesidades de configuración adicionales.

Intents y acceso a datos

Las aplicaciones pueden acceder a un proveedor de contenido de forma indirecta con un Intent. La aplicación no llama a ninguno de los métodos de ContentResolver. ContentProvider En su lugar, envía un intent que inicia una actividad, que a menudo forma parte de la aplicación del proveedor. La actividad de destino está a cargo de recuperar y mostrar los datos en su IU.

Según la acción en el intent, el la actividad de destino también puede indicarle al usuario que realice modificaciones en los datos del proveedor. Un intent también puede contener "extras" datos que muestra la actividad de destino en la IU. El usuario tiene la opción de cambiar estos datos antes de usarlos para modificar la datos en el proveedor.

Puedes usar el acceso mediante intents para mejorar la integridad de los datos. Es posible que tu proveedor dependa para insertar, actualizar y borrar datos según una lógica empresarial bien definida. Si en este caso, permitir que otras aplicaciones modifiquen directamente tus datos puede generar datos no válidos.

Si quieres que los desarrolladores usen acceso mediante intents, asegúrate de documentarlo exhaustivamente. Explicar por qué el acceso mediante intents con la IU de tu aplicación es mejor que intentar acceder modificar los datos con su código.

Administrar un intent entrante que quiere modificar los datos de tu proveedor no es diferente de manejar otros intents. Para obtener más información sobre el uso de intents, lee Intents y filtros de intents.

Para obtener más información relacionada, consulta el Descripción general del proveedor de calendario