Un proveedor de contenido administra el acceso a un repositorio central de datos. Un proveedor forma parte de una aplicación para Android y a menudo proporciona su propia IU para trabajar con los datos. Sin embargo, otros proveedores de contenido son utilizados principalmente por otros que acceden al proveedor usando un objeto de cliente del proveedor. En conjunto, los proveedores y proveedores ofrecen una interfaz estándar y coherente para los datos que también maneja la comunicación entre procesos y el acceso seguro a los datos.
Por lo general, se trabaja con proveedores de contenido en una de estas dos situaciones: la implementación de código para acceder a un proveedor de contenido existente en otra aplicación o crear un nuevo proveedor de contenido en tu aplicación para compartir datos con otras aplicaciones.
Esta página se abordan los aspectos básicos para trabajar con proveedores de contenido existentes. Para aprender a implementar proveedores de contenido en tus propias aplicaciones, consulta Crea un proveedor de contenido.
En este tema, se describe lo siguiente:
- Cómo funcionan los proveedores de contenido.
- La API que usas para recuperar datos de un proveedor de contenido.
- La API que usas para insertar, actualizar o borrar datos en un proveedor de contenido.
- Otras funciones de API que facilitan el trabajo con proveedores.
Descripción general
Un proveedor de contenido 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 un ítem individual de los datos recopilados para una instancia.
Un proveedor de contenido coordina el acceso a la capa de almacenamiento de datos en tu aplicación para una la cantidad de APIs y componentes diferentes. Como se ilustra en la figura 1, se incluye lo siguiente:
- Compartir con otras aplicaciones el acceso a los datos de tu aplicación
- Enviar datos a un widget
- Mostrar sugerencias personalizadas de búsqueda para tu aplicación mediante el marco de trabajo de búsqueda usando
SearchRecentSuggestionsProvider
- Sincronizar los datos de la aplicación con tu servidor mediante una implementación de
AbstractThreadedSyncAdapter
- Cargar datos en tu IU usando
CursorLoader
![Relación entre el proveedor de contenido y otros componentes](https://developer.android.google.cn/static/guide/topics/providers/images/content-provider-tech-stack.png?authuser=0000&hl=es)
Figura 1: Relación entre un proveedor de contenido y otros componentes.
Accede a un proveedor
Cuando quieres acceder a datos en un proveedor de contenido, usas el objeto ContentResolver
en el Context
de tu aplicación para comunicarte con el proveedor como cliente. El objeto ContentResolver
se comunica con el objeto del proveedor, una instancia de una clase que implementa ContentProvider
.
El objeto del proveedor recibe solicitudes de datos de clientes, realiza la acción solicitada y muestra resultados. Este objeto tiene métodos que llaman a métodos con nombres idénticos en el objeto del proveedor.
una instancia de una de las subclases concretas de ContentProvider
. El
Los métodos ContentResolver
proporcionan la
“CRUD” (crear, recuperar, actualizar y borrar) funciones del almacenamiento persistente.
Un patrón común para acceder a un ContentProvider
desde tu IU usa un CursorLoader
para ejecutar una consulta asíncrona en segundo plano. El
Activity
o Fragment
en tu IU llaman a un
CursorLoader
a la consulta, lo que, a su vez, obtiene
ContentProvider
con ContentResolver
.
Esto permite que la IU continúe disponible para el usuario mientras se ejecuta la consulta. Este patrón contempla la interacción de una serie de objetos diferentes y del mecanismo de almacenamiento subyacente, como se detalla en la figura 2.
![Interacción entre ContentProvider, otras clases y el almacenamiento](https://developer.android.google.cn/static/guide/topics/providers/images/content-provider-interaction.png?authuser=0000&hl=es)
Figura 2: Interacción entre ContentProvider
, otras clases y el almacenamiento
Nota: Por lo general, para acceder a un proveedor, tu aplicación debe solicitar permisos específicos en su archivo de manifiesto. Este patrón de desarrollo se describe con más detalle en el Sección Permisos del proveedor de contenido
Uno de los proveedores integrados en la plataforma de Android es el proveedor de diccionario del usuario, que almacena las palabras no estándar que el usuario quiere conservar. En la tabla 1 se detalla 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 app | frecuencia | configuración regional | _ID |
---|---|---|---|---|
mapreduce |
user1 | 100 | es-419 | 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 no
que se encuentra en un diccionario estándar. Cada columna representa un dato para esa palabra, como el
en la que se encontró por primera vez. Los encabezados de las columnas son nombres que se guardan en el proveedor. Por lo tanto, para hacer referencia a la configuración regional de una fila, por ejemplo, debes consultar su columna locale
. Para
este proveedor, la columna _ID
funciona como una columna de clave primaria que
que el proveedor mantiene automáticamente.
Si quieres 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()
:
Kotlin
// Queries the UserDictionary and returns results cursor = contentResolver.query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Selection criteria selectionArgs.toTypedArray(), // Selection criteria sortOrder // The sort order for the returned rows )
Java
// Queries the UserDictionary and returns results cursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Selection criteria selectionArgs, // Selection criteria sortOrder); // The sort order for the returned rows
En la tabla 2, se muestra cómo los argumentos para
query(Uri,projection,selection,selectionArgs,sortOrder)
coinciden con una sentencia SELECT de SQL:
Tabla 2: query()
en comparación con la consulta en 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 un array de columnas que se incluye para cada fila.
recuperado.
|
selection |
WHERE col = value |
selection especifica los criterios para seleccionar filas. |
selectionArgs |
No hay un equivalente exacto. Los argumentos de selección reemplazan a los marcadores de posición ? en el
de selección.
|
|
sortOrder |
ORDER BY col,col,... |
sortOrder especifica el orden en que aparece la fila en el Cursor que se muestra. |
URI de contenido
Un URI de contenido es un URI que identifica datos de un proveedor. URI de contenido incluyan el nombre simbólico de todo el proveedor (su autoridad) y una nombre que apunta a una tabla: una ruta. Cuando llamas al método del cliente para acceder a una tabla del proveedor, el URI de contenido de la tabla es uno de los argumentos.
En las líneas de código anteriores, la constante
CONTENT_URI
incluye el URI de contenido de
la tabla Words
del proveedor de diccionario del usuario El ContentResolver
analiza la autoridad del URI y la usa para resolver el proveedor
comparando la autoridad con una tabla de sistema de proveedores conocidos El
Luego, ContentResolver
puede enviar los argumentos de la consulta al
proveedor.
El ContentProvider
usa la parte de la 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 de la tabla Words
es el siguiente:
content://user_dictionary/words
- La cadena
content://
es el esquema, que siempre está presente y la identifica como un URI de contenido. - La cadena
user_dictionary
es la autoridad del proveedor. - La cadena
words
es la ruta de acceso de la tabla.
Muchos proveedores te permiten acceder a una sola fila de una tabla agregando un valor de ID.
al final del URI. Por ejemplo, para recuperar una fila cuyo _ID
es
4
del proveedor de diccionario del usuario, puedes usar este URI de contenido:
Kotlin
val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)
Java
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
Sueles usar valores de ID cuando recuperas un conjunto de filas y, luego, quieres actualizar o borrar 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. El
La clase ContentUris
contiene métodos convenientes para agregar valores de ID a
un URI. El fragmento anterior usa withAppendedId()
para agregar un ID al URI de contenido del proveedor de diccionario del usuario.
Recupera datos del proveedor
En esta sección se describe cómo recuperar datos de un proveedor usando el proveedor de diccionario del usuario como ejemplo.
Para ser claros, los fragmentos de código de esta sección llaman
ContentResolver.query()
en el subproceso de IU En
código real hacen consultas de forma asíncrona en un subproceso independiente. Puedes
usa la clase CursorLoader
, que se describe
con más detalle en el
Cargadores. Además, las líneas de código son solo fragmentos. No muestran un estado
y mantener la integridad de su aplicación.
Para recuperar datos de un proveedor, sigue estos pasos básicos:
- Solicita permiso de acceso de lectura para el proveedor.
- Define el código que envía una consulta al proveedor.
Solicita 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 durante el tiempo de ejecución. En su lugar, debes especificar que
necesitas este permiso en tu manifiesto, con el
<uses-permission>
y el nombre exacto del permiso definido por el
proveedor.
Cuando especificas este elemento en tu manifiesto, solicitas este permiso para tu aplicación. Cuando los usuarios instalan tu aplicación, conceden de forma implícita el permiso solicitado.
Para buscar el nombre exacto del permiso de acceso de lectura del proveedor que estás usando, así como los nombres de otros permisos de acceso que usa el proveedor, consulta la documentación del proveedor.
El rol de los permisos en el acceso a los proveedores se describe con más detalle en el 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.
Crea la consulta
El siguiente paso en la recuperación de datos de un proveedor es construir una consulta. El siguiente fragmento define algunas variables para acceder al proveedor de diccionario del usuario:
Kotlin
// A "projection" defines the columns that are returned for each row private val mProjection: Array<String> = arrayOf( 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 private var selectionClause: String? = null // Declares an array to contain selection arguments private lateinit var selectionArgs: Array<String>
Java
// A "projection" defines the columns that are 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 selectionClause = null; // Initializes an array to contain selection arguments String[] selectionArgs = {""};
El siguiente fragmento de código muestra cómo usar ContentResolver.query()
con el proveedor de diccionario del usuario como ejemplo. Una consulta de un cliente del proveedor es similar a una consulta de SQL y contiene un
un conjunto de columnas para mostrar, un conjunto de criterios de selección y un orden de clasificación.
El conjunto de columnas que muestra la consulta se denomina proyección.
la variable es 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 es mSelectionClause
. Si especificas
el parámetro reemplazable ?
en lugar de un valor, el método de consulta recupera el valor
del array de argumentos de selección, que es la variable mSelectionArgs
.
En el siguiente fragmento, si el usuario no ingresa una palabra, la cláusula de selección se establece en
null
y la consulta muestra 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.
Kotlin
/* * This declares a String array to contain the selection arguments. */ private lateinit var selectionArgs: Array<String> // Gets a word from the UI searchString = searchWord.text.toString() // Insert code here to check for invalid or malicious input // If the word is the empty string, gets everything selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let { selectionClause = "${UserDictionary.Words.WORD} = ?" arrayOf(it) } ?: run { selectionClause = null emptyArray<String>() } // Does a query against the table and returns a Cursor object mCursor = contentResolver.query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Either null or the word the user entered selectionArgs, // Either empty or the string the user entered sortOrder // The sort order for the returned rows ) // Some providers return null if an error occurs, others throw an exception when (mCursor?.count) { null -> { /* * Insert code here to handle the error. Be sure not to use the cursor! * You might want to call android.util.Log.e() to log this error. */ } 0 -> { /* * Insert code here to notify the user that the search is unsuccessful. This isn't * necessarily an error. You might 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 } }
Java
/* * This defines a one-element String array to contain the selection argument. */ String[] selectionArgs = {""}; // Gets a word from the UI searchString = searchWord.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(searchString)) { // Setting the selection clause to null returns all words selectionClause = null; selectionArgs[0] = ""; } else { // Constructs a selection clause that matches the word that the user entered selectionClause = UserDictionary.Words.WORD + " = ?"; // Moves the user's input string to the selection arguments selectionArgs[0] = searchString; } // 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 projection, // The columns to return for each row selectionClause, // Either null or the word the user entered selectionArgs, // Either empty or the string the user entered sortOrder); // 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 can * 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 is unsuccessful. This isn't necessarily * an error. You can 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 siguiente instrucción de 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 constantes de clase de contratos.
Protección contra entradas malintencionadas
Si los datos que administra el proveedor de contenido están en una base de datos SQL, incluidas las funciones externas no confiables de datos en instrucciones de SQL sin procesar puede llevar a la inyección de SQL.
Considera la siguiente cláusula de selección:
Kotlin
// Constructs a selection clause by concatenating the user's input to the column name var selectionClause = "var = $mUserInput"
Java
// Constructs a selection clause by concatenating the user's input to the column name String selectionClause = "var = " + userInput;
Si haces esto, permites que el usuario concatene potencialmente SQL malicioso en tu instrucción de SQL.
Por ejemplo, el usuario puede ingresar "nothing; DROP TABLE *;" para mUserInput
, que
da como resultado la cláusula de selección var = nothing; DROP TABLE *;
.
Ya que el de selección se trata como una instrucción de SQL, esto podría provocar que el proveedor borre todos las tablas de la base de datos SQLite subyacente, a menos que el proveedor esté configurado para detectar Intentos de inyección de SQL
Para evitar este problema, usa una cláusula de selección que tenga ?
como parámetro reemplazable y una matriz de argumentos de selección independiente. De esta manera, la entrada del usuario
está vinculado directamente a la consulta en lugar de interpretarse como parte de una instrucción de SQL.
Dado que no se abordan como código SQL, los datos ingresados por el usuario no pueden inyectar SQL malintencionado. En lugar de recurrir a la concatenación para incluir los datos que ingresa el usuario, usa esta cláusula de selección:
Kotlin
// Constructs a selection clause with a replaceable parameter var selectionClause = "var = ?"
Java
// Constructs a selection clause with a replaceable parameter String selectionClause = "var = ?";
Configura la matriz de argumentos de selección de esta manera:
Kotlin
// Defines a mutable list to contain the selection arguments var selectionArgs: MutableList<String> = mutableListOf()
Java
// Defines an array to contain the selection arguments String[] selectionArgs = {""};
Coloca un valor en la matriz de argumentos de selección de esta manera:
Kotlin
// Adds the user's input to the selection argument selectionArgs += userInput
Java
// Sets the selection argument to the user's input selectionArgs[0] = userInput;
Cláusula de selección que usa ?
como parámetro reemplazable y un array de
La matriz de argumentos de selección es la forma preferida de especificar una selección, incluso si el proveedor no es
basadas en una base de datos SQL.
Muestra los resultados de la consulta
El método del cliente ContentResolver.query()
siempre muestra 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.
Con los métodos Cursor
, puedes iterar en las filas del
determinar el tipo de datos de cada columna, obtener los datos de una columna y examinar otros
propiedades de los resultados.
Algunas implementaciones de Cursor
se implementan automáticamente.
actualizar el objeto cuando cambien los datos del proveedor, activar métodos en un objeto observador
cuando cambia Cursor
, o ambos.
Nota: Un proveedor puede restringir el acceso a las columnas según la naturaleza del el objeto que realiza la consulta. Por ejemplo, el proveedor de contactos restringe el acceso a algunas columnas a de sincronización, para que no los muestre en una actividad o servicio.
Si no hay filas que coincidan con los criterios de selección, el proveedor
devuelve un objeto Cursor
para el que
Cursor.getCount()
es
0, es decir, un cursor vacío.
Si ocurre un error interno, los resultados de la consulta dependen del proveedor específico. Podría ser
Se puede mostrar null
o puede arrojar una Exception
.
Como Cursor
es una lista de filas, una buena manera de mostrar el
el contenido de un elemento Cursor
es vincularlo a un elemento ListView
usando un SimpleCursorAdapter
.
El siguiente fragmento continúa el código del fragmento anterior. Crea un
Un objeto SimpleCursorAdapter
que contiene Cursor
recuperado por la consulta y establece que este objeto sea el adaptador para un
ListView
Kotlin
// Defines a list of columns to retrieve from the Cursor and load into an output row val wordListColumns : Array<String> = arrayOf( 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 receive the Cursor columns for each row val wordListItems = intArrayOf(R.id.dictWord, R.id.locale) // Creates a new SimpleCursorAdapter cursorAdapter = SimpleCursorAdapter( applicationContext, // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0 // Flags (usually none are needed) ) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter)
Java
// Defines a list of columns to retrieve from the Cursor and load into an output row String[] wordListColumns = { 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 receive the Cursor columns for each row int[] wordListItems = { R.id.dictWord, R.id.locale}; // Creates a new SimpleCursorAdapter cursorAdapter = 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 wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0); // Flags (usually none are needed) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter);
Nota: Para respaldar una ListView
con un Cursor
, el cursor debe contener una columna llamada _ID
.
Por este motivo, la consulta que se mostró antes recupera la columna _ID
para la
Tabla Words
, aunque ListView
no la muestra.
Esta restricción también explica por qué la mayoría de los proveedores tienen una columna _ID
para cada uno de los
sus tablas.
Obtén datos de los resultados de una consulta
Además de mostrar los resultados de la consulta, puedes usarlos para otras tareas. Para
ejemplo, puedes recuperar palabras del proveedor de diccionario del usuario y, luego, buscarlas en
con otros proveedores. Para ello, debes iterar por las filas en el Cursor
, como se muestra en el siguiente ejemplo:
Kotlin
/* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers might throw an Exception instead of returning null. */ mCursor?.apply { // Determine the column index of the column named "word" val index: Int = getColumnIndex(UserDictionary.Words.WORD) /* * 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 get an * exception. */ while (moveToNext()) { // Gets the value from the column newWord = getString(index) // Insert code here to process the retrieved word ... // End of while loop } }
Java
// 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 might 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 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 de Cursor
contienen varios métodos GET para recuperar diferentes tipos de datos del objeto. Por ejemplo, el fragmento anterior usa getString()
. También tienen un método getType()
, el cual muestra un valor que indica el tipo de datos de la columna.
Liberar recursos de resultados de consultas
Los objetos Cursor
deben tener
se cierran si ya no se necesitan, de modo que los recursos asociados a ellos se liberan
antes. Esto se puede hacer llamando
close()
o mediante
una sentencia try-with-resources
en el lenguaje de programación Java o el
Función use()
en el lenguaje de programación Kotlin.
Permisos del proveedor de contenido
La aplicación de un proveedor puede especificar permisos que otras aplicaciones deben tener para acceder a los datos del proveedor. Estos permisos le permiten al usuario saber qué datos a la que intenta 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 tienen el acceso a los datos del proveedor, a menos que este se exporte. Además, los componentes en la aplicación del proveedor siempre tienen acceso pleno de lectura y escritura, independientemente del permisos especificados.
El proveedor de diccionario del usuario requiere
El permiso android.permission.READ_USER_DICTIONARY
para recuperar datos de allí
El proveedor tiene un android.permission.WRITE_USER_DICTIONARY
independiente.
permiso para insertar, actualizar o borrar datos.
A fin de 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,
El Administrador de paquetes continúa la instalación. Si el usuario no los aprueba, el Administrador de paquetes
detiene la instalación.
El siguiente ejemplo
<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 con más detalle en Sugerencias de seguridad.
Inserta, actualiza y borra datos
De la misma manera que recuperas datos de un proveedor, 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
. Proveedor y proveedor
cliente controla automáticamente la seguridad y la comunicación entre procesos.
Inserte 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.
El siguiente fragmento de código muestra cómo insertar una nueva palabra en el proveedor de diccionario del usuario:
Kotlin
// Defines a new Uri object that receives the result of the insertion lateinit var newUri: Uri ... // Defines an object to contain the new values to insert val newValues = ContentValues().apply { /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value". */ put(UserDictionary.Words.APP_ID, "example.user") put(UserDictionary.Words.LOCALE, "en_US") put(UserDictionary.Words.WORD, "insert") put(UserDictionary.Words.FREQUENCY, "100") } newUri = contentResolver.insert( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI newValues // The values to insert )
Java
// Defines a new Uri object that receives the result of the insertion Uri newUri; ... // Defines an object to contain the new values to insert ContentValues newValues = new ContentValues(); /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value". */ newValues.put(UserDictionary.Words.APP_ID, "example.user"); newValues.put(UserDictionary.Words.LOCALE, "en_US"); newValues.put(UserDictionary.Words.WORD, "insert"); newValues.put(UserDictionary.Words.FREQUENCY, "100"); newUri = getContentResolver().insert( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI newValues // The values to insert );
Los datos para la nueva fila están incluidos en un solo objeto ContentValues
, de forma similar a la de 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 anterior 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. Por lo general, los proveedores usan este valor como la clave primaria de la tabla.
El URI de contenido que se muestra en newUri
identifica la fila agregada recientemente 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
que se muestra, llama a ContentUris.parseId()
.
Cómo actualizar datos
Para actualizar una fila, usa un objeto ContentValues
con el valor
como se hace con una inserción, y con criterios de selección, como con una consulta.
El método de cliente que usas es ContentResolver.update()
. Solo debes agregar valores al objeto ContentValues
para las columnas que estés actualizando. Si quieres borrar el contenido de una columna, establece el valor en null
.
El siguiente fragmento cambia todas las filas cuya configuración regional tenga el idioma "en"
a un
tienen una configuración regional de null
. El valor que se muestra es la cantidad de filas que se actualizaron.
Kotlin
// Defines an object to contain the updated values val updateValues = ContentValues().apply { /* * Sets the updated value and updates the selected words. */ putNull(UserDictionary.Words.LOCALE) } // Defines selection criteria for the rows you want to update val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?" val selectionArgs: Array<String> = arrayOf("en_%") // Defines a variable to contain the number of updated rows var rowsUpdated: Int = 0 ... rowsUpdated = contentResolver.update( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI updateValues, // The columns to update selectionClause, // The column to select on selectionArgs // The value to compare to )
Java
// Defines an object to contain the updated values ContentValues updateValues = new ContentValues(); // Defines selection criteria for the rows you want to update String selectionClause = UserDictionary.Words.LOCALE + " LIKE ?"; String[] selectionArgs = {"en_%"}; // Defines a variable to contain the number of updated rows int rowsUpdated = 0; ... /* * Sets the updated value and updates the selected words. */ updateValues.putNull(UserDictionary.Words.LOCALE); rowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI updateValues, // The columns to update selectionClause, // The column to select on selectionArgs // The value to compare to );
Limpia las entradas del usuario durante las llamadas
ContentResolver.update()
Para obtener más información
consulta la sección Protégete contra entradas maliciosas.
Cómo borrar datos
Borrar filas es similar a recuperar datos de fila. Debes especificar los criterios de selección de las filas
deseas borrar, y el método del cliente muestra el número de filas borradas.
El siguiente fragmento borra las filas cuyo ID de app coincide con "user"
. El método muestra el número de filas borradas.
Kotlin
// Defines selection criteria for the rows you want to delete val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?" val selectionArgs: Array<String> = arrayOf("user") // Defines a variable to contain the number of rows deleted var rowsDeleted: Int = 0 ... // Deletes the words that match the selection criteria rowsDeleted = contentResolver.delete( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI selectionClause, // The column to select on selectionArgs // The value to compare to )
Java
// Defines selection criteria for the rows you want to delete String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] selectionArgs = {"user"}; // Defines a variable to contain the number of rows deleted int rowsDeleted = 0; ... // Deletes the words that match the selection criteria rowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI selectionClause, // The column to select on selectionArgs // The value to compare to );
Limpia las entradas del usuario durante las llamadas
ContentResolver.delete()
Para obtener más información
consulta la sección Protégete contra entradas maliciosas.
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 en general los proveedores también pueden ofrecer los siguientes formatos:
- número entero
- entero largo (largo)
- punto flotante
- punto flotante largo (doble)
Otro tipo de datos que los proveedores usan con frecuencia es un objeto binario grande (BLOB) implementado como
Matriz de 64 KB. Si quieres ver los tipos de datos disponibles, puedes ver los métodos GET de la clase Cursor
.
El tipo de datos para cada columna de un proveedor suele indicarse en su documentación.
Los tipos de datos del proveedor de diccionario del usuario se enumeran en la documentación de referencia.
para su clase de contratos, UserDictionary.Words
. Las clases de contrato son
que se describe en la sección Clases de contratos.
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 manejar datos que la que ofrece el proveedor o para elegir un tipo de manejo en función del tipo de MIME. Por lo general, necesitas Es un tipo de MIME cuando trabajas con un proveedor que contiene datos estructuras de datos o archivos.
Por ejemplo, la tabla ContactsContract.Data
del proveedor de contactos usa tipos de MIME para etiquetar el tipo de datos de contacto guardado en cada fila. Para obtener el tipo de MIME correspondiente a un URI de contenido, llama a ContentResolver.getType()
.
La sección de referencia de tipos de MIME describe las la sintaxis de MIME estándar y personalizada.
Formas alternativas de acceso al proveedor
En el desarrollo de la aplicación, hay tres formas alternativas de acceso al proveedor que son importantes.
-
Acceso por lotes: Puedes crear un lote de llamadas de acceso con métodos en
la clase
ContentProviderOperation
y, luego, aplicarlas conContentResolver.applyBatch()
-
Consultas asíncronas: Realizan consultas en un subproceso independiente. Puedes
usa un objeto
CursorLoader
. Los ejemplos incluidos en la guía Cargadores demuestran cómo hacerlo. - Acceso a datos con intents: aunque no puedes enviar un intent directamente a un proveedor, puedes enviar un intent a la aplicación del proveedor, que es están mejor preparadas para modificar los datos del proveedor.
El acceso por lotes y las modificaciones con intents se describen en las siguientes secciones.
Acceso por lotes
El acceso por lotes a un proveedor es útil si se quiere insertar una gran cantidad de filas filas en varias tablas en la misma llamada de método y, en general, para realizar un conjunto de operaciones entre los límites del proceso, como una transacción, denominada operación atómica.
Para acceder a un proveedor en modo por lotes,
crea un array de objetos ContentProviderOperation
y, luego,
enviarlas a un proveedor de contenido con
ContentResolver.applyBatch()
Debes pasar el
la autoridad del proveedor de contenido para este método, en lugar de un URI de contenido en particular.
Esto permite que funcione cada objeto ContentProviderOperation
del array.
en una tabla diferente. Una llamada a ContentResolver.applyBatch()
muestra un array de resultados.
La descripción de la clase de contratos ContactsContract.RawContacts
incluye un fragmento de código que demuestra la inserción por lotes.
Acceso a datos con intents
Las intents pueden proporcionar acceso indirecto a un proveedor de contenido. Puedes permitir que el usuario acceda datos en un proveedor incluso si tu aplicación no tiene permisos de acceso mediante obtener una intent de resultado desde una aplicación que tiene permisos o mediante la activación de una que tenga permisos y permita que el usuario trabaje en ella.
Obtén acceso con permisos temporales
Puedes acceder a los datos en un proveedor de contenido aunque no tengas el acceso adecuado permisos, mediante el envío de un intent a una aplicación que tiene los 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 marcador en la intent de resultado:
- Permiso de lectura:
FLAG_GRANT_READ_URI_PERMISSION
- Permiso de escritura:
FLAG_GRANT_WRITE_URI_PERMISSION
Nota: Estos indicadores no proporcionan acceso general de escritura ni de lectura al proveedor cuya autoridad está incluida en el URI de contenido. El acceso es solo para el URI.
Cuando envíes URI de contenido a otra app, incluye al menos uno de estos marcas. Las marcas proporcionan las siguientes capacidades a cualquier app que reciba un intent que tenga como objetivo Android 11 (nivel de API 30) o versiones posteriores:
- Leer o escribir en los datos que representa el URI de contenido según la marca incluida en el intent.
- Obtener paquete visibilidad en la aplicación que contiene el proveedor de contenido que coincide con Autoridad del URI. La app que envía el intent y la app que que contiene el proveedor de contenido podrían ser dos apps diferentes.
Un proveedor define los permisos de URI para URI de contenido en su manifiesto usando el
android:grantUriPermissions
atributo de la
<provider>
así como el
<grant-uri-permission>
elemento secundario de la
<provider>
. El mecanismo de permisos del URI se explica con más detalle en el
Permisos en Android.
Por ejemplo, puedes recuperar datos para un contacto en el proveedor de contactos aunque no tengas el permiso READ_CONTACTS
. Podrías querer hacer esto en una aplicación que envíe saludos electrónicos a un contacto en su cumpleaños. En lugar de
solicitando READ_CONTACTS
, lo que te da acceso a todos
los contactos del usuario y toda su información, le permiten controlar qué
los contactos que usa tu aplicación. Para hacerlo, usa el siguiente proceso:
-
En tu aplicación, envía un intent que contenga la acción.
ACTION_PICK
y los "contactos" Tipo de MIMECONTENT_ITEM_TYPE
, con el métodostartActivityForResult()
. - Como este intent coincide con el filtro de intents para la "Selección" de la aplicación de personas la actividad pasa a primer plano.
-
En la actividad de selección, el usuario elige 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 el usuario seleccionó y los "extras" banderasFLAG_GRANT_READ_URI_PERMISSION
Estas marcas otorgan permiso de URI para que tu aplicación pueda leer datos del contacto al que hace referencia el URI de contenido. Después, la actividad de selección llama afinish()
para devolverle el control a tu aplicación. -
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 People. - 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 a su dirección de correo electrónico y, luego, envíe un saludo electrónico.
Utilizar otra aplicación
Otra forma de permitir que el usuario modifique datos para los que no tienes permisos de acceso es activar una aplicación que sí tenga permisos y permitir que el usuario haga el trabajo allí.
Por ejemplo, la aplicación Calendario acepta
ACTION_INSERT
que te permite activar la
la IU de inserción de la aplicación. Puedes pasar "extras" datos en este intent, que la aplicación
para prepropagar la IU. Dado que los eventos recurrentes tienen una sintaxis compleja, se prefiere
Para insertar eventos en el proveedor de calendario, debes activar la aplicación Calendario con una
ACTION_INSERT
y, luego, permite que el usuario inserte el evento allí.
Cómo mostrar datos con una app auxiliar
Si tu aplicación tiene permisos de acceso, podrías de todos modos usar un
para mostrar datos en otra aplicación. Por ejemplo, la aplicación Calendario acepta
ACTION_VIEW
que muestra una fecha o un evento en particular.
Esto te permite mostrar información del calendario sin tener que crear tu propia IU.
Para obtener más información sobre esta función, consulta la
Descripción general del proveedor de calendario
No es necesario que la aplicación a la que envías el intent sea la misma
asociada con el proveedor. Por ejemplo, puedes recuperar un contacto del proveedor de contactos y, luego, enviar una intent ACTION_VIEW
con el URI de contenido de la imagen del contacto a un visor de imágenes.
Clases de contratos
Una clase de contratos define constantes que ayudan a las apps 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
se incluyen automáticamente en un proveedor. El desarrollador del proveedor los define
ponerlos a disposición de otros desarrolladores. Muchos de los proveedores incluidos en la plataforma de Android tienen clases de contratos correspondientes en el paquete android.provider
.
Por ejemplo, el proveedor de diccionario del usuario dispone de una clase de contratos UserDictionary
que tiene constantes de URI de contenido y nombre de columna. El
El URI de contenido para la tabla Words
se define en la constante
UserDictionary.Words.CONTENT_URI
La clase UserDictionary.Words
también tiene constantes de nombre de columna,
que se usan en los fragmentos de ejemplo de esta guía. Por ejemplo, una proyección de consulta puede ser
definidos de la siguiente manera:
Kotlin
val projection : Array<String> = arrayOf( UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE )
Java
String[] projection = { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
Otra clase de contratos 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 una clase de contratos que contiene constantes para intents y datos de intents.
Referencia de tipos de MIME
Los proveedores de contenido pueden devolver tipos de medios MIME estándar, cadenas de tipos de MIME personalizadas o ambos.
Los tipos de MIME tienen el siguiente 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 un
que usa ese URI devuelve texto que contenga etiquetas HTML.
Las cadenas de tipo MIME personalizadas, también llamadas tipos de MIME específicos del proveedor, tienen más valores complejos de type y subtype. Para varias filas, el valor del tipo es siempre el siguiente:
vnd.android.cursor.dir
Para una sola fila, el valor de tipo es siempre el siguiente:
vnd.android.cursor.item
subtype es específico del proveedor. Los proveedores integrados a Android suelen tener un subtipo simple. Por ejemplo, cuando la aplicación 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
El valor del subtipo es phone_v2
.
Otros desarrolladores de proveedores pueden crear sus propios patrones de subtipos en función de la
autoridad y nombres de tablas. Por ejemplo, piensa en un proveedor que contiene horarios de trenes.
La autoridad del proveedor es com.example.trains
y contiene las tablas Line1, Line2 y Line3. En respuesta al siguiente URI de contenido para la tabla Line1:
content://com.example.trains/Line1
el proveedor devuelve el siguiente tipo de MIME:
vnd.android.cursor.dir/vnd.example.line1
En respuesta al siguiente URI de contenido para la fila 5 de la tabla Línea2:
content://com.example.trains/Line2/5
el proveedor devuelve el siguiente tipo de MIME:
vnd.android.cursor.item/vnd.example.line2
La mayoría de los proveedores de contenido definen constantes de clase de contratos para los tipos de MIME que usan. Por ejemplo, la clase de contratos 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 el Sección URI de contenido.