lightbulb_outline Please take our October 2018 developer survey. Start survey

Fundamentos do provedor de conteúdo

O provedor de conteúdo gerencia o acesso a um repositório central de dados. Um provedor é parte de um aplicativo do Android, que, em geral, fornece a própria IU para trabalhar com os dados. Contudo, provedores de conteúdo se destinam principalmente ao uso por outros aplicativos, que acessam o provedor usando um objeto cliente do provedor. Juntos, provedores e clientes do provedor oferecem interface padronizada e consistente para dados que também lidam com comunicação em processos internos e garantem acesso a dados.

Este tópico descreve os conceitos básicos do 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

O provedor de conteúdo apresenta dados a aplicativos externos na forma de uma ou mais tabelas similares às tabelas 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 uma parte individual de dados coletados por uma instância.

Por exemplo, um dos provedores embutidos na plataforma do Android é o dicionário do usuário, que armazena as grafias de palavras incomuns que o usuário deseja manter. A tabela 1 ilustra como podem ser os dados nesta tabela do provedor:

Tabela 1: Tabela de exemplo de dicionário do usuário.

palavra id do aplicativo frequência localidade _ID
mapreduce user1 100 en_US 1
precompiler user14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 en_UK 5

Na tabela 1, cada linha representa uma instância de uma palavra que pode não ser encontrada em um dicionário comum. Cada coluna representa alguns dados dessa palavra, como a localidade em que foi encontrada pela primeira vez. Os cabeçalhos da coluna são nomes de coluna armazenados no provedor. Para consultar a localidade de uma linha, consulte sua coluna locale. Para esse provedor, a coluna _ID serve como uma coluna de "chave principal" que o provedor mantém automaticamente.

Observação: Os provedores não precisam ter uma chave principal nem usar _ID como o nome de coluna de uma chave principal se uma for apresentada. Contudo, se você deseja agrupar dados de um provedor em um ListView, um dos nomes de coluna deve ser _ID. Esse requisito é explicado com mais detalhes na seção Exibição dos resultados da consulta.

Acesso a um provedor

Os aplicativos acessam dados de um provedor de conteúdo com um objeto cliente ContentResolver. Esse objeto tem métodos que chamam métodos de nome idêntico no objeto do provedor, uma instância de uma das subclasses concretas de ContentProvider. Os métodos ContentResolver fornecem as funções básicas do "CRUD" (criar, recuperar, atualizar e excluir) de armazenamento persistente.

O objeto ContentResolver no processo do aplicativo cliente e o objeto ContentProvider no aplicativo que tem o provedor lidam automaticamente com a comunicação de processos internos. ContentProvider também age como uma camada de abstração entre seu repositório de dados e a aparência externa de dados na forma de tabelas.

Observação: Para acessar um provedor, o aplicativo normalmente precisa solicitar permissões específicas no arquivo de manifesto. Isso é descrito com mais detalhes na seção Permissões do provedor de conteúdo.

Por exemplo, para obter uma lista das palavras e respectivas localidades do Provedor de dicionário do usuário, chama-se 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 exibem uma chamada ContentResolver.query():

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

A tabela 2 mostra como os argumentos de query(Uri,projection,selection,selectionArgs,sortOrder) correspondem a uma declaração SQL SELECT:

Tabela 2: Query() comparada à consulta SQL.

query() argumento SELECT palavra-chave/parâmetro Observações
Uri FROM table_name Uri mapeia para a tabela no provedor chamado table_name.
projection col,col,col,... projection é uma matriz de colunas que devem ser incluídas para cada linha recuperada.
selection WHERE col = value selection especifica os critérios para selecionar linhas.
selectionArgs (Não exatamente equivalente. Argumentos de seleção substituem espaços reservados ? 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

URI de conteúdo é uma URI que identifica dados em um provedor. URIs de conteúdo contêm o nome simbólico de todo o provedor (sua autoridade) e um nome que aponta para uma tabela (um caminho). Ao chamar um método cliente para acessar uma tabela em um provedor, a URI de conteúdo da tabela é um dos argumentos.

Nas linhas de código anteriores, a constante CONTENT_URI contém a URI de conteúdo da tabela de "palavras" do dicionário do usuário. O objeto ContentResolver analisa a autoridade da URI, usando-a para "determinar" o provedor comparando a autoridade a uma tabela de provedores conhecidos do sistema. O ContentResolver pode, então, enviar os argumentos da consulta ao provedor correto.

O ContentProvider usa o caminho que é parte da URI de conteúdo para escolher a tabela para acessar. Os provedores normalmente têm um caminho para cada tabela exposta.

Nas linhas de código anteriores, a URI completa da tabela de "palavras" é:

content://user_dictionary/words

onde a string user_dictionary é a autoridade do provedor e a string words é o caminho da tabela. A string content:// (o esquema) está sempre presente e identifica isso como uma URI de conteúdo.

Muitos provedores permitem acesso a uma única linha em uma tabela por meio da anexação do valor de um ID no fim da URI. Por exemplo, para recuperar uma linha em que _ID seja 4 do dicionário do usuário, é possível usar esta URI de conteúdo:

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

Normalmente são usados valores de ID ao recuperar um conjunto de linhas e, em seguida, é necessário atualizar ou excluir uma delas.

Observação: As classes Uri e Uri.Builder contêm métodos convenientes para a construção de objetos de URI bem formados a partir de strings. A classe ContentUris contém métodos convenientes para anexar valores de ID a uma URI. O snippet anterior usa withAppendedId() para anexar um ID à URI de conteúdo UserDictionary.

Recuperação de dados do provedor

Esta seção descreve como recuperar dados de um provedor usando o Provedor de dicionário do usuário como exemplo.

Por uma questão de clareza, os snippets de código nesta seção chamam ContentResolver.query() no "encadeamento da IU". No código, contudo, deve-se realizar consultas assincronamente em um encadeamento separado. Um modo de fazê-lo é usar a classe CursorLoader, descrita em mais detalhes no guia Carregadores. Além disso, as linhas de código são somente snippets — não mostram um aplicativo completo.

Para recuperar dados de um provedor, siga estas etapas básicas:

  1. Solicite a permissão de acesso para leitura para um provedor.
  2. Defina o código que envia uma consulta ao provedor.

Solicitação de permissão de acesso para leitura

Para recuperar dados de um provedor, o aplicativo precisa de "permissão de acesso de leitura" para o provedor. Não é possível solicitar essa permissão em tempo de execução. Em vez disso, você deve especificar que precisa dessa permissão no manifesto com o elemento <uses-permission> e o nome da permissão exata definida pelo provedor. Ao especificar esse elemento no manifesto, você está efetivamente "solicitando" essa permissão para o aplicativo. Quando usuários instalam seu aplicativo, eles concedem essa solicitação implicitamente.

Para encontrar o nome exato da permissão de acesso de leitura do provedor que está usando, bem 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 a provedores é descrito com mais detalhes na 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, deverá solicitar essa permissão.

Construção da consulta

A próxima etapa da recuperação de dados de um provedor é construir uma consulta. Esse primeiro snippet define algumas variáveis para acessar o Provedor de Dicionário do Usuário:


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

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

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

O próximo snippet mostra como usar ContentResolver.query() usando o Provedor de Dicionário do Usuário como exemplo. As consultas clientes do provedor se assemelham a consultas SQL e contêm um conjunto de colunas a retornar, um conjunto de critérios de seleção e uma classificação ordenada.

O conjunto de colunas que a consulta deve retornar é chamado de projeção (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 e nomes e valores de colunas (a variável mSelectionClause). Ao especificar o parâmetro ? substituível em vez de um valor, o método da consulta recupera o valor da matriz de argumentos de seleção (a variável mSelectionArgs).

No próximo snippet, se o usuário não inserir nenhuma 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 é definida como a palavra que o usuário inserir.

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

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

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

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

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

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

}

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

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

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

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

}

Essa consulta é análoga à declaração SQL:

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

Nesta declaração SQL, os nomes de coluna reais são usados no lugar de constantes de classes de contrato.

Proteção contra inserções mal-intencionadas

Se os dados gerenciados pelo provedor de conteúdo estiverem em um banco de dados SQL, inclusive dados não confiáveis externos nas declarações SQL brutas, existe a possibilidade de haver uma injeção de SQL.

Considere esta cláusula de seleção:

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

Se você fizer isso, estará permitindo que o usuário concatene SQL mal-intencionados na sua declaração SQL. Por exemplo, o usuário poderia inserir "nothing; DROP TABLE *;" para mUserInput, o que resultaria na cláusula de seleção var = nothing; DROP TABLE *;. Como a cláusula de seleção é tratada como uma declaração SQL, isso pode fazer com que o provedor apague todas as tabelas no banco de dados SQLite em questão (a menos que o provedor esteja configurado para capturar 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. Ao fazer isso, a inserção de dados do usuário se limita diretamente à consulta em vez de ser interpretada como parte de uma declaração SQL. Pelo fato de não ser tratada como SQL, a inserção de dados do usuário não injeta SQL mal-intencionados. Em vez de usar a concatenação para incluir a inserção de dados do usuário, use esta cláusula de seleção:

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

Configure a matriz de argumentos de seleção desta maneira:

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

Insira um valor na matriz de argumentos de seleção desta maneira:

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

Usar uma cláusula de seleção que tenha ? como um parâmetro substituível e uma matriz de argumentos de seleção é o melhor modo de especificar uma seleção, mesmo que o provedor se baseie em um banco de dados SQL.

Exibição dos 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 contém. Usando métodos de Cursor, é possível repetir as linhas nos resultados, determinar o tipo dos dados de cada coluna, extrair os dados de uma coluna e examinar outras propriedades dos resultados. Algumas implementações de Cursor atualizam o objeto automaticamente quando os dados do provedor mudam ou acionam métodos em um objeto observador quando o Cursor muda, ou ambos.

Observação: Os provedores podem restringir acesso a colunas com base na natureza do objeto que realiza a consulta. Por exemplo, o Provedor de Contatos restringe o acesso de algumas colunas a adaptadores de sincronização, por isso ela não os retornará a uma atividade ou serviço.

Se nenhuma linha atender aos critérios de seleção, o provedor retorna um objeto Cursor para o qual Cursor.getCount() é 0 (um cursor vazio).

Se ocorrer um erro interno, os resultados da consulta dependem do provedor determinado. Ele pode escolher retornar null ou pode gerar uma Exception.

Já que Cursor é uma "lista" de linhas, um bom modo de exibir o conteúdo de um Cursor é vinculá-lo a uma ListView por meio de um SimpleCursorAdapter.

O snippet a seguir continua o código do snippet anterior. Ele cria um objeto SimpleCursorAdapter contendo o Cursor recuperado pela consulta e configura esse objeto para ser o adaptador de uma ListView:

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

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

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

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

Observação: Para retornar uma ListView com um Cursor, o cursor deve conter uma coluna chamada _ID. Por isso, a consulta exibida anteriormente recupera a coluna _ID da tabelas de "palavras", mesmo que a ListView não a exiba. Essa restrição também explica por que a maioria dos provedores tem uma coluna _ID para cada tabela.

Obtenção de dados de resultados da consulta

Em vez de simplesmente exibir resultados da consulta, é possível usá-los para outras tarefas. Por exemplo, é possível recuperar grafias de um dicionário do usuário e, em seguida, procurá-los em outros provedores. Para isso, repetem-se as linhas no Cursor:


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

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

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

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

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

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

As implementações Cursor contêm diversos 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

Um aplicativo do provedor pode especificar permissões que outros aplicativos devem ter para acessar os dados do provedor. Essas permissões garantem que o usuário saiba quais dados um aplicativo tentará acessar. Com base nos requisitos do provedor, outros aplicativos solicitam as permissões de que precisam para acessar o provedor. Usuários finais veem as permissões solicitadas quando instalam o aplicativo.

Se um aplicativo do provedor não especificar nenhuma permissão, outros aplicativos não terão acesso aos dados do provedor. No entanto, os componentes no aplicativo do provedor sempre têm acesso total de leitura e gravação, independentemente das permissões especificadas.

Como observado anteriormente, o Provedor de Dicionário do Usuário requer a permissão android.permission.READ_USER_DICTIONARY para recuperar dados dele. O provedor tem a permissão android.permission.WRITE_USER_DICTIONARY separadamente para inserção, atualização ou exclusão de dados.

Para obter as permissões necessárias para acessar um provedor, um aplicativo as solicita como um elemento <uses-permission> no arquivo de manifesto. Quando o Android Package Manager (gerente de pacotes do Android) instala o aplicativo, o usuário precisa aprovar todas as permissões que o aplicativo solicita. Se o usuário aprovar todas, o gerente de pacotes continuará a instalação; se o usuário não as aprovar, o gerente de pacotes interromperá a instalação.

O elemento <uses-permission> a seguir 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 no guia Permissões e segurança.

Inserção, atualização e exclusão de 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. Chama-se um método de ContentResolver com argumentos que são passados para o método correspondente de ContentProvider. O provedor e o cliente do provedor processam automaticamente da segurança e da comunicação de processos internos.

Inserção de dados

Para inserir dados em um provedor, chame o método ContentResolver.insert(). Esse método insere uma nova linha no provedor e retorna uma URI de conteúdo dessa linha. Esse snippet mostra como inserir uma nova palavra no Provedor de Dicionário do Usuário:

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

...

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

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

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

Os dados da nova linha vão para um objeto ContentValues único, que tem forma 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, pode definir uma coluna como null usando ContentValues.putNull().

O snippet não adiciona a coluna _ID porque essa coluna é mantida automaticamente. O provedor atribui um valor exclusivo de _ID para cada linha adicionada. Os provedores normalmente usam esse valor como a chave principal da tabela.

A URI de conteúdo retornada em newUri identifica a linha recentemente 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 naquela linha.

Para obter o valor de _ID do Uri retornado, chame ContentUris.parseId().

Atualização de dados

Para atualizar uma linha, use um objeto ContentValues com os valores e os critérios de seleção atualizados, como se faz com uma inserção e em uma consulta, respectivamente. O método cliente usado é ContentResolver.update(). Só é necessário adicionar valores ao objeto ContentValues das colunas que forem atualizadas. Se você deseja 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 terem uma localidade de null. O valor de retorno é o número de linhas que foram atualizadas:

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

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

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

...

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

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

Você também deve filtrar a inserção de dados do usuário ao chamar ContentResolver.update(). Para saber mais sobre isso, leia a seção Proteção contra inserções mal-intencionadas.

Exclusão de dados

Excluir linhas é semelhante a recuperar dados de linhas: você especifica critérios de seleção para as linhas que deseja excluir e o método cliente retorna o número de linhas excluídas. O snippet a seguir exclui linhas cujo appid (id do aplicativo) corresponda a "usuário". O método retorna o número de linhas excluídas.


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

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

...

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

Você também deve filtrar a inserção de dados do usuário ao chamar ContentResolver.delete(). Para saber mais sobre isso, leia a seção Proteção contra inserções 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 oferece somente texto, mas provedores também podem oferecer os seguintes formatos:

  • número inteiro
  • número inteiro longo (longo)
  • ponto flutuante
  • ponto flutuante longo (duplo)

Outros tipos de dados que provedores usam com frequência são objetos binários largos (BLOB) implementados como uma matriz de byte de 64 kB. É possível ver os tipos de dados disponíveis consultando 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 do Provedor de Dicionário do Usuário são listados na documentação de referência para sua classe de contrato UserDictionary.Words (classes de contrato são descritas 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 MIME de dados para cada URI de conteúdo que definem. É possível usar as informações do tipo MIME para descobrir se o aplicativo pode processar dados que o provedor fornece ou para escolher um tipo de processamento com base no tipo MIME. Normalmente é necessário ter o tipo MIME ao trabalhar com um provedor que contenha estruturas complexas 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 obter o tipo MIME correspondente a uma URI de conteúdo, chame ContentResolver.getType().

A seção Referência de tipo MIME descreve 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: É possível criar um lote de chamadas de acesso com métodos na classe ContentProviderOperation e, em seguida, aplicá-los com ContentResolver.applyBatch().
  • Consultas assíncronas: Devem-se realizar consultas em um encadeamento separado. Um modo de fazer isso é usar um objeto CursorLoader. Os exemplos no guia Carregadores demonstram como fazer isso.
  • Acesso a dados via intents Embora não seja possível enviar um intent diretamente a um provedor, é possível enviar um intent ao aplicativo do provedor, que normalmente é o recomendável para modificar os dados do provedor.

O acesso em lote e a modificação via intents são descritos nas seções a seguir.

Acesso em lote

O acesso em lote a um provedor é útil para inserir uma grande quantidade de linhas ou para inserir linhas em diversas tabelas na mesma chamada de método ou, em geral, para realizar um conjunto de operações entre limites de processo como uma transação (uma operação atômica).

Para acessar um provedor em "modo de lote", você cria uma matriz de objetos ContentProviderOperation e, em seguida, despacha-os a um provedor de conteúdo com ContentResolver.applyBatch(). Passe a autoridade do provedor de conteúdo para esse método em vez de para um URI de conteúdo específico. Isso permite que cada objeto ContentProviderOperation da matriz trabalhe com uma tabela diferente. 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. O aplicativo de exemplo do Gerenciador de contatos contém um exemplo de acesso em lote em seu arquivo de origem ContactAdder.java.

Acesso a dados via intents

Intents podem fornecer acesso indireto a um provedor de conteúdo. Basta permitir que o usuário acesse dados em um provedor mesmo se o aplicativo não tiver permissões de acesso, tanto retornando um intent de resultado de um aplicativo que tem permissões quanto ativando um aplicativo que tem permissões e permitindo que o usuário trabalhe nele.

Obtenção de acesso com permissões temporárias

É possível acessar dados em um provedor de conteúdo, mesmo sem as permissões de acesso adequadas. Basta enviar um intent a um aplicativo que tenha as permissões e receber de volta um intent de resultado contendo permissões da "URI". Essas são permissões para uma URI de conteúdo específica que duram enquanto durar a atividade que as recebeu. O aplicativo que tem permissões permanentes concedem permissões temporárias ativando um sinalizador no intent de resultado:

Observação: Esses sinalizadores não fornecem acesso geral de leitura ou gravação ao provedor cuja autoridade está contida na URI de conteúdo. O acesso é apenas para a URI propriamente dita.

Um provedor define permissões de URI para URIs de conteúdo no manifesto usando o atributo android:grantUriPermission do elemento <provider>, bem como o elemento secundário <grant-uri-permission> do elemento <provider> . O mecanismo das permissões de URI é explicado com mais detalhes no guia Permissões e segurança, na seção "Permissões da URI".

Por exemplo, é possível recuperar dados de um contato no Provedor de Contatos, mesmo sem a permissão READ_CONTACTS. Você pode desejar fazer isso em um aplicativo que envie e-mails de parabenização a um contato no aniversário dele. Em vez de solicitar READ_CONTACTS, que fornece acesso a todos os contatos do usuário e suas informações, é preferível deixar que o usuário controle quais contatos serão usados pelo seu aplicativo. Para isso, use o processo a seguir:

  1. O aplicativo envia um intent contendo a ação ACTION_PICK e o tipo MIME CONTENT_ITEM_TYPE dos "contatos" usando o método startActivityForResult().
  2. Como esse intent corresponde ao filtro de intents da atividade de "seleção" do aplicativo Pessoas, a atividade ficará em primeiro plano.
  3. Na atividade de seleção, o usuário seleciona um contato para atualizar. Quando isso acontece, a atividade de seleção chama setResult(resultcode, intent) para configurar um intent para retornar ao aplicativo. O intent contém a URI de conteúdo do contato que o usuário selecionou e os sinalizadores "extras" FLAG_GRANT_READ_URI_PERMISSION. Esses sinalizadores concedem permissão de URI para que o aplicativo leia dados do contato para o qual a URI de conteúdo aponta. A atividade de seleção, em seguida, chama finish() para retornar o controle para o aplicativo.
  4. A atividade retorna ao primeiro plano e o sistema chama o método onActivityResult() da sua atividade. Esse método recebe o intent de resultado criada pela atividade de seleção no aplicativo Pessoas.
  5. Com a URI de conteúdo do intent de resultado, é possível ler os dados do contato do Provedor de Contatos, mesmo que não tenha solicitado permissão permanente de acesso de leitura para o provedor no manifesto. Pode-se, então, pode obter as informações de data de nascimento do contato ou o endereço de e-mail e, assim, enviar um e-mail de parabenização.

Uso de outro aplicativo

Um modo simples que permite ao usuário modificar dados para os quais você não tem permissões de acesso é ativar um aplicativo que tenha permissões e deixar o usuário fazer o resto.

Por exemplo, o aplicativo Agenda aceita um intent ACTION_INSERT, que permite ativar a IU de inserção do aplicativo. Você pode passar dados "extras" nesse intent, que o aplicativo usa para pré-preencher a IU. Como os eventos recorrentes têm sintaxe complexa, o modo preferencial de inserir eventos no Provedor de Agenda é ativar o aplicativo Agenda com um ACTION_INSERT e, em seguida, deixar o usuário inserir o evento.

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 de intents e outros recursos de um provedor de conteúdo. Elas não estão automaticamente inclusas em um provedor; o desenvolvedor do provedor deve defini-las e disponibilizá-las a outros desenvolvedores. Muitos dos provedores incluídos na plataforma do 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 de "palavras" é definida 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 como:

String[] mProjection =
{
    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 desta classe contém snippets de código de exemplo. Uma das suas subclasses, ContactsContract.Intents.Insert, é uma classe de contrato que contém constantes para intents e dados do intent.

Referência do tipo MIME

Provedores de conteúdo podem retornar tipos MIME de mídia, strings de tipos MIME personalizados ou ambos.

Tipos MIME têm o 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 uma URI, uma consulta usando essa URI retornará um texto que contém tags HTML.

Strings de tipo MIME personalizado, também chamadas de tipos MIME "específicos do fornecedor", têm mais valores de tipo e subtipo complexos. O valor de tipo é sempre

vnd.android.cursor.dir

para diversas linhas ou

vnd.android.cursor.item

para uma única linha.

O subtipo é específico do provedor. Os provedores embutidos do Android normalmente têm um subtipo simples. Por exemplo, quando o aplicativo Contatos cria uma linha para um número de telefone, ele configura o seguinte tipo MIME na linha:

vnd.android.cursor.item/phone_v2

Observe que o valor do subtipo é simplesmente phone_v2.

Outros desenvolvedores de provedor podem criar os próprios padrões de subtipos com base na autoridade do provedor e nos nomes da tabela. Por exemplo, considere um provedor que contenha horários de trens. A autoridade do provedor é com.example.trains e ele contém as tabelas Linha1, Linha2 e Linha3. Em resposta à URI de conteúdo

content://com.example.trains/Line1

para a tabela Linha1, o provedor retorna o tipo MIME

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

Em resposta à URI de conteúdo

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

da linha 5 na tabela Linha2, o provedor retorna o tipo MIME

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

A maioria dos provedores 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 de linhas exclusivas são descritas na seção URIs de conteúdo.