O provedor de conteúdo gerencia o acesso a um repositório central de dados. Implemente um
provedor como uma ou mais classes em um aplicativo Android, junto com elementos no
arquivo de manifesto. Uma das classes implementa uma subclasse de
ContentProvider
, que é a interface entre seu provedor e
outros aplicativos.
Embora os provedores de conteúdo se destinem a disponibilizar dados para outros aplicativos, é possível ter atividades no aplicativo que permitam que o usuário consulte e modifique os dados gerenciados pelo provedor.
Esta página contém o processo básico para criar um provedor de conteúdo e uma lista de APIs a serem usadas.
Antes de começar a criar
Antes de começar a criar um provedor, considere o seguinte:
-
Decida se você precisa de um provedor de conteúdo. Você precisa criar um provedor de conteúdo se quiser fornecer um ou mais dos seguintes recursos:
- Oferecer dados ou arquivos complexos a outros aplicativos.
- Você quer permitir que os usuários copiem dados complexos do seu app para outros.
- Fornecer sugestões de pesquisa personalizada usando a biblioteca de pesquisa.
- Expor os dados do aplicativo a widgets.
- Você quer implementar as classes
AbstractThreadedSyncAdapter
,CursorAdapter
ouCursorLoader
.
Você não precisa de um provedor para usar bancos de dados ou outros tipos de armazenamento permanente se o uso for inteiramente dentro do seu próprio aplicativo e você não precisar de nenhum dos recursos anteriores listados. Em vez disso, é possível usar um dos sistemas de armazenamento descritos em Visão geral do armazenamento de dados e arquivos.
- Se você ainda não tiver feito isso, leia Fundamentos do provedor de conteúdo para saber mais sobre provedores e como eles funcionam.
A seguir, siga estas etapas para criar seu provedor:
-
Projete o armazenamento bruto dos dados. Um provedor de conteúdo oferece dados de duas maneiras:
- Dados de arquivo
- Dados que normalmente entram em arquivos, como fotos, áudio ou vídeos. Armazene os arquivos no espaço privado do seu aplicativo. Em resposta a uma solicitação de um arquivo por outro aplicativo, seu provedor pode oferecer um identificador para o arquivo.
- Dados "estruturados"
- Dados que normalmente entram em um banco de dados, uma matriz ou uma estrutura semelhante. Armazene os dados de forma compatível com tabelas de linhas e colunas. Cada linha representa uma entidade, como uma pessoa ou um item em um inventário. Uma coluna representa alguns dados da entidade, como o nome de uma pessoa ou o preço de um item. Uma maneira comum de armazenar esse tipo de dados é em um banco de dados SQLite, mas é possível usar qualquer tipo de armazenamento persistente. Para saber mais sobre os tipos de armazenamento disponíveis no sistema Android, consulte a seção Projetar armazenamento de dados.
-
Defina uma implementação concreta da classe
ContentProvider
e dos métodos obrigatórios. Essa classe é a interface entre seus dados e o restante do sistema Android. Para saber mais sobre essa classe, consulte a seção Implementar a classe ContentProvider. - Defina a string de autoridade do provedor, URIs de conteúdo e nomes de coluna. Se você quiser que o aplicativo do provedor processe intents, defina também ações de intent, dados extras e flags. Defina também as permissões necessárias para os aplicativos que querem acessar seus dados. Considere definir todos esses valores como constantes em uma classe de contrato separada. Mais tarde, você pode expor essa classe a outros desenvolvedores. Para saber mais sobre URIs de conteúdo, consulte a seção Projetar URIs de conteúdo. Para saber mais sobre intents, consulte a seção Intents e acesso a dados.
-
Adicione outras partes opcionais, como dados de amostra ou uma implementação
de
AbstractThreadedSyncAdapter
que possa sincronizar dados entre o provedor e os dados baseados na nuvem.
Armazenamento de dados de design
Os provedores de conteúdo são a interface para dados salvos em um formato estruturado. Antes de criar a interface, decida como armazenar os dados. É possível armazenar os dados da forma que quiser e, em seguida, projetar a interface para ler e gravar os dados conforme necessário.
Estas são algumas das tecnologias de armazenamento de dados disponíveis no Android:
- Se você estiver trabalhando com dados estruturados, considere usar um banco de dados relacional, como o SQLite, ou um armazenamento de dados de chave-valor não relacional, como o LevelDB. Se você estiver trabalhando com dados não estruturados, como áudio, imagem ou vídeo, considere armazenar os dados como arquivos. É possível misturar e combinar vários tipos diferentes de armazenamento e expô-los usando um único provedor de conteúdo, se necessário.
-
O sistema Android pode interagir com a biblioteca de persistência Room, que fornece acesso à API de banco de dados SQLite que os provedores do Android usam para armazenar dados orientados por tabelas. Para criar um banco de dados usando essa
biblioteca, instancie uma subclasse de
RoomDatabase
, conforme descrito em Salvar dados em um banco de dados local usando Room.Não é necessário usar um banco de dados para implementar seu repositório. Um provedor aparece externamente como um conjunto de tabelas, semelhante a um banco de dados relacional, mas isso não é um requisito para a implementação interna do provedor.
- Para armazenar dados de arquivos, o Android tem diversas APIs orientadas a arquivo. Para saber mais sobre armazenamento de arquivos, leia a Visão geral de armazenamento de dados e arquivos. Se você estiver projetando um provedor que oferece dados relacionados a mídia, como música ou vídeos, é possível ter um provedor que combine dados de tabela e arquivos.
- Em casos raros, você pode se beneficiar da implementação de mais de um provedor de conteúdo para um único aplicativo. Por exemplo, você pode querer compartilhar alguns dados com um widget usando um provedor de conteúdo e expor um conjunto de dados diferente para compartilhamento com outros aplicativos.
-
Para trabalhar com dados baseados em rede, use classes em
java.net
eandroid.net
. Também é possível sincronizar dados em rede com um repositório de dados local, como um banco de dados, e, em seguida, oferecer os dados como tabelas ou arquivos.
Observação: se você fizer uma alteração no seu repositório que não seja compatível com versões anteriores, será necessário marcá-lo com um novo número de versão. Também é necessário aumentar o número da versão do app que implementa o novo provedor de conteúdo. Essa mudança evita que downgrades do sistema causem falhas ao tentar reinstalar um app que tenha um provedor de conteúdo incompatível.
Considerações para projetar dados
Veja a seguir algumas dicas para projetar a estrutura de dados do seu provedor:
-
Os dados da tabela precisam sempre ter uma coluna de "chave primária" que o provedor mantém
como um valor numérico exclusivo para cada linha. É possível usar esse valor para vincular a linha a linhas relacionadas em outras tabelas (usando-o como uma "chave estrangeira"). Embora você possa usar qualquer nome
para essa coluna, o uso de
BaseColumns._ID
é a melhor escolha, porque vincular os resultados de uma consulta de provedor a umaListView
exige que uma das colunas recuperadas tenha o nome_ID
. -
Se você quiser fornecer imagens bitmap ou outras partes muito grandes de dados orientados a arquivos, armazene os dados em um arquivo e o forneça indiretamente em vez de armazená-lo diretamente em uma tabela. Se fizer isso, será necessário informar aos usuários do provedor que eles precisam usar um
método de arquivo
ContentResolver
para acessar os dados. -
Use o tipo de dados de objeto binário grande (BLOB, na sigla em inglês) para armazenar dados que variam em tamanho ou que têm uma
estrutura variável. Por exemplo, é possível usar uma coluna BLOB para armazenar um buffer de protocolo ou estrutura JSON.
Também é possível usar um BLOB para implementar uma tabela independente de esquema. Nesse tipo de tabela, você define uma coluna de chave primária, uma coluna de tipo MIME e uma ou mais colunas genéricas como BLOB. O significado dos dados nas colunas BLOB é indicado pelo valor na coluna de tipo MIME. Isso permite armazenar diferentes tipos de linha na mesma tabela. A tabela de "dados"
ContactsContract.Data
do Provedor de contatos é um exemplo de tabela independente de esquema.
Projetar 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 ou arquivo (um caminho). Parte do ID opcional aponta para
uma linha individual em uma tabela. Cada método de acesso a dados de
ContentProvider
tem um URI de conteúdo como argumento. Isso permite
determinar a tabela, linha ou arquivo a ser acessado.
Para saber mais sobre URIs de conteúdo, consulte Fundamentos do provedor de conteúdo.
Criar uma autoridade
Os provedores normalmente têm uma única autoridade, que serve como seu nome interno do Android. Para evitar conflitos com outros provedores, use a propriedade do domínio da Internet (ao contrário) como base da autoridade do provedor. Como essa recomendação também é verdadeira para nomes de pacotes do Android, é possível definir a autoridade do provedor como uma extensão do nome do pacote que o contém.
Por exemplo, se o nome do pacote Android for
com.example.<appname>
, atribua ao provedor a
autoridade com.example.<appname>.provider
.
Projetar uma estrutura de caminho
Os desenvolvedores geralmente criam URIs de conteúdo a partir da autoridade anexando caminhos que apontam para tabelas individuais. Por exemplo, se você tiver duas tabelas, table1 e
table2, combine-as com a autoridade do exemplo anterior para produzir os
URIs de conteúdo
com.example.<appname>.provider/table1
e
com.example.<appname>.provider/table2
. Os caminhos não se limitam a um único segmento, e não precisam existir uma tabela para cada nível do caminho.
Processar IDs de URI de conteúdo
Por convenção, os provedores oferecem acesso a uma única linha em uma tabela ao aceitar um URI de conteúdo
com um valor de ID para a linha no final do URI. Também por convenção, os provedores fazem a correspondência do valor do ID com a coluna _ID
da tabela e realizam o acesso solicitado na linha correspondente.
Essa convenção facilita um padrão comum de projeto para aplicativos que acessam um provedor. O app
faz uma consulta no provedor e exibe o Cursor
resultante
em uma ListView
usando um CursorAdapter
.
A definição de CursorAdapter
requer que uma das colunas no Cursor
seja _ID
Em seguida, o usuário escolhe uma das linhas mostradas na interface para analisar ou modificar os
dados. O app recebe a linha correspondente do Cursor
que oferece suporte ao
ListView
, recebe o valor _ID
para essa linha, anexa essa linha ao
URI de conteúdo e envia a solicitação de acesso ao provedor. O provedor pode fazer a
consulta ou modificação na linha exata que o usuário escolheu.
Padrões de URI de conteúdo
Para ajudar você a escolher qual ação realizar para um URI de conteúdo de entrada, a API do provedor inclui
a classe de conveniência UriMatcher
, que mapeia padrões de URI de conteúdo para
valores inteiros. É possível usar os valores inteiros em uma instrução switch
que
escolha a ação desejada para o URI de conteúdo ou para URIs que correspondam a um padrão específico.
Um padrão de URI de conteúdo corresponde a URIs de conteúdo que usam caracteres curinga:
-
*
corresponde a uma string com qualquer caractere válido de qualquer comprimento. -
#
corresponde a uma string de caracteres numéricos de qualquer comprimento.
Como um exemplo de design e codificação do processamento de URI de conteúdo, considere um provedor com a autoridade com.example.app.provider
que reconheça os seguintes URIs de conteúdo que apontam para tabelas:
-
content://com.example.app.provider/table1
: uma tabela com o nometable1
. -
content://com.example.app.provider/table2/dataset1
: uma tabela chamadadataset1
. -
content://com.example.app.provider/table2/dataset2
: uma tabela chamadadataset2
. -
content://com.example.app.provider/table3
: uma tabela com o nometable3
.
O provedor também reconhece esses URIs de conteúdo se eles tiverem um ID de linha anexado, como content://com.example.app.provider/table3/1
para a linha identificada por
1
em table3
.
Estes são os padrões de URI de conteúdo possíveis:
-
content://com.example.app.provider/*
- Corresponde a qualquer URI de conteúdo no provedor.
-
content://com.example.app.provider/table2/*
-
Corresponde a um URI de conteúdo das tabelas
dataset1
edataset2
, mas não corresponde a URIs de conteúdo detable1
outable3
. -
content://com.example.app.provider/table3/#
-
Corresponde a um URI de conteúdo para linhas únicas em
table3
, comocontent://com.example.app.provider/table3/6
para a linha identificada por6
.
O snippet de código abaixo mostra como os métodos em UriMatcher
funcionam.
Esse código lida com URIs de uma tabela inteira, de maneira diferente dos URIs de uma
única linha, usando o padrão de URI de conteúdo
content://<authority>/<path>
para tabelas e
content://<authority>/<path>/<id>
para linhas únicas.
O método addURI()
mapeia uma
autoridade e um caminho para um valor inteiro. O método match()
retorna o valor inteiro de um URI. Uma instrução switch
escolhe entre consultar a tabela inteira ou um único registro.
Kotlin
private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { /* * The calls to addURI() go here for all the content URI patterns that the provider * recognizes. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path. */ addURI("com.example.app.provider", "table3", 1) /* * Sets the code for a single row to 2. In this case, the # wildcard is * used. content://com.example.app.provider/table3/3 matches, but * content://com.example.app.provider/table3 doesn't. */ addURI("com.example.app.provider", "table3/#", 2) } ... class ExampleProvider : ContentProvider() { ... // Implements ContentProvider.query() override fun query( uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { var localSortOrder: String = sortOrder ?: "" var localSelection: String = selection ?: "" when (sUriMatcher.match(uri)) { 1 -> { // If the incoming URI was for all of table3 if (localSortOrder.isEmpty()) { localSortOrder = "_ID ASC" } } 2 -> { // If the incoming URI was for a single row /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query. */ localSelection += "_ID ${uri?.lastPathSegment}" } else -> { // If the URI isn't recognized, // do some error handling here } } // Call the code to actually do the query } }
Java
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { /* * The calls to addURI() go here for all the content URI patterns that the provider * recognizes. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to one. No wildcard is used * in the path. */ uriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the # wildcard is * used. content://com.example.app.provider/table3/3 matches, but * content://com.example.app.provider/table3 doesn't. */ uriMatcher.addURI("com.example.app.provider", "table3/#", 2); } ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (uriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query. */ selection = selection + "_ID = " + uri.getLastPathSegment(); break; default: ... // If the URI isn't recognized, do some error handling here } // Call the code to actually do the query }
Outra classe, ContentUris
, oferece métodos de conveniência para trabalhar com a parte id
dos URIs de conteúdo. As classes Uri
e Uri.Builder
incluem métodos convenientes para analisar objetos Uri
existentes e criar novos.
Implementar a classe ContentProvider
A instância ContentProvider
gerencia o acesso
a um conjunto estruturado de dados ao processar solicitações de outros aplicativos. Todas as formas de acesso chamam ContentResolver
, que chama um método concreto de ContentProvider
para ter acesso.
Métodos obrigatórios
A classe abstrata ContentProvider
define seis métodos abstratos que
você implementa como parte da subclasse concreta. Todos esses métodos, exceto onCreate()
, são chamados por um aplicativo cliente que está tentando acessar seu provedor de conteúdo.
-
query()
-
Recupere os dados do provedor. Use os argumentos para selecionar a tabela a ser consultada, as linhas e colunas a serem retornadas e a ordem de classificação do resultado.
Retorne os dados como um objeto
Cursor
. -
insert()
- Insira uma nova linha no provedor. Use os argumentos para selecionar a tabela de destino e receber os valores de coluna a serem usados. Retorne um URI de conteúdo para a linha recém-inserida.
-
update()
- Atualize as linhas no provedor. Use os argumentos para selecionar a tabela e as linhas para atualizar e receber os valores de coluna atualizados. Retorne o número de linhas atualizadas.
-
delete()
- Exclua linhas do provedor. Use os argumentos para selecionar a tabela e as linhas a serem excluídas. Retorne o número de linhas excluídas.
-
getType()
- Retorna o tipo MIME correspondente a um URI de conteúdo. Esse método é descrito com mais detalhes na seção Implementar tipos MIME do provedor de conteúdo.
-
onCreate()
-
Inicialize seu provedor. O sistema Android chama esse método imediatamente após
criar seu provedor. O provedor não é criado até que um
objeto
ContentResolver
tente acessá-lo.
Esses métodos têm a mesma assinatura que os métodos ContentResolver
com nomes idênticos.
A implementação desses métodos precisa considerar o seguinte:
-
Todos esses métodos, exceto
onCreate()
, podem ser chamados por várias linhas de execução ao mesmo tempo. Portanto, eles precisam ser seguros para linha de execução. Para saber mais sobre várias linhas de execução, consulte Visão geral dos processos e linhas de execução. -
Evite realizar longas operações no
onCreate()
. Adie tarefas de inicialização até que sejam realmente necessárias. A seção sobre a implementação do método onCreate() discute isso com mais detalhes. -
Embora você precise implementar esses métodos, seu código não precisa fazer nada além
de retornar o tipo de dados esperado. Por exemplo, é possível evitar que outros aplicativos insiram dados em algumas tabelas ignorando a chamada para
insert()
e retornando 0.
Implementar o método query()
O método ContentProvider.query()
precisa retornar um objeto Cursor
ou, se falhar, gerar uma Exception
. Se você estiver usando um banco de dados SQLite como armazenamento
de dados, pode retornar o Cursor
retornado por um dos métodos
query()
da classe SQLiteDatabase
.
Se a consulta não corresponder a nenhuma linha, retorne uma instância Cursor
cujo método getCount()
retorne 0.
Retorne null
somente se ocorrer um erro interno durante o processo de consulta.
Se você não estiver usando um banco de dados SQLite como armazenamento de dados, use uma das subclasses concretas
de Cursor
. Por exemplo, a classe MatrixCursor
implementa um cursor em que cada linha é uma matriz de instâncias de Object
. Com essa classe,
use addRow()
para adicionar uma nova linha.
O sistema Android precisa ser capaz de comunicar o Exception
entre os limites do processo. O Android pode fazer isso para as exceções a seguir, que são úteis
para processar erros de consulta:
-
IllegalArgumentException
. Você pode optar por lançar isso se o provedor receber um URI de conteúdo inválido. -
NullPointerException
Implementar o método insert()
O método insert()
adiciona uma nova linha à tabela apropriada usando os valores no argumento ContentValues
. Se um nome de coluna não estiver no argumento ContentValues
, forneça um valor padrão para ele no código do provedor ou esquema
do banco de dados.
Esse método retorna o URI de conteúdo da nova linha. Para criar isso, anexe a chave primária da nova linha, normalmente o valor _ID
, ao URI de conteúdo da tabela usando withAppendedId()
.
Implementar o método delete()
O método delete()
não precisa excluir linhas do armazenamento de dados. Se você estiver usando um adaptador de sincronização
com o provedor, marque uma linha excluída
com uma sinalização "delete", em vez de removê-la completamente. O adaptador de sincronização pode
verificar se há linhas excluídas e removê-las do servidor antes de excluí-las do provedor.
Implementar o método update()
O método update()
usa o mesmo argumento ContentValues
usado por
insert()
e os mesmos
argumentos selection
e selectionArgs
usados por
delete()
e
ContentProvider.query()
.
Isso permite reutilizar o código entre esses métodos.
Implementar o método onCreate()
O sistema Android chama onCreate()
quando inicia o provedor. Realize apenas tarefas de inicialização
de execução rápida nesse método e adie a criação do banco de dados e o carregamento de dados até que o provedor
receba uma solicitação de dados. Se você fizer tarefas longas em
onCreate()
, a inicialização do
provedor vai atrasar. Por sua vez, isso diminui a resposta do provedor para outros
aplicativos.
Os dois snippets a seguir demonstram a interação entre
ContentProvider.onCreate()
e
Room.databaseBuilder()
. O primeiro
snippet mostra a implementação de
ContentProvider.onCreate()
, em que o
objeto de banco de dados é criado e os identificadores para os objetos de acesso a dados são criados:
Kotlin
// Defines the database name private const val DBNAME = "mydb" ... class ExampleProvider : ContentProvider() { // Defines a handle to the Room database private lateinit var appDatabase: AppDatabase // Defines a Data Access Object to perform the database operations private var userDao: UserDao? = null override fun onCreate(): Boolean { // Creates a new database object appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build() // Gets a Data Access Object to perform the database operations userDao = appDatabase.userDao return true } ... // Implements the provider's insert method override fun insert(uri: Uri, values: ContentValues?): Uri? { // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc. } }
Java
public class ExampleProvider extends ContentProvider // Defines a handle to the Room database private AppDatabase appDatabase; // Defines a Data Access Object to perform the database operations private UserDao userDao; // Defines the database name private static final String DBNAME = "mydb"; public boolean onCreate() { // Creates a new database object appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build(); // Gets a Data Access Object to perform the database operations userDao = appDatabase.getUserDao(); return true; } ... // Implements the provider's insert method public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc. } }
Implementar tipos MIME do ContentProvider
A classe ContentProvider
tem dois métodos para retornar tipos MIME:
-
getType()
- Um dos métodos obrigatórios que você implementa para qualquer provedor.
-
getStreamTypes()
- Um método que precisará ser implementado se o provedor oferecer arquivos.
Tipos MIME para tabelas
O método getType()
retorna um String
no formato MIME que descreve o tipo de dados retornado pelo argumento do URI de conteúdo. O argumento Uri
pode ser um padrão em vez de um URI específico.
Nesse caso, retorne o tipo de dados associados aos URIs de conteúdo que correspondem ao
padrão.
Para tipos de dados comuns, como texto, HTML ou JPEG, getType()
retorna o tipo MIME padrão desses dados. Uma lista completa dos tipos padrão está disponível no site Tipos de mídia MIME IANA.
Para URIs de conteúdo que apontam para uma ou linhas de dados de tabela,
getType()
retorna
um tipo MIME no formato MIME específico do fornecedor do Android:
-
Parte do tipo:
vnd
-
Parte do subtipo:
-
Se o padrão de URI for para uma única linha:
android.cursor.item/
-
Se o padrão de URI se referir a mais de uma linha:
android.cursor.dir/
-
Se o padrão de URI for para uma única linha:
-
Parte específica do provedor:
vnd.<name>
.<type>
Você fornece o
<name>
e o<type>
. O valor<name>
é globalmente exclusivo, e<type>
é exclusivo para o padrão de URI correspondente. Uma boa opção para<name>
é o nome da sua empresa ou alguma parte do nome do pacote Android do seu app. Uma boa opção para<type>
é uma string que identifique a tabela associada ao URI.
Por exemplo, se a autoridade de um provedor for
com.example.app.provider
e ele expuser uma tabela chamada
table1
, o tipo MIME de várias linhas em table1
será:
vnd.android.cursor.dir/vnd.com.example.provider.table1
Para uma única linha de table1
, o tipo MIME é:
vnd.android.cursor.item/vnd.com.example.provider.table1
Tipos MIME para arquivos
Caso seu provedor ofereça arquivos, implemente
getStreamTypes()
.
O método retorna uma matriz String
de tipos MIME para os arquivos que o provedor
pode retornar de um determinado URI de conteúdo. Filtre os tipos MIME oferecidos pelo argumento do filtro de tipo
MIME para retornar apenas os tipos MIME que o cliente quer processar.
Por exemplo, considere um provedor que oferece imagens de fotos como arquivos nos formatos JPG, PNG e GIF.
Se um aplicativo chamar ContentResolver.getStreamTypes()
com a string de filtro image/*
para algo que
seja uma "imagem",
o método ContentProvider.getStreamTypes()
retornará a matriz:
{ "image/jpeg", "image/png", "image/gif"}
Se o app estiver interessado apenas em arquivos JPG, ele poderá chamar
ContentResolver.getStreamTypes()
com a string de filtro *\/jpeg
, e
getStreamTypes()
retornará:
{"image/jpeg"}
Se o provedor não oferecer nenhum dos tipos MIME solicitados na string de filtro,
getStreamTypes()
vai retornar null
.
Implementar uma classe de contrato
Uma classe de contrato é uma classe public final
que contém definições de constantes para os
URIs, nomes de coluna, tipos MIME e outros metadados pertencentes ao provedor. A classe
estabelece um contrato entre o provedor e outros aplicativos, garantindo que o provedor
possa ser acessado corretamente, mesmo que haja mudanças nos valores reais de URIs, nomes de colunas
e assim por diante.
Uma classe de contrato também ajuda os desenvolvedores porque geralmente tem nomes mnemônicos para as constantes. Dessa forma, é menos provável que os desenvolvedores usem valores incorretos para nomes de colunas ou URIs. Por ser uma classe, ela pode conter documentação do Javadoc. Ambientes de desenvolvimento integrados, como o Android Studio, podem preencher automaticamente os nomes de constantes da classe de contrato e exibir um Javadoc para elas.
Os desenvolvedores não podem acessar o arquivo de classe da classe de contrato pelo aplicativo, mas podem compilá-lo estaticamente no aplicativo a partir de um arquivo JAR que você fornece.
A classe ContactsContract
e as classes aninhadas são exemplos de
classes de contrato.
Implementar permissões do provedor de conteúdo
As permissões e o acesso a todos os aspectos do sistema Android são descritos em detalhes em Dicas de segurança. A Visão geral do armazenamento de dados e arquivos também descreve a segurança e as permissões em vigor para vários tipos de armazenamento. Em resumo, os pontos importantes são os seguintes:
- Por padrão, os arquivos de dados armazenados no armazenamento interno do dispositivo são particulares para seu aplicativo e provedor.
-
Os bancos de dados
SQLiteDatabase
criados são particulares para seu aplicativo e provedor. - Por padrão, os arquivos de dados salvos no armazenamento externo são públicos e legíveis para todos. Não é possível usar um provedor de conteúdo para restringir o acesso a arquivos no armazenamento externo, porque outros aplicativos podem usar outras chamadas de API para lê-los e gravá-los.
- O método que chama a abertura ou criação de arquivos ou bancos de dados SQLite no armazenamento interno do dispositivo pode oferecer acesso de leitura e gravação a todos os outros apps. Se você usar um arquivo ou banco de dados interno como repositório do provedor e conceder acesso "legível para todos" ou "gravável globalmente", as permissões definidas para o provedor no manifesto não protegerão seus dados. O acesso padrão para arquivos e bancos de dados no armazenamento interno é "particular". Não altere isso para o repositório do provedor.
Se você quiser usar as permissões do provedor de conteúdo para controlar o acesso aos seus dados, armazene-os em arquivos internos, bancos de dados SQLite ou na nuvem, como em um servidor remoto, e mantenha esses arquivos e bancos de dados privados para o aplicativo.
Implementar permissões
Por padrão, todos os aplicativos podem ler ou gravar no provedor, mesmo que os dados subjacentes sejam
particulares, porque o provedor não tem permissões definidas. Para mudar isso,
defina permissões para seu provedor no arquivo de manifesto usando atributos ou elementos
filhos do elemento
<provider>
. É possível definir permissões que se aplicam a todo o provedor,
a determinadas tabelas, registros ou aos três.
Defina permissões para seu provedor com um ou mais
elementos
<permission>
no arquivo de manifesto. Para tornar a
permissão exclusiva do provedor, use o escopo no estilo Java para o
atributo
android:name
. Por exemplo, nomeie a permissão de leitura como com.example.app.provider.permission.READ_PROVIDER
.
A lista a seguir descreve o escopo das permissões do provedor, começando com as permissões que se aplicam a todo o provedor e seguindo para as mais específicas. Permissões mais granulares têm precedência sobre aquelas com escopo maior.
- Única permissão de leitura e gravação no nível do provedor
-
Uma permissão que controla o acesso de leitura e gravação em todo o provedor, especificada
com o atributo
android:permission
do elemento<provider>
. - Separar as permissões de leitura e gravação no nível do provedor
-
Uma permissão de leitura e de gravação para todo o provedor. Você os especifica com os atributos
android:readPermission
eandroid:writePermission
do elemento<provider>
. Elas têm precedência sobre a permissão exigida peloandroid:permission
. - Permissão no nível do caminho
-
Permissão de leitura, gravação ou leitura/gravação para um URI de conteúdo no provedor. Especifique cada URI que você quer controlar com um elemento filho
<path-permission>
do elemento<provider>
. Para cada URI de conteúdo especificado, é possível definir uma permissão de leitura/gravação, uma permissão de leitura, uma permissão de gravação ou as três. As permissões de leitura e gravação têm precedência sobre a permissão de leitura/gravação. Além disso, a permissão no nível do caminho tem precedência sobre as permissões no nível do provedor. - Permissão temporária
-
Um nível de permissão que concede acesso temporário a um aplicativo, mesmo que ele
não tenha as permissões normalmente necessárias. O recurso de acesso temporário reduz o número de permissões que um aplicativo precisa solicitar no manifesto. Quando você ativa permissões temporárias, os únicos aplicativos que precisam
de permissões permanentes para seu provedor são os que acessam continuamente todos
os dados.
Por exemplo, considere as permissões necessárias se você estiver implementando um provedor de e-mail e um app e quiser que um aplicativo visualizador de imagens externas exiba anexos de fotos do seu provedor. Para conceder ao visualizador de imagens o acesso necessário sem as permissões, configure permissões temporárias para URIs de conteúdo de fotos.
Projete seu app de e-mails para que, quando o usuário quiser mostrar uma foto, o app envie uma intent com o URI de conteúdo da foto e flags de permissão para o visualizador de imagens. O visualizador de imagens poderá consultar seu provedor de e-mail para recuperar a foto, mesmo que ele não tenha a permissão normal de leitura para o provedor.
Para ativar permissões temporárias, defina o atributo
android:grantUriPermissions
do elemento<provider>
ou adicione um ou mais elementos filhos<grant-uri-permission>
ao elemento<provider>
. ChameContext.revokeUriPermission()
sempre que remover o suporte a um URI de conteúdo associado a uma permissão temporária do provedor.O valor do atributo determina quanto do provedor pode ser acessado. Se o atributo for definido como
"true"
, o sistema concederá permissão temporária a todo o provedor, substituindo quaisquer outras permissões exigidas pelas permissões no nível do provedor ou do caminho.Se essa sinalização estiver definida como
"false"
, adicione elementos filhos<grant-uri-permission>
ao seu elemento<provider>
. Cada elemento filho especifica o URI ou URIs de conteúdo para os quais o acesso temporário é concedido.Para delegar acesso temporário a um aplicativo, uma intent precisa conter a sinalização
FLAG_GRANT_READ_URI_PERMISSION
, a sinalizaçãoFLAG_GRANT_WRITE_URI_PERMISSION
ou ambas. Elas são definidas com o métodosetFlags()
.Se o atributo
android:grantUriPermissions
não estiver presente, ele será considerado"false"
.
O elemento <provider>
Assim como os componentes Activity
e Service
,
uma subclasse de ContentProvider
é definida no arquivo de manifesto do aplicativo usando o
elemento
<provider>
. O sistema Android recebe as seguintes informações do
elemento:
-
Autoridade
(
android:authorities
) - Nomes simbólicos que identificam todo o provedor dentro do sistema. Esse atributo é descrito com mais detalhes na seção Projetar URIs de conteúdo.
-
Nome da classe do provedor
(
android:name
) -
A classe que implementa
ContentProvider
. Essa classe é descrita em mais detalhes na seção Implementar a classe ContentProvider. - Permissões
-
Atributos que especificam as permissões que outros aplicativos precisam ter para acessar
os dados do provedor:
-
android:grantUriPermissions
: sinalização de permissão temporária -
android:permission
: permissão de leitura/gravação em todo o provedor. -
android:readPermission
: permissão de leitura em todo o provedor -
android:writePermission
: permissão de gravação em todo o provedor
As permissões e os atributos correspondentes são descritos com mais detalhes na seção Implementar permissões do provedor de conteúdo.
-
- Atributos de início e controle
-
Esses atributos determinam como e quando o sistema Android inicia o provedor, as
características do processo do provedor e outras configurações de execução:
-
android:enabled
: flag que permite que o sistema inicie o provedor. -
android:exported
: sinaliza que permite que outros aplicativos usem esse provedor. -
android:initOrder
: a ordem em que esse provedor é iniciado, em relação a outros provedores no mesmo processo. -
android:multiProcess
: sinalização que permite ao sistema iniciar o provedor no mesmo processo que o cliente que faz a chamada -
android:process
: o nome do processo em que o provedor é executado -
android:syncable
: sinalização que indica que os dados do provedor devem ser sincronizados com os dados em um servidor.
Esses atributos estão totalmente documentados no guia do elemento
<provider>
. -
- Atributos informacionais
-
Um ícone e uma etiqueta opcionais para o provedor:
-
android:icon
: recurso drawable contendo um ícone para o provedor. O ícone aparece ao lado do rótulo do provedor na lista de apps em Configurações > Apps > Todos. -
android:label
: um rótulo informativo que descreve o provedor, os dados dele ou ambos. O rótulo aparece na lista de apps em Configurações > Apps > Todos.
Esses atributos estão totalmente documentados no guia do elemento
<provider>
. -
Intents e acesso a dados
Os aplicativos podem acessar um provedor de conteúdo indiretamente com um Intent
.
O aplicativo não chama nenhum dos métodos de ContentResolver
ou
ContentProvider
. Em vez disso, ele envia uma intent que inicia uma atividade,
que geralmente faz parte do aplicativo do próprio provedor. A atividade de destino é responsável por
recuperar e mostrar os dados na IU.
Dependendo da ação na intent, a atividade de destino também pode solicitar que o usuário faça modificações nos dados do provedor. Um intent também pode conter dados "extras" que a atividade de destino exibe na interface. O usuário tem a opção de alterar esses dados antes de usá-los para modificar os dados no provedor.
O acesso à intent pode ser usado para ajudar a integridade dos dados. Seu provedor pode depender da inserção, atualização e exclusão de dados de acordo com a lógica de negócios estritamente definida. Nesse caso, permitir que outros aplicativos modifiquem seus dados diretamente pode gerar dados inválidos.
Se você quiser que os desenvolvedores usem o acesso via intents, certifique-se de documentá-lo minuciosamente. Explique por que o acesso à intent usando a interface do aplicativo é melhor do que tentar modificar os dados com o código.
O processamento de uma intent recebida que quer modificar os dados do seu provedor não é diferente de processar outros intents. Para saber mais sobre o uso de intents, leia Intents e filtros de intent.
Para mais informações relacionadas, consulte a Visão geral do provedor de agenda.