O provedor de conteúdo gerencia o acesso a um repositório central de dados. Um provedor é parte de um aplicativo para Android, que, em geral, fornece a própria IU para trabalhar com os dados. No entanto, provedores de conteúdo são usados principalmente aplicativos, que acessam o provedor usando um objeto cliente do provedor. Juntos, os provedores e provedores oferecem uma interface padronizada e consistente para dados que também lidam comunicação entre processos e acesso seguro aos dados.
Normalmente, você trabalha com provedores de conteúdo em um de dois cenários: na implementação para acessar um provedor de conteúdo existente em outro aplicativo ou criar um novo provedor de conteúdo no seu aplicativo para compartilhar dados com outros aplicativos.
Esta página abrange os fundamentos do trabalho com provedores de conteúdo existentes. Para saber mais sobre a implementação provedores de conteúdo nos seus próprios aplicativos, consulte Criar um provedor de conteúdo
Este tópico descreve o seguinte:
- Como os provedores de conteúdo funcionam.
- A API usada para recuperar dados de um provedor de conteúdo.
- A API usada para inserir, atualizar ou excluir dados em um provedor de conteúdo.
- Outros recursos de API que facilitam o trabalho com provedores.
Visão geral
Um provedor de conteúdo apresenta dados a aplicativos externos na forma de uma ou mais tabelas similares às que são encontradas em um banco de dados relacional. Uma linha representa uma instância de algum tipo de dados que o provedor coleta, e cada coluna na linha representa um dado individual coletado por uma instância.
O provedor de conteúdo coordena o acesso à camada de armazenamento de dados no aplicativo para um várias APIs e componentes diferentes. Conforme ilustrado na figura 1, eles incluem o seguinte:
- Compartilhamento do acesso aos dados do seu aplicativo com outros aplicativos
- Envio de dados a um widget
- Retorno de sugestões de pesquisas personalizadas para o aplicativo por meio do framework
de pesquisa usando
SearchRecentSuggestionsProvider
- Sincronização dos dados do aplicativo com seu servidor usando uma implementação de
AbstractThreadedSyncAdapter
- Carregamento de dados na sua IU usando um
CursorLoader
![Relacionamento entre o provedor de conteúdo e outros componentes.](https://developer.android.com/static/guide/topics/providers/images/content-provider-tech-stack.png?authuser=9&hl=pt)
Figura 1. Relação entre um provedor de conteúdo e outros componentes.
Acessar um provedor
Quando quiser acessar dados em um provedor de conteúdo, use o
objeto ContentResolver
no
Context
do aplicativo para se comunicar com o provedor como cliente. O
objeto ContentResolver
se comunica com o objeto do provedor, uma
instância de uma classe que implementa ContentProvider
.
O objeto do
provedor recebe solicitações de dados de clientes, realiza a ação solicitada e retorna os
resultados. Esse objeto tem métodos que chamam métodos com nomes idênticos no objeto provedor,
uma instância de uma das subclasses concretas de ContentProvider
. A
Os métodos ContentResolver
fornecem os recursos
"CRUD" (criar, recuperar, atualizar e excluir) funções de armazenamento permanente.
Um padrão comum de acesso ao ContentProvider
a partir da IU é usar um
CursorLoader
para executar uma consulta assíncrona em segundo plano. A
Activity
ou Fragment
na sua interface chama um
CursorLoader
à consulta, que recebe
ContentProvider
usando a ContentResolver
.
Isso permite que a interface continue disponível para o usuário enquanto a consulta é executada. Esse padrão envolve a interação de vários objetos diferentes, assim como o mecanismo de armazenamento subjacente, conforme pode ser visto na figura 2.
![Interação entre ContentProvider, outras classes e o armazenamento.](https://developer.android.com/static/guide/topics/providers/images/content-provider-interaction.png?authuser=9&hl=pt)
Figura 2. Interação entre ContentProvider
, outras classes e armazenamento.
Observação: para acessar um provedor, o aplicativo geralmente precisa solicitar permissões específicas no arquivo de manifesto. Esse padrão de desenvolvimento é descrito com mais detalhes no Seção Permissões do provedor de conteúdo.
Um dos provedores integrados à plataforma Android é o Provedor de dicionário do usuário, que armazena as palavras fora do padrão que o usuário deseja manter. A tabela 1 mostra como os dados podem ser exibidos nesta tabela do provedor:
Tabela 1: amostra da tabela do dicionário do usuário.
palavra | ID do aplicativo | frequência | localidade | _ID |
---|---|---|---|---|
mapreduce |
usuário1 | 100 | en_US | 1 |
precompiler |
usuário14 | 200 | fr_FR | 2 |
applet |
usuário2 | 225 | fr_CA | 3 |
const |
usuário1 | 255 | pt_BR | 4 |
int |
usuário5 | 100 | en_UK | 5 |
Na tabela 1, cada linha representa uma instância de uma palavra que não
encontrados em um dicionário comum. Cada coluna representa um dado para essa palavra, como
local em que foi encontrada pela primeira vez. Os cabeçalhos da coluna são nomes de coluna armazenados no
provedor. Portanto, para se referir à localidade de uma linha, por exemplo, consulte a coluna locale
. Para
neste provedor, a coluna _ID
serve como uma coluna de chave primária que
pelo provedor automaticamente.
Para ter acesso a uma lista das palavras e às localidades delas no Provedor de dicionário do usuário,
chame ContentResolver.query()
.
O método query()
chama o
método ContentProvider.query()
definido pelo
Provedor de dicionário do usuário. As linhas de código a seguir mostram uma
chamada 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
A tabela 2 mostra como os argumentos para
query(Uri,projection,selection,selectionArgs,sortOrder)
corresponde a uma instrução SQL SELECT:
Tabela 2:query()
em comparação com a consulta SQL.
Argumento query() |
palavra-chave/parâmetro SELECT | Observações |
---|---|---|
Uri |
FROM table_name |
Uri é mapeado para a tabela no provedor chamado table_name. |
projection |
col,col,col,... |
projection é uma matriz de colunas incluída em cada linha.
recuperados.
|
selection |
WHERE col = value |
selection especifica os critérios para selecionar linhas. |
selectionArgs |
Não há equivalente exato. Argumentos de seleção substituem marcadores de posição ? na
cláusula de seleção.
|
|
sortOrder |
ORDER BY col,col,... |
sortOrder especifica a ordem em que as linhas aparecem no
Cursor retornado.
|
URIs de conteúdo
Um URI de conteúdo é um URI que identifica dados em um provedor. URIs de conteúdo incluem o nome simbólico de todo o provedor (a autoridade dele), e um nome que aponta para uma tabela: um caminho. Ao chamar um método cliente para acessar uma tabela em um provedor, o URI de conteúdo da tabela é um dos argumentos.
Nas linhas de código anteriores, a constante
CONTENT_URI
contém o URI de conteúdo de
a tabela Words
do Provedor de dicionário do usuário. O ContentResolver
analisa a autoridade do URI e o usa para resolver o provedor pela
comparando a autoridade com uma tabela do sistema de provedores conhecidos. A
ContentResolver
poderá expedir os argumentos da consulta ao
de nuvem.
O ContentProvider
usa o caminho que é parte do URI de conteúdo para escolher a
tabela que será acessada. Os provedores geralmente têm um caminho para cada tabela exposta.
Nas linhas de código anteriores, o URI completo da tabela Words
é:
content://user_dictionary/words
- A string
content://
é o esquema, que está sempre presente e o identifica como um URI de conteúdo. - A string
user_dictionary
é a autoridade do provedor. - A string
words
é o caminho da tabela.
Muitos provedores permitem que você acesse uma única linha em uma tabela anexando um valor de ID
ao final do URI. Por exemplo, para recuperar uma linha em que _ID
seja
4
do Provedor de dicionário do usuário, é possível usar este URI de conteúdo:
Kotlin
val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)
Java
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
Geralmente, você usa valores de ID ao recuperar um conjunto de linhas e depois quer atualizar ou excluir um deles.
Observação: as classes Uri
e Uri.Builder
contêm métodos de conveniência para construir objetos URI bem formados a partir de strings. A
A classe ContentUris
contém métodos convenientes para anexar valores de ID ao
um URI. O snippet anterior usa withAppendedId()
para anexar um ID ao URI de conteúdo do Provedor de dicionário do usuário.
Recuperar dados do provedor
Esta seção descreve como recuperar dados de um provedor usando o Provedor de dicionário do usuário como exemplo.
Para maior clareza, os snippets de código nesta seção chamam
ContentResolver.query()
na linha de execução de interface. Em
código real, no entanto, realiza consultas de forma assíncrona em uma linha de execução separada. Você pode
use a classe CursorLoader
, que é descrita
com mais detalhes na
Carregadores. Além disso, as linhas de código são apenas snippets. Eles não mostram um
para o aplicativo.
Para recuperar dados de um provedor, siga estas etapas básicas:
- Solicite permissão de acesso de leitura para o provedor.
- Defina o código que envia uma consulta ao provedor.
Solicitar permissão de acesso de leitura
Para recuperar dados de um provedor, seu aplicativo precisa de permissão de acesso de leitura para o
de nuvem. Não é possível solicitar essa permissão durante a execução. Em vez disso, você deve especificar
você precisa dessa permissão em seu manifesto, usando o
<uses-permission>
e o nome de permissão exato definido pelo
de nuvem.
Ao especificar esse elemento no manifesto, você solicita esse para seu aplicativo. Quando os usuários instalarem o aplicativo, eles concederão essa permissão implicitamente.
Para encontrar o nome exato da permissão de acesso para leitura do provedor que você está usando, assim como os nomes de outras permissões de acesso usadas pelo provedor, consulte a documentação do provedor.
O papel das permissões no acesso aos provedores é descrito em mais detalhes nas Seção Permissões do provedor de conteúdo.
O Provedor de dicionário do usuário define a permissão
android.permission.READ_USER_DICTIONARY
no arquivo de manifesto. Portanto, se um
aplicativo quiser ler pelo provedor, ele precisará solicitar essa permissão.
Criar a consulta
A próxima etapa da recuperação de dados de um provedor é construir uma consulta. O snippet a seguir define algumas variáveis para acessar o Provedor de dicionário do usuário:
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 = {""};
O snippet a seguir mostra como usar
ContentResolver.query()
, tendo o Provedor
de dicionário do usuário como exemplo. Uma consulta de cliente do provedor é similar a uma consulta SQL e contém uma
conjunto de colunas a serem retornadas, um conjunto de critérios de seleção e uma ordem de classificação.
O conjunto de colunas que a consulta retorna é chamado de projeção, e
a variável é mProjection
.
A expressão que especifica as linhas a recuperar é dividida em uma cláusula de seleção e
em argumentos de seleção. A cláusula de seleção é uma combinação de expressões lógicas e booleanas,
nomes e valores de colunas. A variável é mSelectionClause
. Se você especificar
parâmetro substituível ?
em vez de um valor, o método de consulta recuperará o valor
da matriz de argumentos de seleção, que é a variável mSelectionArgs
.
No próximo snippet, se o usuário não inserir uma palavra, a cláusula de seleção será definida como
null
, e a consulta retornará todas as palavras no provedor. Se o usuário inserir
uma palavra, a cláusula de seleção será definida como UserDictionary.Words.WORD + " = ?"
e
o primeiro elemento da matriz de argumentos de seleção será definido como a palavra que o usuário inserir.
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 }
Essa consulta é análoga à seguinte instrução SQL:
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
Nessa instrução SQL, os nomes de coluna reais são usados no lugar de constantes de classes de contrato.
Proteção contra entradas maliciosas
Se os dados gerenciados pelo provedor de conteúdo estiverem em um banco de dados SQL, incluindo usuários externos não confiáveis em instruções SQL brutas pode levar à injeção de SQL.
Considere a seguinte cláusula de seleção:
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;
Se fizer isso, você permitirá que o usuário concatene SQL malicioso na sua instrução SQL.
Por exemplo, o usuário pode inserir "nada; DROP TABLE *;" para mUserInput
, que
resulta na cláusula de seleção var = nothing; DROP TABLE *;
.
Como o cláusula de seleção é tratada como uma instrução SQL, isso pode fazer com que o provedor apague todas as tabelas no banco de dados SQLite subjacente, a menos que o provedor esteja configurado para detectar Tentativas de injeção de SQL.
Para evitar esse problema, use uma cláusula de seleção que tenha ?
como um parâmetro
substituível e uma matriz de argumentos de seleção separada. Dessa forma, a entrada do usuário
é vinculado diretamente à consulta em vez de ser interpretada como parte de uma instrução SQL.
Pelo fato de não ser tratada como SQL, a inserção do usuário não injetará SQL malicioso. Em vez de usar
a concatenação para incluir a inserção do usuário, use esta cláusula de seleção:
Kotlin
// Constructs a selection clause with a replaceable parameter var selectionClause = "var = ?"
Java
// Constructs a selection clause with a replaceable parameter String selectionClause = "var = ?";
Configure a matriz de argumentos de seleção desta maneira:
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 = {""};
Insira um valor na matriz de argumentos de seleção desta maneira:
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;
Uma cláusula de seleção que usa ?
como um parâmetro substituível e uma matriz de
matriz de argumentos de seleção é a melhor maneira de especificar uma seleção, mesmo que o provedor não esteja
com base em um banco de dados SQL.
Mostrar os resultados da consulta
O método cliente ContentResolver.query()
sempre
retorna um Cursor
contendo as colunas especificadas pela projeção
da consulta para as linhas que atendem aos critérios de seleção da consulta. Um
objeto Cursor
fornece acesso de leitura aleatório às linhas e colunas que
ele contém.
Com os métodos Cursor
, é possível iterar nas linhas do
resultados, determinar o tipo de dados de cada coluna, extrair os dados de uma coluna e examinar outros
dos resultados.
Algumas implementações de Cursor
automaticamente
Atualizar o objeto quando os dados do provedor mudarem, acionar métodos em um objeto observador
quando o Cursor
muda ou ambos.
Observação:um provedor pode restringir o acesso a colunas com base na natureza da que faz a consulta. Por exemplo, o Provedor de contatos restringe o acesso de algumas colunas aos os adaptadores de sincronização, para não retorná-los a uma atividade ou serviço.
Se nenhuma linha corresponder aos critérios de seleção, o provedor
retorna um objeto Cursor
para o qual
Cursor.getCount()
é
0, ou seja, um cursor vazio.
Se ocorrer um erro interno, os resultados da consulta dependerão do provedor específico. Pode ser
retorne uma null
ou poderá gerar uma Exception
.
Como Cursor
é uma lista de linhas, uma boa maneira de exibir o
o conteúdo de um Cursor
é vinculá-lo a um ListView
usando um SimpleCursorAdapter
.
O snippet a seguir continua o código do snippet anterior. Ele cria uma
Objeto SimpleCursorAdapter
que contém o Cursor
recuperado pela consulta e define esse objeto para ser o adaptador de um
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);
Observação: para reverter uma ListView
com um
Cursor
, o cursor precisa conter uma coluna denominada _ID
.
Por isso, a consulta mostrada anteriormente recupera a coluna _ID
para o
Words
, mesmo que ListView
não a exiba.
Essa restrição também explica por que a maioria dos provedores tem uma coluna _ID
para cada um dos
nas mesas deles.
Extrair dados dos resultados da consulta
Além de exibir os resultados da consulta, é possível usá-los para outras tarefas. Para
exemplo, você pode recuperar grafias do Provedor de dicionário do usuário e, em seguida, procurá-las no
e outros provedores. Para fazer isso, itere as linhas no Cursor
, conforme mostrado no exemplo a seguir:
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 }
As implementações Cursor
contêm vários métodos "get" para
recuperar diferentes tipos de dados do objeto. Por exemplo, o snippet anterior
usa getString()
. Elas também têm um
método getType()
que retorna um valor indicando
o tipo dos dados da coluna.
Permissões do provedor de conteúdo
O aplicativo de um provedor pode especificar permissões que outros aplicativos precisam ter para acessar os dados do provedor. Com essas permissões, o usuário sabe quais dados que um aplicativo tenta acessar. Com base nos requisitos do provedor, outros aplicativos solicitam as permissões de que precisam para acessar o provedor. Os usuários finais verão as permissões solicitadas quando instalarem o aplicativo.
Se um aplicativo do provedor não especificar nenhuma permissão, os outros aplicativos não terão acesso aos dados do provedor, a menos que ele seja exportado. Além disso, os componentes aplicativo do provedor sempre terão acesso total de leitura e gravação, as permissões especificadas.
O Provedor de dicionário do usuário requer a
android.permission.READ_USER_DICTIONARY
para recuperar dados dele.
O provedor tem um android.permission.WRITE_USER_DICTIONARY
separado
permissão para inserir, atualizar ou excluir dados.
Para ter as permissões necessárias para acessar um provedor, um aplicativo as solicitará com um
elemento <uses-permission>
no arquivo de manifesto. Quando o Gerenciador de pacotes do Android instala o aplicativo, o usuário
precisa aprovar todas as permissões solicitadas pelo aplicativo. Se o usuário aprová-las,
O Gerenciador de pacotes continua a instalação. Se o usuário não as aprovar, o gerenciador de pacotes
interrompe a instalação.
O exemplo a seguir
<uses-permission>
Elemento solicita acesso de leitura ao Provedor de dicionário do usuário:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
O impacto das permissões no acesso ao provedor é explicado com mais detalhes em Dicas de segurança.
Inserir, atualizar e excluir dados
Do mesmo modo que você recupera dados de um provedor, também usa a interação entre
um cliente do provedor e o ContentProvider
do provedor para modificar dados.
Você chama um método de ContentResolver
com argumentos que são passados para
o método correspondente de ContentProvider
. O fornecedor
a segurança e a comunicação entre processos.
Inserir dados
Para inserir dados em um provedor, chame o
método
ContentResolver.insert()
. Esse método insere uma nova linha no provedor e retorna um URI de conteúdo dessa linha.
O snippet a seguir mostra como inserir uma nova palavra no Provedor de dicionário do usuário:
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 );
Os dados da nova linha vão para um único objeto ContentValues
, que
é semelhante a um cursor de uma linha. As colunas nesse objeto não precisam ter o
mesmo tipo de dados e, se você não quiser especificar um valor, poderá definir uma coluna
como null
usando ContentValues.putNull()
.
O snippet anterior não adiciona a coluna _ID
porque essa coluna é mantida
automaticamente. O provedor atribui um valor exclusivo de _ID
para cada linha
adicionada. Normalmente, os provedores usam esse valor como a chave primária da tabela.
O URI de conteúdo retornado em newUri
identifica a linha recém-adicionada com
o seguinte formato:
content://user_dictionary/words/<id_value>
O <id_value>
é o conteúdo de _ID
da nova linha.
A maioria dos provedores pode detectar essa forma de URI de conteúdo automaticamente e, em seguida, realizar a operação
solicitada na linha específica.
Para receber o valor de _ID
do Uri
retornado, chame
ContentUris.parseId()
.
Atualizar dados
Para atualizar uma linha, use um objeto ContentValues
com as
valores, assim como é feito com uma inserção e critérios de seleção, como é feito com uma consulta.
O método cliente usado é
ContentResolver.update()
. Só é necessário adicionar
valores ao objeto ContentValues
para as colunas que você está atualizando. Se você
quiser apagar o conteúdo de uma coluna, defina o valor como null
.
O snippet a seguir altera todas as linhas cuja localidade tem o idioma "en"
para uma
a localidade é null
. O valor de retorno é o número de linhas que foram atualizadas.
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 );
Limpar a entrada do usuário ao fazer a chamada
ContentResolver.update()
: Para saber mais sobre
isso, leia a seção Proteger-se contra entradas mal-intencionadas.
Excluir dados
Excluir linhas é semelhante a recuperar dados de linhas. Você especifica os critérios de seleção para as linhas
que você deseja excluir, e o método cliente retorna o número de linhas excluídas.
O snippet a seguir exclui linhas cujo ID do app corresponde a "user"
. O método retorna o
número de linhas excluídas.
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 );
Limpar a entrada do usuário ao fazer a chamada
ContentResolver.delete()
: Para saber mais sobre
isso, leia a seção Proteger-se contra entradas mal-intencionadas.
Tipos de dados do provedor
Provedores de conteúdo podem oferecer muitos tipos de dados diferentes. O Provedor de dicionário do usuário fornece apenas texto, mas provedores também podem oferecer os seguintes formatos:
- número inteiro
- número inteiro longo (longo)
- ponto flutuante
- ponto flutuante longo (duplo)
Outro tipo de dado que os provedores usam com frequência é um objeto binário grande (BLOB) implementado como um
matriz de bytes de 64 KB. Para ver os tipos de dados disponíveis, consulte os
métodos "get" da classe Cursor
.
O tipo dos dados para cada coluna em um provedor normalmente é listado na documentação do provedor.
Os tipos de dados para o Provedor de dicionário do usuário estão listados na documentação de referência.
para a classe de contrato UserDictionary.Words
. As classes de contrato são
descritos na seção Classes de contrato.
Também é possível determinar o tipo dos dados chamando Cursor.getType()
.
Os provedores também mantêm informações do tipo de dados MIME para cada URI de conteúdo que definem. Você pode usar as informações do tipo MIME para descobrir se o aplicativo pode lidar com dados que o provedor de serviços ou escolher um tipo de processamento com base no tipo MIME. Você geralmente precisa das tipo MIME quando você trabalha com um provedor que contém estruturas de dados ou arquivos.
Por exemplo, a tabela ContactsContract.Data
no Provedor de contatos usa tipos MIME para rotular o tipo dos dados de contato armazenados em cada
linha. Para acessar o tipo MIME correspondente a um URI de conteúdo, chame
ContentResolver.getType()
.
A seção Referência do tipo MIME descreve as a sintaxe de tipos MIME padrão e personalizados.
Formas alternativas de acesso ao provedor
Três formas alternativas de acesso ao provedor são importantes no desenvolvimento do aplicativo:
-
Acesso em lote: você pode criar um lote de chamadas de acesso com métodos no
a classe
ContentProviderOperation
e, em seguida, aplicá-las comContentResolver.applyBatch()
. -
Consultas assíncronas: faça as consultas em uma linha de execução separada. Você pode
use um objeto
CursorLoader
. Os exemplos no guia Carregadores demonstram como fazer isso. - Acesso a dados usando intents: embora não seja possível enviar uma intent diretamente a um provedor, é possível enviar uma intent ao aplicativo do provedor, que é geralmente mais bem equipados para modificar os dados do provedor.
O acesso em lote e a modificação usando intents são descritos nas seções a seguir.
Acesso em lote
O acesso em lote a um provedor é útil para inserir um grande número de linhas, de linhas em várias tabelas na mesma chamada de método e, em geral, para a execução de um conjunto de operações entre limites de processo como uma transação, chamada de operação atômica.
Para acessar um provedor no modo de lote,
crie uma matriz de objetos ContentProviderOperation
e, em seguida,
enviá-los a um provedor de conteúdo com
ContentResolver.applyBatch()
. Você passa o valor
a autoridade do provedor de conteúdo a esse método, em vez de um URI de conteúdo específico.
Isso permite que cada objeto ContentProviderOperation
na matriz funcione
com outra tabela. Uma chamada para ContentResolver.applyBatch()
retorna uma matriz de resultados.
A descrição da classe de contrato ContactsContract.RawContacts
contém um snippet de código que demonstra a inserção em lote.
Acesso a dados usando intents
As intents podem fornecer acesso indireto a um provedor de conteúdo. É possível permitir que o usuário acesse dados em um provedor, mesmo que seu aplicativo não tenha permissões de acesso por recuperar uma intent de resultado de um aplicativo que tenha permissões ou ativando uma aplicativo que tem permissões e permite que o usuário trabalhe nele.
Receber acesso com permissões temporárias
Você pode acessar dados em um provedor de conteúdo, mesmo que não tenha o acesso adequado permissões, enviando uma intent a um aplicativo que tenha as permissões e recebendo de volta uma intent de resultado que contém permissões de URI. Essas são permissões para um URI de conteúdo específico que têm a mesma duração da atividade que as recebeu. O aplicativo que tem permissões permanentes concede permissões temporárias ao definir uma sinalização na intent de resultado:
-
Permissão de leitura:
FLAG_GRANT_READ_URI_PERMISSION
-
Permissão de gravação:
FLAG_GRANT_WRITE_URI_PERMISSION
Observação: essas sinalizações não fornecem acesso geral de leitura ou gravação ao provedor que tem a autoridade contida na URI de conteúdo. O acesso destina-se somente ao próprio URI.
Ao enviar URIs de conteúdo para outro app, inclua pelo menos um destes de status. As flags oferecem os seguintes recursos para qualquer app que receba uma intent destinada ao Android 11 (nível 30 da API) ou versões mais recentes:
- ler ou gravar os dados que o URI de conteúdo representa; dependendo da sinalização incluída na intent.
- Ganhar pacote visibilidade do aplicativo que contém o provedor de conteúdo que corresponde ao Autoridade de URI. O app que envia a intent e o app contém o provedor de conteúdo podem ser dois aplicativos diferentes.
Um provedor define permissões de URI para URIs de conteúdo em seu manifesto usando a propriedade
android:grantUriPermissions
do elemento
<provider>
bem como o
<grant-uri-permission>
elemento filho da
<provider>
. O mecanismo de permissões de URI é explicado com mais detalhes nas
Guia Permissões no Android.
Por exemplo, é possível recuperar dados de um contato no Provedor de contatos, mesmo sem
a permissão READ_CONTACTS
. Você pode querer fazer
isso em um aplicativo que envie e-mails de parabenização a um contato no aniversário dele. Em vez de
solicitando READ_CONTACTS
, que dá acesso a todos
os contatos e todas as informações do usuário, permitem que o usuário controle quais
contatos que seu aplicativo utiliza. Para fazer isso, use o seguinte processo:
-
No seu aplicativo, envie uma intent contendo a ação
ACTION_PICK
e os "contatos" Tipo MIMECONTENT_ITEM_TYPE
, usando o método métodostartActivityForResult()
. - Como essa intent corresponde ao filtro de intents da "Seleção" do app People atividade, ela vai para o primeiro plano.
-
Na atividade de seleção, o usuário pode selecionar um
contato para atualizar. Quando isso acontece, a atividade de seleção chama
setResult(resultcode, intent)
para configurar uma intent para retornar ao aplicativo. A intent contém o URI de conteúdo. do contato selecionado pelo usuário e os "extras" sinalizaçõesFLAG_GRANT_READ_URI_PERMISSION
: Essas sinalizações concedem permissão de URI para que o aplicativo leia dados do contato a que o URI de conteúdo aponta. A atividade de seleção, em seguida, chamafinish()
para retornar o controle ao aplicativo. -
A atividade retorna ao primeiro plano, e o sistema chama o método
onActivityResult()
da atividade. Esse método recebe a intent de resultado criada pela atividade de seleção no app Pessoas. - Com o URI de conteúdo da intent de resultado, é possível ler os dados do contato do Provedor de contatos, mesmo que você não tenha solicitado permissão permanente de acesso de leitura para o provedor no manifesto. É possível ver as informações de data de nascimento do contato ou endereço de e-mail e, em seguida, enviar um e-mail de saudação.
Usar outro aplicativo
Outra maneira de permitir que o usuário modifique dados aos quais você não tem permissão de acesso é ativar um aplicativo que não tenha permissões e permitir que o usuário faça o trabalho lá.
Por exemplo, o aplicativo Agenda aceita uma
ACTION_INSERT
, que permite ativar
interface de inserção do seu aplicativo. É possível transmitir "extras" nessa intent, que o aplicativo
usa para pré-preencher a interface. Como os eventos recorrentes têm uma sintaxe complexa,
de inserir eventos no Provedor de Agenda é ativar o aplicativo Agenda com uma
ACTION_INSERT
e, em seguida, deixe o usuário inserir o evento.
Mostrar dados usando um app assistente
Se seu aplicativo tiver permissões de acesso, você ainda poderá usar uma
de exibir dados em outro aplicativo. Por exemplo, o aplicativo Agenda aceita uma
ACTION_VIEW
que exibe uma data ou um evento específico.
Isso permite que você exiba as informações da agenda sem precisar criar sua própria interface.
Para saber mais sobre esse recurso, consulte a
Visão geral do provedor de agenda.
O aplicativo para o qual você envia a intent não precisa ser o aplicativo
associados ao provedor. Por exemplo, é possível recuperar um contato do
Provedor de contatos e enviar uma intent ACTION_VIEW
que contenha o URI de conteúdo da imagem do contato para um visualizador de imagens.
Classes de contrato
As classes de contrato definem constantes que ajudam os aplicativos a trabalhar com URIs de conteúdo,
nomes de colunas, ações da intent e outros recursos de um provedor de conteúdo. As classes de contrato não são
incluído automaticamente em um provedor. O desenvolvedor do provedor precisa defini-los e
disponibilizá-las a outros desenvolvedores. Muitos dos provedores incluídos na Plataforma
Android têm classes de contrato correspondentes no pacote android.provider
.
Por exemplo, o Provedor de dicionário do usuário tem uma classe de contrato
UserDictionary
que contém constantes de URI de conteúdo e de nome de coluna. A
URI de conteúdo da tabela Words
é definido na constante
UserDictionary.Words.CONTENT_URI
.
A classe UserDictionary.Words
também contém constantes de nome de coluna,
que são usadas nos snippets de exemplo neste guia. Por exemplo, uma projeção de consulta pode ser
definida assim:
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 };
Outra classe de contrato é ContactsContract
para o Provedor de contatos.
A documentação de referência dessa classe contém exemplos de snippets de código. Uma das
subclasses, ContactsContract.Intents.Insert
, é uma classe de
contrato que contém as constantes para intents e dados de intents.
Referência do tipo MIME
Provedores de conteúdo podem retornar tipos MIME de mídia MIME padrão, strings de tipos MIME personalizados ou ambos.
Os tipos MIME têm o seguinte formato:
type/subtype
Por exemplo, o tipo MIME text/html
conhecido tem o tipo text
e
o subtipo html
. Se o provedor retornar esse tipo para um URI, isso significa que um
consulta usando esse URI retorna o texto que contém tags HTML.
As strings de tipos MIME personalizados, também chamadas de tipos MIME específicos do fornecedor, têm mais valores type e subtype complexos. Para várias linhas, o valor do tipo é sempre o seguinte:
vnd.android.cursor.dir
Para uma única linha, o valor do tipo é sempre este:
vnd.android.cursor.item
O subtype é específico do provedor. Os provedores integrados do Android normalmente têm um subtipo simples. Por exemplo, quando o aplicativo Contatos criar uma linha para um número de telefone, ele configurará o seguinte tipo MIME na linha:
vnd.android.cursor.item/phone_v2
O valor do subtipo é phone_v2
.
Outros desenvolvedores de provedor podem criar os próprios padrões de subtipos com base no
nomes de tabela e autoridade. Por exemplo, considere um provedor que contenha horários de trens.
A autoridade do provedor é com.example.trains
, e ele contém as tabelas
Line1, Line2 e Line3. Em resposta ao seguinte URI de conteúdo para a tabela Line1:
content://com.example.trains/Line1
o provedor retorna o seguinte tipo MIME:
vnd.android.cursor.dir/vnd.example.line1
Em resposta ao seguinte URI de conteúdo para a linha 5 na tabela Line2:
content://com.example.trains/Line2/5
o provedor retorna o seguinte tipo MIME:
vnd.android.cursor.item/vnd.example.line2
A maioria dos provedores de conteúdo define constantes de classe de contrato para os tipos MIME que usam. A
classe de contrato ContactsContract.RawContacts
do Provedor de contatos, por exemplo, define a constante
CONTENT_ITEM_TYPE
para o tipo MIME de
uma linha exclusiva do contato bruto.
URIs de conteúdo para linhas únicas são descritos na Seção URIs de conteúdo.