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 para Android, que, em geral, fornece a própria IU para trabalhar com os dados. No entanto, os provedores de conteúdo são usados principalmente por outros aplicativos, que acessam o provedor usando um objeto cliente do provedor. Juntos, provedores e clientes do provedor oferecem uma interface padronizada e consistente para dados que também lida com a comunicação entre processos e com o acesso seguro aos dados.

Em geral, os provedores de conteúdo são usados em um destes dois cenários: quando você quer implementar um código para acessar um provedor de conteúdo atual em outro aplicativo ou quando quer criar um provedor no seu aplicativo para compartilhar dados com outros aplicativos.

Esta página aborda os fundamentos do trabalho com provedores de conteúdo existentes. Para saber mais sobre a implementação de 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 seu aplicativo para diversos componentes e APIs diferentes. Como ilustrado na Figura 1, eles incluem:

  • 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.

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 do provedor, uma instância de uma das subclasses concretas de ContentProvider. Os métodos ContentResolver fornecem as funções básicas "CRUD" (criar, recuperar, atualizar e excluir) do armazenamento persistente.

Um padrão comum de acesso ao ContentProvider a partir da IU é usar um CursorLoader para executar uma consulta assíncrona em segundo plano. O Activity ou Fragment na sua interface chama um CursorLoader para a consulta, que, por sua vez, recebe o ContentProvider usando o ContentResolver.

Isso permite que a IU continue disponível ao usuário durante a execução da consulta. 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.

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 na seção Permissões do provedor de conteúdo.

Um dos provedores integrados na plataforma do Android é o provedor de dicionário do usuário, que armazena as palavras não padrão que o usuário quer 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 está presente em um dicionário padrão. Cada coluna representa um conjunto de 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. Portanto, para se referir à localidade de uma linha, por exemplo, consulte a coluna locale. Para esse provedor, a coluna _ID serve como uma coluna de chave primária que o provedor mantém 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 de query(Uri,projection,selection,selectionArgs,sortOrder) correspondem 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 mapeia para a tabela no provedor chamado table_name.
projection col,col,col,... projection é uma matriz de colunas incluída para cada linha recuperada.
selection WHERE col = value selection especifica os critérios para selecionar linhas.
selectionArgs Não há equivalente exato. Argumentos de seleção substituem marcadores ? 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, 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 da tabela Words do Provedor de dicionário do usuário. O objeto ContentResolver analisa a autoridade do URI e a usa para resolver o provedor, comparando a autoridade a uma tabela de provedores conhecidos do sistema. O ContentResolver pode enviar os argumentos da consulta ao provedor correto.

O ContentProvider usa o caminho que é parte do URI de conteúdo para escolher a tabela que será acessada. Os provedores normalmente 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);

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 de conveniência para construir objetos URI bem formados a partir de strings. A classe ContentUris contém métodos convenientes para anexar valores de ID a um URI. O snippet anterior usa withAppendedId() para anexar um ID ao URI de conteúdo do provedor de dicionário do usuário.

Extrair 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() na linha de execução da interface. No código real, no entanto, faça consultas de forma assíncrona em uma linha de execução separada. É possível usar a classe CursorLoader, descrita com mais detalhes no guia Carregadores. Além disso, as linhas de código são somente snippets. Elas não demonstram um aplicativo completo.

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

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

Solicitar permissão de acesso de leitura

Para recuperar dados de um provedor, o aplicativo precisa da permissão de acesso de leitura para o provedor. Não é possível solicitar essa permissão no momento da execução. Em vez disso, você precisa especificar que precisa dessa permissão no manifesto usando o elemento <uses-permission> e o nome da permissão exata definida pelo provedor.

Ao especificar esse elemento no manifesto, você está solicitando essa permissão para o 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 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, 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 cliente do provedor é semelhante a uma consulta SQL e contém um conjunto de colunas a retornar, 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 de colunas e valores. A variável é mSelectionClause. Se você especificar o parâmetro substituível ? em vez de um valor, o método de consulta vai extrair 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 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 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.

Proteger contra entradas mal-intencionadas

Se os dados gerenciados pelo provedor de conteúdo estiverem em um banco de dados SQL, a inclusão de dados externos não confiáveis em instruções SQL brutas poderá 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 você fizer isso, permitirá que o usuário concatene SQL maliciosos na sua instrução SQL. Por exemplo, o usuário pode inserir "nothing; DROP TABLE *;" para mUserInput, o que resulta na cláusula de seleção var = nothing; DROP TABLE *;.

Como a 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 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. Dessa forma, a entrada do usuário é vinculada 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 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 não se baseie em um banco de dados SQL.

Mostrar 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.

Usando métodos de Cursor, é possível iterar 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 automaticamente o objeto quando os dados do provedor mudam, acionam métodos em um objeto observador quando o Cursor muda ou realizam ambas as ações.

Observação:um provedor pode restringir o acesso a colunas com base na natureza do objeto que faz a consulta. Por exemplo, o Provedor de contatos restringe o acesso de algumas colunas a adaptadores de sincronização, para não retorná-las a uma atividade ou um serviço.

Se nenhuma linha atender aos critérios de seleção, o provedor vai retornar um objeto Cursor para o qual Cursor.getCount() será 0, ou seja, um cursor vazio.

Se ocorrer um erro interno, os resultados da consulta dependerão do provedor específico. Ele pode retornar null ou gerar uma Exception.

Como Cursor é uma lista de linhas, um bom modo de mostrar o conteúdo de um Cursor é vincular a uma ListView usando 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 define esse objeto para ser o adaptador de uma 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 da tabela 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 tabela.

Conseguir dados dos resultados da consulta

Além de mostrar os resultados da consulta, você pode usá-los para outras tarefas. Por exemplo, você pode recuperar grafias do Provedor de dicionário do usuário e, em seguida, procurá-las em 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.

Liberar recursos de resultados da consulta

Os objetos Cursor precisam ser fechados se não forem mais necessários para que os recursos associados a eles sejam liberados mais rapidamente. Isso pode ser feito chamando close() ou usando uma instrução try-with-resources na linguagem de programação Java ou a função use() na linguagem de programação Kotlin.

Permissões do provedor de conteúdo

O aplicativo de um provedor pode especificar as permissões que outros aplicativos precisam ter para acessar os dados do provedor. Essas permissões informam ao usuário quais dados 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, outros aplicativos não terão acesso aos dados do provedor, a menos que ele seja exportado. Além disso, os componentes no aplicativo do provedor sempre têm acesso total de leitura e gravação, independentemente das permissões especificadas.

O Provedor de dicionário do usuário precisa da permissão android.permission.READ_USER_DICTIONARY para recuperar dados dele. O provedor tem uma permissão android.permission.WRITE_USER_DICTIONARY separada para inserção, atualização ou exclusão de 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 Android Package Manager instala o aplicativo, o usuário precisa aprovar todas as permissões que o aplicativo solicita. Se o usuário aprovar, o gerenciador de pacotes continuará a instalação. Se o usuário não as aprovar, o Package Manager interromperá a instalação.

O exemplo de 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 nas 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 provedor e o cliente do provedor processam automaticamente 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 ela é 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 os valores atualizados, como se faz com uma inserção, e com critérios de seleção, como se faz 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 muda todas as linhas cuja localidade tem o idioma "en" para ter uma 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
);

Limpe a entrada do usuário ao chamar ContentResolver.update(). Para saber mais sobre isso, leia a seção Proteção contra entradas mal-intencionadas.

Excluir dados

Excluir linhas é semelhante a recuperar dados de linhas. Você especifica critérios de seleção para as linhas que quer excluir, e o método cliente retorna o número de linhas excluídas. O snippet a seguir exclui linhas em que o 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
);

Filtre a entrada do usuário ao chamar ContentResolver.delete(). Para saber mais sobre isso, leia a seção Proteção 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)

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. 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 do Provedor de dicionário do usuário estão listados na documentação de referência da classe de contrato correspondente, UserDictionary.Words. As 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 de dados MIME 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 oferece ou para escolher um tipo de processamento com base no tipo MIME. Normalmente, o tipo MIME é necessário 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 acessar o tipo MIME correspondente a um URI de conteúdo, chame ContentResolver.getType().

A seção Referência do 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:

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, para inserir linhas em várias tabelas na mesma chamada de método e, em geral, para realizar um conjunto de operações entre limites de processo como uma transação, chamada de operação atômica.

Para acessar um provedor em modo de lote, crie uma matriz de objetos ContentProviderOperation e, em seguida, enviá-los a um provedor de conteúdo com ContentResolver.applyBatch(). Transmita a autoridade do provedor de conteúdo para esse método, e não para um URI de conteúdo específico.

Isso permite que cada objeto ContentProviderOperation na matriz trabalhe em 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.

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 o aplicativo não tenha permissões de acesso, retornando uma intent de resultado de um aplicativo que tenha permissões ou ativando um aplicativo que tenha permissões e permitindo que o usuário trabalhe nele.

Ter acesso com permissões temporárias

É possível acessar dados em um provedor de conteúdo, mesmo que você não tenha as permissões de acesso adequadas. Para isso, envie uma intent a um app que tenha as permissões e receba uma intent de resultado com 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:

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 uma dessas sinalizações. As flags oferecem os seguintes recursos a qualquer app que recebe uma intent e é direcionado ao Android 11 (nível 30 da API) ou mais recente:

  • Ler ou gravar os dados que o URI de conteúdo representa, dependendo da flag incluída na intent.
  • Ganhar visibilidade do pacote no app que contém o provedor de conteúdo correspondente à autoridade de URI. O app que envia a intent e o que contém o provedor de conteúdo podem ser dois apps diferentes.

Um provedor define permissões de URI para URIs de conteúdo no manifesto usando o atributo android:grantUriPermissions do elemento <provider>, bem como o elemento filho <grant-uri-permission> do elemento <provider>. O mecanismo das permissões de URI é explicado com mais detalhes no 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 solicitar READ_CONTACTS, que fornece acesso a todos os contatos do usuário e às informações deles, é preferível deixar que o usuário controle quais contatos seu aplicativo usa. Para isso, use o seguinte processo:

  1. No app, envie uma intent que contenha a ação ACTION_PICK e o tipo MIME "contacts" CONTENT_ITEM_TYPE, usando o método startActivityForResult().
  2. Como essa intent corresponde ao filtro de intents da atividade "seleção" do app Pessoas, a atividade fica em primeiro plano.
  3. 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 que o usuário selecionou e as flags "extras" FLAG_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, chama finish() para retornar o controle ao aplicativo.
  4. 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.
  5. 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. Dessa forma, é possível acessar as informações de data de nascimento ou o endereço de e-mail do contato e enviar um e-mail de parabenização.

Usar outro aplicativo

Outra maneira de permitir que o usuário modifique 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 uma intent ACTION_INSERT que permite ativar a interface de inserção do aplicativo. Você pode transmitir dados "extras" nessa intent, que o aplicativo usa para pré-preencher a interface. Como os eventos recorrentes têm uma sintaxe complexa, a maneira preferencial de inserir eventos no Provedor de agenda é ativar o app Agenda com um ACTION_INSERT e deixar o usuário inserir o evento lá.

Mostrar dados usando um app auxiliar

Se o aplicativo tiver permissões de acesso, ainda será possível usar uma intent para mostrar dados em outro aplicativo. Por exemplo, o app Agenda aceita um intent ACTION_VIEW que exibe uma data ou um evento específico. Isso permite a exibição de informações da agenda sem precisar criar a própria IU. Para saber mais sobre esse recurso, consulte a Visão geral do provedor de agenda.

O aplicativo para o qual você enviou uma intent não precisa ser o aplicativo associado 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ídas automaticamente em um provedor. O desenvolvedor do provedor precisa defini-los e disponibilizá-los 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. O 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 exemplos de snippets neste guia. Por exemplo, uma projeção de consulta pode ser definida da seguinte maneira:

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 de tipo MIME

Os provedores de conteúdo podem retornar tipos MIME de mídia 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, uma consulta que usar esse URI retornará um texto que contém tags HTML.

As strings de tipos MIME personalizados, também chamadas de tipos MIME específicos do fornecedor, têm valores type e subtype mais 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 o seguinte:

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 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 Line1, Line2 e Line3. Em resposta ao seguinte URI de conteúdo para a linha 1 da tabela:

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 "Linha2":

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 de linhas únicas são descritos na seção URIs de conteúdo.