A AppSearch é uma solução de pesquisa no dispositivo de alto desempenho para gerenciar dados estruturados armazenados localmente. Ela contém APIs para indexar e extrair dados usando a pesquisa de texto completo. Os aplicativos podem usar o AppSearch para oferecer recursos de pesquisa personalizados no app, permitindo que os usuários pesquisem conteúdo mesmo off-line.
![Diagrama ilustrando a indexação e a pesquisa com o AppSearch](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?authuser=0&hl=pt-br)
O AppSearch oferece os seguintes recursos:
- Uma implementação de armazenamento rápida e com foco em dispositivos móveis com uso de E/S baixo
- Indexação e consulta altamente eficientes em grandes conjuntos de dados
- Suporte a vários idiomas, como inglês e espanhol
- Classificação de relevância e pontuação de uso
Devido ao uso menor de E/S, o AppSearch oferece latência menor para indexação e pesquisa em grandes conjuntos de dados em comparação com o SQLite. O AppSearch simplifica as consultas entre tipos com suporte a consultas únicas, enquanto o SQLite mescla resultados de várias tabelas.
Para ilustrar os recursos do AppSearch, vamos usar o exemplo de um aplicativo de música que gerencia as músicas favoritas dos usuários e permite que eles as pesquisem com facilidade. Os usuários podem curtir músicas do mundo todo com títulos em diferentes idiomas, que o AppSearch oferece suporte nativo para indexação e consulta. Quando o usuário pesquisa uma música pelo título ou nome do artista, o aplicativo simplesmente transmite a solicitação ao AppSearch para recuperar músicas correspondentes de maneira rápida e eficiente. O app mostra os resultados, permitindo que os usuários comecem a tocar as músicas favoritas rapidamente.
Configurar
Para usar o AppSearch no app, adicione as seguintes dependências ao
arquivo build.gradle
do app:
Groovy
dependencies { def appsearch_version = "1.1.0-alpha05" implementation "androidx.appsearch:appsearch:$appsearch_version" // Use kapt instead of annotationProcessor if writing Kotlin classes annotationProcessor "androidx.appsearch:appsearch-compiler:$appsearch_version" implementation "androidx.appsearch:appsearch-local-storage:$appsearch_version" // PlatformStorage is compatible with Android 12+ devices, and offers additional features // to LocalStorage. implementation "androidx.appsearch:appsearch-platform-storage:$appsearch_version" }
Kotlin
dependencies { val appsearch_version = "1.1.0-alpha05" implementation("androidx.appsearch:appsearch:$appsearch_version") // Use annotationProcessor instead of kapt if writing Java classes kapt("androidx.appsearch:appsearch-compiler:$appsearch_version") implementation("androidx.appsearch:appsearch-local-storage:$appsearch_version") // PlatformStorage is compatible with Android 12+ devices, and offers additional features // to LocalStorage. implementation("androidx.appsearch:appsearch-platform-storage:$appsearch_version") }
Conceitos do AppSearch
O diagrama a seguir ilustra os conceitos do AppSearch e as interações deles.
Figura 1. Diagrama dos conceitos do AppSearch: banco de dados, esquema,
tipos de esquema, documentos, sessão e pesquisa.
Banco de dados e sessão
Um banco de dados do AppSearch é uma coleção de documentos que está em conformidade com o esquema do banco de dados. Os aplicativos clientes criam um banco de dados fornecendo o contexto do aplicativo e um nome de banco de dados. Os bancos de dados só podem ser abertos pelo aplicativo que os criou. Quando um banco de dados é aberto, uma sessão é retornada para interagir com ele. A sessão é o ponto de entrada para chamar as APIs do AppSearch e permanece aberta até ser fechada pelo aplicativo cliente.
Esquema e tipos de esquema
Um esquema representa a estrutura organizacional dos dados em um banco de dados do AppSearch.
O esquema é composto por tipos que representam tipos exclusivos de dados. Os tipos de esquema consistem em propriedades que contêm um nome, tipo de dados e cardinalidade. Depois que um tipo de esquema é adicionado ao esquema do banco de dados, é possível criar e adicionar documentos desse tipo ao banco de dados.
Documentos
No AppSearch, uma unidade de dados é representada como um documento. Cada documento em um banco de dados do AppSearch é identificado de maneira exclusiva pelo namespace e ID. Os namespaces são usados para separar dados de diferentes origens quando apenas uma origem precisa ser consultada, como contas de usuário.
Os documentos contêm um carimbo de data/hora de criação, um tempo de vida (TTL, na sigla em inglês) e uma pontuação que pode ser usada para classificação durante a recuperação. Um documento também recebe um tipo de esquema que descreve outras propriedades de dados que ele precisa ter.
Uma classe de documento é uma abstração de um documento. Ele contém campos anotados que representam o conteúdo de um documento. Por padrão, o nome da classe de documento define o nome do tipo de esquema.
Pesquisar
Os documentos são indexados e podem ser pesquisados com uma consulta. Um documento é correspondido e incluído nos resultados da pesquisa se ele contém os termos da consulta ou corresponde a outra especificação de pesquisa. Os resultados são ordenados com base na pontuação e na estratégia de classificação. Os resultados da pesquisa são representados por páginas que podem ser recuperadas sequencialmente.
O AppSearch oferece personalizações para a pesquisa, como filtros, configuração do tamanho da página e snippeting.
Armazenamento da plataforma, armazenamento local ou armazenamento do Google Play Services
O AppSearch oferece três soluções de armazenamento: LocalStorage
, PlatformStorage
e
PlayServicesStorage
. Com LocalStorage
, seu aplicativo gerencia um
índice específico do app que fica no diretório de dados do aplicativo. Com
PlatformStorage
e PlayServicesStorage
, seu app contribui para um
índice central em todo o sistema. O índice de PlatformStorage
é hospedado no servidor
do sistema, e o índice de PlayServicesStorage
é hospedado no armazenamento
do Google Play Services. O acesso a dados nesses índices centrais é restrito aos dados que
o aplicativo contribuiu e aos dados que foram compartilhados explicitamente com você
por outro aplicativo. Todas essas opções de armazenamento compartilham a mesma API e podem ser
trocadas com base na versão de um dispositivo:
Kotlin
if (BuildCompat.isAtLeastS()) { appSearchSessionFuture.setFuture( PlatformStorage.createSearchSession( PlatformStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } else { if (usePlayServicesStorageBelowS) { appSearchSessionFuture.setFuture( PlayServicesStorage.createSearchSession( PlayServicesStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } else { appSearchSessionFuture.setFuture( LocalStorage.createSearchSession( LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } }
Java
if (BuildCompat.isAtLeastS()) { mAppSearchSessionFuture.setFuture(PlatformStorage.createSearchSession( new PlatformStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } else { if (usePlayServicesStorageBelowS) { mAppSearchSessionFuture.setFuture(PlayServicesStorage.createSearchSession( new PlayServicesStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } else { mAppSearchSessionFuture.setFuture(LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } }
Usando PlatformStorage
e PlayServicesStorage
, seu app pode
compartilhar dados com segurança com outros apps para que eles também possam pesquisar
os dados do seu app. O compartilhamento de dados de aplicativos somente leitura é concedido
usando uma negociação de certificado para garantir que o outro aplicativo tenha
permissão para ler os dados. Leia mais sobre essa API na documentação
para setSchemaTypeVisibilityForPackage()
.
Além disso, com PlatformStorage
, os dados indexados podem ser mostrados
nas plataformas da interface do sistema. Os aplicativos podem desativar a exibição de alguns ou todos os dados
nas plataformas da interface do sistema. Leia mais sobre essa API na
documentação de setSchemaTypeDisplayedBySystem()
.
Recursos | LocalStorage (compatível com o Android 5.0 e versões mais recentes) |
PlatformStorage (compatível com o Android 12 e versões mais recentes) |
PlayServicesStorage (compatível com o Android 5.0 e versões mais recentes) |
---|---|---|---|
Pesquisa de texto completo eficiente | |||
Suporte a vários idiomas | |||
Tamanho binário reduzido | |||
Compartilhamento de dados entre aplicativos | |||
Capacidade de mostrar dados nas plataformas da interface do sistema | |||
O tamanho e a contagem de documentos podem ser ilimitados | |||
Operações mais rápidas sem latência adicional do vinculador |
Há outras vantagens e desvantagens a serem consideradas ao escolher entre LocalStorage
e PlatformStorage
. Como PlatformStorage
envolve APIs do Jetpack no
serviço de sistema do AppSearch, o impacto no tamanho do APK é mínimo em comparação com o uso do
LocalStorage. No entanto, isso também significa que as operações do AppSearch têm latência
adicional de vinculação ao chamar o serviço do sistema do AppSearch. Com PlatformStorage
,
o AppSearch limita o número e o tamanho dos documentos que um aplicativo
pode indexar para garantir um índice central eficiente. O PlayServicesStorage
também tem
as mesmas limitações que o PlatformStorage
e só tem suporte em dispositivos com
o Google Play Services.
Começar a usar o AppSearch
O exemplo desta seção mostra como usar as APIs AppSearch para fazer a integração com um aplicativo hipotético de anotações.
Escrever uma classe de documento
A primeira etapa para integrar o AppSearch é escrever uma classe de documento para
descrever os dados a serem inseridos no banco de dados. Marque uma classe como uma classe de documento
usando a anotação
@Document
.É possível usar instâncias da classe de documento para inserir e
extrair documentos do banco de dados.
O código abaixo define uma classe de documento de nota com um campo
anexado @Document.StringProperty
para indexar o texto de um objeto de nota.
Kotlin
@Document public data class Note( // Required field for a document class. All documents MUST have a namespace. @Document.Namespace val namespace: String, // Required field for a document class. All documents MUST have an Id. @Document.Id val id: String, // Optional field for a document class, used to set the score of the // document. If this is not included in a document class, the score is set // to a default of 0. @Document.Score val score: Int, // Optional field for a document class, used to index a note's text for this // document class. @Document.StringProperty(indexingType = AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES) val text: String )
Java
@Document public class Note { // Required field for a document class. All documents MUST have a namespace. @Document.Namespace private final String namespace; // Required field for a document class. All documents MUST have an Id. @Document.Id private final String id; // Optional field for a document class, used to set the score of the // document. If this is not included in a document class, the score is set // to a default of 0. @Document.Score private final int score; // Optional field for a document class, used to index a note's text for this // document class. @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) private final String text; Note(@NonNull String id, @NonNull String namespace, int score, @NonNull String text) { this.id = Objects.requireNonNull(id); this.namespace = Objects.requireNonNull(namespace); this.score = score; this.text = Objects.requireNonNull(text); } @NonNull public String getNamespace() { return namespace; } @NonNull public String getId() { return id; } public int getScore() { return score; } @NonNull public String getText() { return text; } }
Abrir um banco de dados
Você precisa criar um banco de dados antes de trabalhar com documentos. O código abaixo
cria um novo banco de dados com o nome notes_app
e recebe um ListenableFuture
para um AppSearchSession
,
que representa a conexão com o banco de dados e fornece as APIs para
operações de banco de dados.
Kotlin
val context: Context = getApplicationContext() val sessionFuture = LocalStorage.createSearchSession( LocalStorage.SearchContext.Builder(context, /*databaseName=*/"notes_app") .build() )
Java
Context context = getApplicationContext(); ListenableFuture<AppSearchSession> sessionFuture = LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(context, /*databaseName=*/ "notes_app") .build() );
Definir um esquema
É necessário definir um esquema antes de inserir e recuperar documentos do banco de dados. O esquema do banco de dados consiste em diferentes tipos de dados estruturados, chamados de "tipos de esquema". O código abaixo define o esquema fornecendo a classe de documento como um tipo de esquema.
Kotlin
val setSchemaRequest = SetSchemaRequest.Builder().addDocumentClasses(Note::class.java) .build() val setSchemaFuture = Futures.transformAsync( sessionFuture, { session -> session?.setSchema(setSchemaRequest) }, mExecutor )
Java
SetSchemaRequest setSchemaRequest = new SetSchemaRequest.Builder().addDocumentClasses(Note.class) .build(); ListenableFuture<SetSchemaResponse> setSchemaFuture = Futures.transformAsync(sessionFuture, session -> session.setSchema(setSchemaRequest), mExecutor);
Colocar um documento no banco de dados
Depois que um tipo de esquema é adicionado, você pode adicionar documentos desse tipo ao banco de dados.
O código a seguir cria um documento do tipo de esquema Note
usando o builder de classe de documento Note
. Ele define o namespace do documento user1
para representar um
usuário arbitrário desse exemplo. O documento é inserido no banco de dados
e um listener é anexado para processar o resultado da operação put.
Kotlin
val note = Note( namespace="user1", id="noteId", score=10, text="Buy fresh fruit" ) val putRequest = PutDocumentsRequest.Builder().addDocuments(note).build() val putFuture = Futures.transformAsync( sessionFuture, { session -> session?.put(putRequest) }, mExecutor ) Futures.addCallback( putFuture, object : FutureCallback<AppSearchBatchResult<String, Void>?> { override fun onSuccess(result: AppSearchBatchResult<String, Void>?) { // Gets map of successful results from Id to Void val successfulResults = result?.successes // Gets map of failed results from Id to AppSearchResult val failedResults = result?.failures } override fun onFailure(t: Throwable) { Log.e(TAG, "Failed to put documents.", t) } }, mExecutor )
Java
Note note = new Note(/*namespace=*/"user1", /*id=*/ "noteId", /*score=*/ 10, /*text=*/ "Buy fresh fruit!"); PutDocumentsRequest putRequest = new PutDocumentsRequest.Builder().addDocuments(note) .build(); ListenableFuture<AppSearchBatchResult<String, Void>> putFuture = Futures.transformAsync(sessionFuture, session -> session.put(putRequest), mExecutor); Futures.addCallback(putFuture, new FutureCallback<AppSearchBatchResult<String, Void>>() { @Override public void onSuccess(@Nullable AppSearchBatchResult<String, Void> result) { // Gets map of successful results from Id to Void Map<String, Void> successfulResults = result.getSuccesses(); // Gets map of failed results from Id to AppSearchResult Map<String, AppSearchResult<Void>> failedResults = result.getFailures(); } @Override public void onFailure(@NonNull Throwable t) { Log.e(TAG, "Failed to put documents.", t); } }, mExecutor);
Pesquisar
É possível pesquisar documentos indexados usando as operações de pesquisa abordadas
nesta seção. O código a seguir executa consultas do termo "fruit" no
banco de dados para documentos que pertencem ao namespace user1
.
Kotlin
val searchSpec = SearchSpec.Builder() .addFilterNamespaces("user1") .build(); val searchFuture = Futures.transform( sessionFuture, { session -> session?.search("fruit", searchSpec) }, mExecutor ) Futures.addCallback( searchFuture, object : FutureCallback<SearchResults> { override fun onSuccess(searchResults: SearchResults?) { iterateSearchResults(searchResults) } override fun onFailure(t: Throwable?) { Log.e("TAG", "Failed to search notes in AppSearch.", t) } }, mExecutor )
Java
SearchSpec searchSpec = new SearchSpec.Builder() .addFilterNamespaces("user1") .build(); ListenableFuture<SearchResults> searchFuture = Futures.transform(sessionFuture, session -> session.search("fruit", searchSpec), mExecutor); Futures.addCallback(searchFuture, new FutureCallback<SearchResults>() { @Override public void onSuccess(@Nullable SearchResults searchResults) { iterateSearchResults(searchResults); } @Override public void onFailure(@NonNull Throwable t) { Log.e(TAG, "Failed to search notes in AppSearch.", t); } }, mExecutor);
Iterar por SearchResults
As pesquisas retornam uma instância de SearchResults
, que dá acesso às páginas de objetos SearchResult
. Cada SearchResult
contém o GenericDocument
correspondente, a forma geral de um
documento para o qual todos os documentos são convertidos. O código a seguir recebe a primeira
página de resultados da pesquisa e converte o resultado de volta em um documento Note
.
Kotlin
Futures.transform( searchResults?.nextPage, { page: List<SearchResult>? -> // Gets GenericDocument from SearchResult. val genericDocument: GenericDocument = page!![0].genericDocument val schemaType = genericDocument.schemaType val note: Note? = try { if (schemaType == "Note") { // Converts GenericDocument object to Note object. genericDocument.toDocumentClass(Note::class.java) } else null } catch (e: AppSearchException) { Log.e( TAG, "Failed to convert GenericDocument to Note", e ) null } note }, mExecutor )
Java
Futures.transform(searchResults.getNextPage(), page -> { // Gets GenericDocument from SearchResult. GenericDocument genericDocument = page.get(0).getGenericDocument(); String schemaType = genericDocument.getSchemaType(); Note note = null; if (schemaType.equals("Note")) { try { // Converts GenericDocument object to Note object. note = genericDocument.toDocumentClass(Note.class); } catch (AppSearchException e) { Log.e(TAG, "Failed to convert GenericDocument to Note", e); } } return note; }, mExecutor);
Remover um documento
Quando o usuário exclui uma nota, o aplicativo exclui o documento Note
correspondente do banco de dados. Isso garante que a nota não seja mais exibida nas
consultas. O código abaixo faz uma solicitação explícita para remover o documento Note
do banco de dados por ID.
Kotlin
val removeRequest = RemoveByDocumentIdRequest.Builder("user1") .addIds("noteId") .build() val removeFuture = Futures.transformAsync( sessionFuture, { session -> session?.remove(removeRequest) }, mExecutor )
Java
RemoveByDocumentIdRequest removeRequest = new RemoveByDocumentIdRequest.Builder("user1") .addIds("noteId") .build(); ListenableFuture<AppSearchBatchResult<String, Void>> removeFuture = Futures.transformAsync(sessionFuture, session -> session.remove(removeRequest), mExecutor);
Persistência no disco
As atualizações de um banco de dados precisam ser mantidas periodicamente no disco chamando
requestFlush()
. O
código a seguir chama requestFlush()
com um listener para determinar se a chamada
foi bem-sucedida.
Kotlin
val requestFlushFuture = Futures.transformAsync( sessionFuture, { session -> session?.requestFlush() }, mExecutor ) Futures.addCallback(requestFlushFuture, object : FutureCallback<Void?> { override fun onSuccess(result: Void?) { // Success! Database updates have been persisted to disk. } override fun onFailure(t: Throwable) { Log.e(TAG, "Failed to flush database updates.", t) } }, mExecutor)
Java
ListenableFuture<Void> requestFlushFuture = Futures.transformAsync(sessionFuture, session -> session.requestFlush(), mExecutor); Futures.addCallback(requestFlushFuture, new FutureCallback<Void>() { @Override public void onSuccess(@Nullable Void result) { // Success! Database updates have been persisted to disk. } @Override public void onFailure(@NonNull Throwable t) { Log.e(TAG, "Failed to flush database updates.", t); } }, mExecutor);
Encerrar uma sessão
Uma AppSearchSession
precisa ser fechada quando um aplicativo não chamar mais nenhuma operação
do banco de dados. O código abaixo fecha a sessão do AppSearch que foi aberta
anteriormente e persiste todas as atualizações no disco.
Kotlin
val closeFuture = Futures.transform<AppSearchSession, Unit>(sessionFuture, { session -> session?.close() Unit }, mExecutor )
Java
ListenableFuture<Void> closeFuture = Futures.transform(sessionFuture, session -> { session.close(); return null; }, mExecutor);
Outros recursos
Para saber mais sobre o AppSearch, consulte estes recursos:
Amostras
- Amostra do AppSearch para Android (Kotlin), um app de anotações que usa o AppSearch para indexar as anotações de um usuário e permite que os usuários pesquisem entre elas.
Enviar feedback
Envie comentários e ideias usando os recursos abaixo:
Informe bugs para que possamos corrigi-los.