Skip to content

Most visited

Recently visited

navigation

Conceptos básicos sobre el proveedor de contenido

Un proveedor de contenido administra el acceso a un repositorio central de datos. Un proveedor forma parte de una aplicación de Android y a menudo proporciona su propia IU para trabajar con los datos. No obstante, los proveedores de contenido están principalmente orientados a que los usen otras aplicaciones que acceden al proveedor usando un objeto de cliente del proveedor. Juntos, proveedores y clientes de proveedores ofrecen una interfaz estándar y uniforme para los datos que también manipula la comunicación dentro del proceso y el acceso seguro a datos.

Este tema describe los aspectos básicos de lo siguiente:

Información general

Un proveedor de datos presenta datos a aplicaciones externas en forma de una o más tablas que son similares a las tablas de una base de datos relacional. Una fila representa una instancia de algún tipo de datos que recopila el proveedor, y cada columna de la fila representa una pieza individual de los datos recopilados para una instancia.

Por ejemplo, uno de los proveedores integrados a la plataforma Android es el diccionario del usuario, que guarda la ortografía de palabras no estándar que el usuario quiere conservar. La tabla 1 ilustra cómo se verían los datos en la tabla de este proveedor:

Tabla 1: Ejemplo de tabla de diccionario del usuario.

palabra id. de la aplicación frecuencia configuración regional _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

En la tabla 1, cada fila representa una instancia de una palabra que podría no encontrarse en un diccionario estándar. Cada columna representa algunos datos para esa palabra, como la configuración regional en la que se encontró por primera vez. Los encabezados de las columnas son nombres que se guardan en el proveedor. Para consultar la configuración regional de una fila, debes consultar su columna locale. Para este proveedor, la columna _ID cumple la función de columna de "clave primaria" que el proveedor mantiene de forma automática.

Nota: No es obligatorio que un proveedor tenga una clave primaria ni que use _ID como el nombre de columna de una columna primaria, si hubiera una. No obstante, si quieres enlazar datos de un proveedor con una ListView, uno de los nombres de las columnas debe ser _ID. Este requisito se explica en más detalle en la sección Visualización de resultados de una consulta.

Acceso a un proveedor

Una aplicación accede a datos de un proveedor de contenido con un objeto de cliente ContentResolver. Este objeto tiene métodos que llaman a métodos con el mismo nombre en el objeto del proveedor, una instancia de una de las subclases concretas de ContentProvider. Los métodos ContentResolver proporcionan las funciones básicas "CRUD" (proveniente de los términos en inglés que equivalen a crear, recuperar, actualizar y eliminar) del almacenamiento persistente.

El objeto ContentResolver en el proceso de la aplicación cliente y el objetoContentProvider en la aplicación a la que pertenece el proveedor manipulan automáticamente la comunicación dentro del proceso. ContentProvider también actúa como una capa de abstracción entre su repositorio de datos y el aspecto externo de los datos como tablas.

Nota: Para acceder a un proveedor, tu aplicación generalmente debe solicitar permisos específicos en su archivo de manifiesto. Esto se describe en más detalle en la sección Permisos del proveedor de contenido.

Por ejemplo, para obtener una lista de palabras y sus configuraciones regionales del proveedor de diccionario del usuario, debes llamar a ContentResolver.query(). El método query() llama al método ContentProvider.query() definido por el proveedor de diccionario del usuario. Las siguientes líneas de código muestran una llamada ContentResolver.query():

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

En la tabla 2 se muestra la forma en que los argumentos para query(Uri,projection,selection,selectionArgs,sortOrder) coinciden con una instrucción SQL SELECT:

Tabla 2: comparación de una consulta “query()” y una consulta de SQL.

Argumento de query() Palabra clave/parámetro SELECT Notas
Uri FROM table_name Uri se asigna a la tabla del proveedor llamada table_name.
projection col,col,col,... projection es una matriz de columnas que debe incluirse para cada fila recuperada.
selection WHERE col = value selection especifica los criterios para seleccionar filas.
selectionArgs (Sin equivalente exacto; los argumentos de selección reemplazan a los marcadores de posición ? en la cláusula de selección).
sortOrder ORDER BY col,col,... sortOrder especifica el orden en que aparecen las filas en el Cursor mostrado.

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 un nombre que apunta a una tabla (una ruta de acceso). Cuando llamas al método del cliente para acceder a una tabla del proveedor, el URI de contenido para la tabla es uno de los argumentos.

En las líneas de código anteriores, la constante CONTENT_URI contiene el URI de contenido de la tabla "palabras" del diccionario del usuario. El objeto ContentResolver analiza la autoridad del URI y la usa para "resolver" el proveedor al comparar la autoridad con una tabla del sistema de proveedores conocidos. El ContentResolver luego puede enviar los argumentos de la consulta al proveedor correcto.

El ContentProvider usa la parte de ruta de acceso del URI de contenido para seleccionar la tabla a la que quiere acceder. Un proveedor generalmente tiene una ruta de acceso para cada tabla que expone.

En las líneas de código anteriores, el URI completo para la tabla "palabras" es:

content://user_dictionary/words

donde la string user_dictionary es la autoridad del proveedor y la string words es la ruta de acceso de la tabla. La string content:// (el esquema) siempre está presente e identifica esto como un URI de contenido.

Muchos proveedores te permiten acceder a una sola fila de una tabla al anexar un valor de ID al final del URI. Por ejemplo, para recuperar una fila cuyo _ID es 4 del diccionario del usuario, puedes usar este URI de contenido:

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

Con frecuencia usas valores de ID cuando recuperas un conjunto de filas y luego quieres actualizar o eliminar una de ellas.

Nota: Las clases Uri y Uri.Builder contienen métodos prácticos para construir objetos de URI bien formados a partir de strings. La clase ContentUris contiene métodos convenientes para anexar valores de ID a un URI. El fragmento de código anterior usa withAppendedId() para anexar un id. al URI de contenido UserDictionary.

Recuperación de datos del proveedor

Esta sección describe cómo recuperar datos de un proveedor usando el proveedor Diccionario del usuario como ejemplo.

Para ser claros, los fragmentos de código de esta sección llaman a ContentResolver.query() en el "subproceso de la IU". Sin embargo, para los códigos reales debes realizar consultas de forma asincrónica en un subproceso independiente. Una manera de hacerlo es usar la clase CursorLoader, que se describe en más detalle en la guía Cargadores. Asimismo, las líneas de código son solo fragmentos de código; no muestran una aplicación completa.

Para recuperar datos de un proveedor, sigue estos pasos básicos:

  1. Solicita permiso de acceso de lectura para el proveedor.
  2. Define el código que envía una consulta al proveedor.

Solicitud de permiso de acceso de lectura

Para recuperar datos de un proveedor, tu aplicación necesita "permiso de acceso de lectura" para el proveedor. No puedes solicitar este permiso en tiempo de ejecución, sino que debes especificar que necesitas este permiso en tu manifiesto usando el elemento <uses-permission> y el nombre exacto del permiso definido por el proveedor. Cuando especificas este elemento en tu manifiesto, estás "solicitando" este permiso para tu aplicación. Cuando los usuarios instalan tu aplicación, implícitamente confieren esta solicitud.

Para buscar el nombre exacto del permiso de acceso de lectura para el proveedor que estás usando, como también los nombres para otros permisos de acceso que usa el proveedor, lee la documentación del proveedor.

El rol de los permisos al acceder a los proveedores se describe en más detalle en la sección Permisos del proveedor de contenido.

El proveedor de diccionario del usuario define el permiso android.permission.READ_USER_DICTIONARY en su archivo de manifiesto, de modo que una aplicación que quiera leer desde el proveedor debe solicitar este permiso.

Construcción de la consulta

El siguiente paso en la recuperación de datos de un proveedor es construir una consulta. Este primer fragmento de código define algunas variables para acceder al proveedor de diccionario del usuario:


// A "projection" defines the columns that will be returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String mSelectionClause = null;

// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};

El siguiente fragmento de código muestra cómo usar ContentResolver.query(), mediante el proveedor de diccionario del usuario como ejemplo. Una consulta de un cliente del proveedor es similar a una de SQL y contiene un conjunto de columnas que se mostrarán, un conjunto de criterios de selección y un orden de clasificación.

El conjunto de columnas que la consulta debe devolver se denomina proyección (la variable mProjection).

La expresión que especifica las filas para recuperar se divide en una cláusula de selección y argumentos de selección. La cláusula de selección es una combinación de expresiones lógicas y booleanas, nombres de columnas y valores (la variable mSelectionClause). Si especificas el parámetro reemplazable ? en lugar de un valor, el método de consulta obtiene el valor de la matriz de argumentos de selección (la variable mSelectionArgs).

En el siguiente fragmento de código, si el usuario no ingresa una palabra, la cláusula de selección se establece en null y la consulta devuelve todas las palabras del proveedor. Si el usuario ingresa una palabra, la cláusula de selección se establece en UserDictionary.Words.WORD + " = ?" y el primer elemento de la matriz de argumentos de selección se establece en la palabra que ingresa el usuario.

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
     * an error. You may want to offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

Esta consulta es similar a la instrucción SQL:

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

En esta instrucción SQL, se usan los nombres de columnas reales en lugar de las constantes de clase Contract.

Protección contra entradas malintencionadas

Si los datos que administra el proveedor de contenido se encuentran en una base de datos SQL, incluso los datos externos que no son de confianza que se encuentran en instrucciones SQL sin procesar, pueden generar la inyección de código SQL.

Considera esta cláusula de selección:

// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause =  "var = " + mUserInput;

Si haces esto, le permites al usuario concatenar código SQL malintencionado en tu instrucción SQL. Por ejemplo, el usuario podría ingresar "nothing; DROP TABLE *;" para mUserInput, que resultaría en la cláusula de selección var = nothing; DROP TABLE *;. Como la cláusula de selección se aborda como una instrucción SQL, esto podría provocar que el proveedor borre todas las tablas de la base de datos SQLite subyacente (a menos que el proveedor esté configurado para detectar intentos de inyección de código SQL).

Para evitar este problema, usa una cláusula de selección que use ? como parámetro reemplazable y una matriz de argumentos de selección independiente. Cuando haces esto, los datos ingresados por el usuario se vinculan directamente con la consulta en lugar de interpretarse como parte de una instrucción SQL. Como no se abordan como código SQL, los datos ingresados por el usuario no pueden inyectar código SQL malintencionado. En lugar de usar concatenación para incluir los datos ingresados por el usuario, usa esta cláusula de selección:

// Constructs a selection clause with a replaceable parameter
String mSelectionClause =  "var = ?";

Configura la matriz de argumentos de selección de esta manera:

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

Coloca un valor en la matriz de argumentos de selección de esta manera:

// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;

Una cláusula de selección que use ? como parámetro reemplazable y una matriz de argumentos de selección son la forma preferida para especificar una selección, incluso cuando el proveedor no se base en una base de datos SQL.

Visualización de resultados de una consulta

El método del cliente ContentResolver.query() siempre devuelve un Cursor que contiene las columnas especificadas por la proyección de la consulta para las filas que coinciden con los criterios de selección de la consulta. Un objeto Cursor proporciona acceso de lectura aleatorio a las filas y columnas que contiene. Al usar métodos Cursor, puedes iterar en las filas en los resultados, determinar el tipo de datos de cada columna, obtener datos de una columna y examinar otras propiedades de los resultados. Algunas implementaciones Cursor actualizan automáticamente el objeto cuando cambian los datos del proveedor, o activan métodos en un objeto observador cuando cambia Cursor, o ambos.

Nota: Un proveedor puede restringir el acceso a columnas en función del origen del objeto que realiza la consulta. Por ejemplo, el proveedor de contactos restringe el acceso de los adaptadores de sincronización a algunas columnas, de modo que no las devolverá a una actividad o servicio.

Si no hay filas que coincidan con los criterios de selección, el proveedor devuelve un objeto Cursor cuyo Cursor.getCount() es 0 (un cursor vacío).

Si ocurre un error interno, los resultados de la consulta dependen del proveedor específico. Puede decidir devolver null, o bien puede producir una Exception.

Como Cursor es una "lista" de filas, una buena manera de mostrar el contenido de un Cursor es vincularlo a una ListView a través de un SimpleCursorAdapter.

El siguiente fragmento de código continúa el código del fragmento anterior. Crea un objeto SimpleCursorAdapter que contiene el cursor Cursor recuperado por la consulta, y establece este objeto como el adaptador para una ListView:

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

Nota: Para respaldar una ListView con un Cursor, el cursor debe contener una columna llamada _ID. Por este motivo, la consulta que te mostramos antes recupera la columna _ID para la tabla "palabras" aunque ListView no la muestre. Esta restricción también explica por qué la mayoría de los proveedores tienen una columna _ID para cada una de sus tablas.

Obtención de datos de los resultados de una consulta

En lugar de simplemente mostrar resultados de una consulta, puedes usarlos para otras tareas. Por ejemplo, puedes recuperar palabras del diccionario del usuario y luego buscarlas en otros proveedores. Para hacerlo, debes iterar en las filas del Cursor:


// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}

Las implementaciones Cursor contienen varios métodos de "obtención" para recuperar diferentes tipos de datos del objeto. Por ejemplo, el fragmento de código anterior usa getString(). También tienen un método getType() que devuelve un valor que indica el tipo de datos de la columna.

Permisos del proveedor de contenido

La aplicación de un proveedor puede especificar permisos que otras aplicaciones deben tener para poder acceder a los datos del proveedor. Estos permisos garantizan que el usuario sepa a qué datos intentará acceder una aplicación. En función de los requisitos del proveedor, otras aplicaciones solicitan los permisos que necesitan para acceder al proveedor. Los usuarios finales ven los permisos solicitados cuando instalan la aplicación.

Si la aplicación de un proveedor no especifica ningún permiso, entonces otras aplicaciones no podrán acceder a los datos del proveedor. No obstante, los componentes de la aplicación del proveedor siempre tienen acceso pleno de lectura y escritura, independientemente de los permisos especificados.

Como se indicó anteriormente, el proveedor de diccionario del usuario requiere el permiso android.permission.READ_USER_DICTIONARY para recuperar datos de él. El proveedor tiene el permiso android.permission.WRITE_USER_DICTIONARY independiente para insertar, actualizar o eliminar datos.

Para obtener los permisos necesarios para acceder a un proveedor, una aplicación los solicita con un elemento <uses-permission> en su archivo de manifiesto. Cuando el Administrador de paquetes de Android instala la aplicación, el usuario debe aprobar todos los permisos que la aplicación solicita. Si el usuario los aprueba a todos, el administrador de paquetes continúa con la instalación; si no los aprueba, el administrador de paquetes aborta la instalación.

El siguiente elemento <uses-permission> solicita acceso de lectura al proveedor de diccionario del usuario:

    <uses-permission android:name="android.permission.READ_USER_DICTIONARY">

El impacto de los permisos en el acceso al proveedor se explica en más detalle en la guía Seguridad y permisos.

Inserción, actualización y eliminación de datos

De la misma manera que recuperas datos de un proveedor, también puedes usar la interacción entre un cliente del proveedor y el ContentProvider del proveedor para modificar datos. Debes llamar a un método de ContentResolver con argumentos que se pasan al método correspondiente de ContentProvider. El proveedor y el cliente del proveedor manipulan automáticamente la seguridad y la comunicación dentro del proceso.

Inserción de datos

Para insertar datos en un proveedor, debes llamar al método ContentResolver.insert() . Este método inserta una nueva fila en el proveedor y devuelve un URI de contenido para esa fila. Este fragmento de código muestra cómo insertar una nueva palabra en el proveedor de diccionario del usuario:

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);

Los datos para la nueva fila están incluidos en un solo objeto ContentValues, similar en forma a un cursor de una fila. No es necesario que las columnas de este objeto tengan el mismo tipo de datos, y si no quieres especificar un valor, puedes establecer una columna en null usando ContentValues.putNull().

El fragmento de código no agrega la columna _ID ya que esta columna se mantiene automáticamente. El proveedor asigna un valor único de _ID a cada fila que se agrega. Los proveedores generalmente usan este valor como la clave primaria de la tabla.

El URI de contenido devuelto en newUri identifica la fila recientemente agregada con el siguiente formato:

content://user_dictionary/words/<id_value>

<id_value> es el contenido de _ID para la nueva fila. La mayoría de los proveedores detectan esta forma de URI de contenido automáticamente y luego realizan la operación solicitada en esa fila específica.

Para obtener el valor de _ID del Uri devuelto, llama a ContentUris.parseId().

Actualización de datos

Para actualizar una fila, debes usar un objeto ContentValues con los valores actualizados tal como lo haces con una inserción, y con criterios de selección como lo haces con una consulta. El método de cliente que usas es ContentResolver.update(). Solo debes agregar valores al objeto ContentValues para las columnas que actualices. Si deseas borrar el contenido de una columna, fija el valor en null.

El siguiente fragmento de código cambia todas las filas cuya configuración regional tenga el idioma "en" por la configuración regional null. El valor devuelto es el número de filas que se actualizaron:

// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Cuando llamas a ContentResolver.update(), también debes depurar las entradas del usuario. Para obtener más información, lee la sección Protección contra entradas malintencionadas.

Eliminación de datos

Eliminar filas es similar a recuperar datos de una fila: debes especificar criterios de selección para las filas que quieras eliminar y el método de cliente devuelve el número de filas eliminadas. El siguiente fragmento de código elimina filas cuyas id. de aplicación coincidan con "usuario". El método devuelve el número de filas eliminadas.


// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Cuando llamas a ContentResolver.delete(), también debes depurar las entradas del usuario. Para obtener más información, lee la sección Protección contra entradas malintencionadas.

Tipos de datos del proveedor

Los proveedores de contenido pueden ofrecer muchos tipos de datos diferentes. El proveedor de diccionario del usuario solo ofrece texto, pero los proveedores también pueden ofrecer los siguientes formatos:

Otro tipo de datos que los proveedores usan con frecuencia es el Objeto binario grande (Binary Large Object, BLOB) implementado como una matriz de 64 KB. Puedes ver los tipos de datos disponibles al observar los métodos de "obtención" de la clase Cursor.

El tipo de datos para cada columna de un proveedor generalmente se indica en su documentación. Los tipos de datos para el proveedor de diccionario del usuario se indican en la documentación de referencia para su clase Contract UserDictionary.Words (las clases Contract se describen en la sección Clases Contract). También puedes determinar el tipo de datos llamando a Cursor.getType().

Los proveedores también mantienen información del tipo de datos MIME para cada URI de contenido que definen. Puedes usar la información del tipo MIME para averiguar si tu aplicación puede manipular datos que ofrece el proveedor o seleccionar un tipo de manipulación en función del tipo MIME. Generalmente necesitas el tipo MIME cuando trabajas con un proveedor que contiene estructuras de datos o archivos complejos. Por ejemplo, la tabla ContactsContract.Data del proveedor de contactos usa tipos MIME para etiquetar el tipo de datos de contacto guardado en cada fila. Para obtener el tipo MIME correspondiente a un URI de contenido, llama a ContentResolver.getType().

La sección Referencia a tipos de MIME describe la sintaxis de los tipos MIME estándar y personalizado.

Formas alternativas de acceso al proveedor

En el desarrollo de la aplicación, hay tres formas alternativas de acceso al proveedor importantes:

El acceso por lotes y las modificaciones mediante intents se describen en las siguientes secciones.

Acceso por lotes

El acceso por lotes a un proveedor es útil para insertar una gran cantidad de filas o para insertar filas en diferentes tablas en la misma llamada al método, o bien, en general, para realizar un conjunto de operaciones entre límites del proceso como una transacción (una operación atómica).

Para acceder a un proveedor en "modo de lote", puedes crear una matriz de objetos ContentProviderOperation y luego enviarlos a un proveedor de contenido con ContentResolver.applyBatch(). Debes pasar la autoridad del proveedor de contenido a este método en lugar de a un URI de contenido específico. Esto permite que cada objeto ContentProviderOperation de la matriz opere en función de una tabla diferente. Una llamada a ContentResolver.applyBatch() devuelve una matriz de resultados.

La descripción de la clase Contract ContactsContract.RawContacts incluye un fragmento de código que demuestra la inserción por lotes. La aplicación de muestra Administrador de contactos contiene un ejemplo de acceso por lotes en su archivo de origen ContactAdder.java .

Acceso a datos mediante intents

Las intents pueden proporcionar acceso indirecto a un proveedor de contenido. Puedes permitirle al usuario que acceda a datos en el proveedor aunque tu aplicación no tenga permisos de acceso, ya sea mediante la obtención de una intent de resultado desde una aplicación que tenga permisos o mediante la activación de una aplicación que tenga permisos y la habilitación para que el usuario pueda realizar tareas en ella.

Obtención de acceso con permisos temporales

Puedes acceder a datos en un proveedor de contenido aunque no tengas los permisos de acceso adecuados al enviar una intent a una aplicación que tenga permisos y recibir una intent de resultado que contenga permisos de "URI". Se trata de permisos para un URI de contenido específico que duran hasta que finaliza la actividad que los recibe. La aplicación que tiene permisos permanentes concede permisos temporales estableciendo un indicador en la intent de resultado:

Nota: Estos indicadores no proporcionan acceso general de escritura o lectura al proveedor cuya autoridad está incluida en el URI de contenido. El acceso es solo para el URI.

Un proveedor define permisos de URI para URI de contenido en su manifiesto usando el atributo android:grantUriPermission del elemento <provider> y también el elemento secundario <grant-uri-permission> del elemento <provider>. El mecanismo de los permisos de URI se explica en más detalle en la guía Seguridad y permisos, en la sección "Permisos de URI".

Por ejemplo, puedes recuperar datos para un contacto en el proveedor de contactos aunque no tengas el permiso READ_CONTACTS. Te recomendamos que hagas esto en una aplicación que envíe saludos electrónicos a contactos en sus cumpleaños. En lugar de solicitar READ_CONTACTS, que te permite acceder a todos los contactos del usuario y a toda su información, prefieres permitirle al usuario controlar los contactos que usa tu aplicación. Para hacerlo, usa el siguiente proceso:

  1. Tu aplicación envía una intent que contiene la acción ACTION_PICK y el tipo MIME de "contactos" CONTENT_ITEM_TYPEmediante el método startActivityForResult().
  2. Como esta intent coincide con el filtro de intent para la actividad "selección" de la aplicación Personas, la actividad pasará a primer plano.
  3. En la actividad de selección, el usuario selecciona un contacto para actualizar. Cuando esto ocurre, la actividad de selección llama a setResult(resultcode, intent) para configurar una intent que regrese a tu aplicación. La intent tiene el URI de contenido del contacto que seleccionó el usuario y los indicadores "extra" FLAG_GRANT_READ_URI_PERMISSION. Esos indicadores otorgan permiso de URI para que tu aplicación pueda leer datos para el contacto al que hace referencia el URI de contenido. La actividad de selección luego llama a finish() para devolverle el control a tu aplicación.
  4. Tu actividad vuelve a segundo plano y el sistema llama al método onActivityResult() de tu actividad. Este método recibe la intent de resultado que creó la actividad de selección en la aplicación Personas.
  5. Con el URI de contenido de la intent de resultado, puedes leer los datos del contacto desde el proveedor de contactos aunque no hayas solicitado un permiso de acceso de lectura permanente al proveedor en tu manifiesto. Luego, puedes obtener la información de cumpleaños del contacto o su dirección de correo electrónico y enviarle un saludo electrónico.

Uso de otra aplicación

Una forma sencilla de permitir que el usuario modifique datos para los que no tienes permisos de acceso es activar una aplicación que tenga permisos y permitirle al usuario realizar operaciones allí.

Por ejemplo, la aplicación Calendario acepta una intent ACTION_INSERT, que te permite activar la IU de inserción de la aplicación. Puedes pasar datos "extra" en esa intent, que la aplicación usa para precompletar la IU. Como los eventos periódicos tienen una sintaxis compleja, el método preferido para insertar eventos en un proveedor de calendario es activar la aplicación Calendario con una ACTION_INSERT y luego permitirle al usuario que inserte el evento allí.

Clases Contract

Una clase Contract define constantes que ayudan a las aplicaciones a trabajar con URI de contenido, nombres de columnas, acciones de intents y otras funciones de un proveedor de contenido. Las clases de contrato no están incluidas automáticamente en un proveedor; el desarrollador del proveedor debe definirlas y luego permitir que estén disponibles para otros desarrolladores. Muchos de los proveedores incluidos en la plataforma Android tienen clases Contract correspondientes en el paquete android.provider.

Por ejemplo, el proveedor de diccionario del usuario tiene una clase Contract UserDictionary que posee constantes de URI de contenido y nombre de columna. El URI de contenido para la tabla "palabras" se define en la constante UserDictionary.Words.CONTENT_URI. La clase UserDictionary.Words también tiene constantes de nombre de columnas, que se usan en los fragmentos de código de ejemplo de esta guía. Por ejemplo, una proyección de consulta se puede definir de la siguiente manera:

String[] mProjection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

Otra clase Contract es ContactsContract para el proveedor de contactos. La documentación de referencia para esta clase incluye fragmentos de código de ejemplo. Una de sus subclases, ContactsContract.Intents.Insert, es una1 clase Contract que contiene constantes para intents y datos de intents.

Referencia a tipos de MIME

Los proveedores de contenido pueden devolver tipos de medios MIME estándar o strings de tipos de MIME personalizadas, o ambos.

Los tipos de MIME tienen el formato

type/subtype

Por ejemplo, el tipo de MIME conocido text/html tiene el tipo text y el subtipo html. Si el proveedor devuelve este tipo para un URI, significa que una consulta que use ese URI devolverá texto que contenga etiquetas HTML.

Las strings de tipo de MIME personalizadas, también llamadas tipos de MIME “específicos del proveedor”, tienen valores de tipo y subtipo más complejos. El valor de tipo es siempre

vnd.android.cursor.dir

para múltiples filas, o

vnd.android.cursor.item

para una sola fila.

El subtipo es específico del proveedor. Los proveedores integrados a Android generalmente tienen un subtipo simple. Por ejemplo, cuando la app de Contactos crea una fila para un número de teléfono, establece el siguiente tipo de MIME en la fila:

vnd.android.cursor.item/phone_v2

Ten en cuenta que el valor del subtipo es simplemente phone_v2.

Otros desarrolladores de proveedores pueden crear sus propios patrones de subtipos en función de la autoridad y los nombres de tablas del proveedor. Por ejemplo, piensa en un proveedor que contiene horarios de trenes. La autoridad del proveedor es com.example.trains y contiene las tablas Línea1, Línea2 y Línea3. En respuesta al URI de contenido

content://com.example.trains/Line1

para la tabla Línea1, el proveedor devuelve el tipo de MIME

vnd.android.cursor.dir/vnd.example.line1

En respuesta al URI de contenido

content://com.example.trains/Line2/5

para la fila 5 de la tabla Línea2, el proveedor devuelve el tipo de MIME

vnd.android.cursor.item/vnd.example.line2

La mayoría de los proveedores de contenido definen constantes de clase Contract para los tipos de MIME que usan. Por ejemplo, la clase Contract ContactsContract.RawContacts del proveedor de contactos define la constante CONTENT_ITEM_TYPE para el tipo de MIME de una sola fila de contacto sin procesar.

Los URI de contenido para filas individuales se describen en la sección URI de contenido.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)