AppSearch est une solution de recherche intégrée à l'appareil hautes performances qui permet de gérer les données structurées stockées localement. Il contient des API permettant d'indexer et de récupérer les données à l'aide de la recherche en texte intégral. Les applications peuvent utiliser AppSearch pour proposer des fonctionnalités de recherche personnalisées dans l'application, ce qui permet aux utilisateurs de rechercher du contenu même en mode hors connexion.
![Schéma illustrant l'indexation et la recherche dans AppSearch](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?hl=fr)
AppSearch propose les fonctionnalités suivantes:
- Implémentation de stockage rapide et mobile first avec une faible utilisation des E/S
- Indexation et interrogation hautement efficaces sur de grands ensembles de données
- Compatibilité multilingue, par exemple en anglais et en espagnol
- Classement de la pertinence et évaluation de l'utilisation
En raison d'une utilisation moindre des E/S, AppSearch offre une latence inférieure pour l'indexation et la recherche sur de grands ensembles de données par rapport à SQLite. AppSearch simplifie les requêtes multitypes en acceptant les requêtes uniques, tandis que SQLite fusionne les résultats de plusieurs tables.
Pour illustrer les fonctionnalités d'AppSearch, prenons l'exemple d'une application musicale qui gère les titres préférés des utilisateurs et leur permet de les rechercher facilement. Les utilisateurs écoutent de la musique du monde entier avec des titres de chansons dans différentes langues, ce que AppSearch prend en charge en natif pour l'indexation et les requêtes. Lorsque l'utilisateur recherche un titre par titre ou par nom d'artiste, l'application transmet simplement la requête à AppSearch pour récupérer rapidement et efficacement les titres correspondants. L'application affiche les résultats, ce qui permet aux utilisateurs de commencer rapidement à écouter leurs titres préférés.
Configuration
Pour utiliser AppSearch dans votre application, ajoutez les dépendances suivantes au fichier build.gradle
de votre application:
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") }
Concepts AppSearch
Le schéma suivant illustre les concepts AppSearch et leurs interactions.
Figure 1 : Diagramme des concepts AppSearch: base de données AppSearch, schéma, types de schéma, documents, session et recherche.
Base de données et session
Une base de données AppSearch est un ensemble de documents conformes au schéma de la base de données. Les applications clientes créent une base de données en fournissant leur contexte d'application et un nom de base de données. Les bases de données ne peuvent être ouvertes que par l'application qui les a créées. Lorsqu'une base de données est ouverte, une session est renvoyée pour interagir avec la base de données. La session est le point d'entrée pour appeler les API AppSearch et reste ouverte jusqu'à ce qu'elle soit fermée par l'application cliente.
Schéma et types de schémas
Un schéma représente la structure organisationnelle des données dans une base de données AppSearch.
Le schéma est composé de types de schémas qui représentent des types de données uniques. Les types de schémas se composent de propriétés contenant un nom, un type de données et une cardinalité. Une fois qu'un type de schéma a été ajouté au schéma de la base de données, vous pouvez créer des documents de ce type de schéma et les ajouter à la base de données.
Documents
Dans AppSearch, une unité de données est représentée par un document. Chaque document d'une base de données AppSearch est identifié de manière unique par son espace de noms et son ID. Les espaces de noms permettent de séparer les données de différentes sources lorsqu'une seule source doit être interrogée, comme les comptes utilisateur.
Les documents contiennent un code temporel de création, un code temporel de validité (TTL) et un score pouvant être utilisé pour le classement lors de la récupération. Un type de schéma est également attribué à un document, qui décrit les propriétés de données supplémentaires que le document doit posséder.
Une classe de document est une abstraction d'un document. Il contient des champs annotés qui représentent le contenu d'un document. Par défaut, le nom de la classe de document définit le nom du type de schéma.
Recherche
Les documents sont indexés et peuvent être recherchés en fournissant une requête. Un document est mis en correspondance et inclus dans les résultats de recherche s'il contient les termes de la requête ou s'il correspond à une autre spécification de recherche. Les résultats sont triés en fonction de leur score et de leur stratégie de classement. Les résultats de recherche sont représentés par des pages que vous pouvez récupérer de manière séquentielle.
AppSearch propose des personnalisations pour la recherche, telles que des filtres, la configuration de la taille de la page et l'extraction d'extraits.
Stockage de la plate-forme, stockage local ou stockage des services Play
AppSearch propose trois solutions de stockage: LocalStorage
, PlatformStorage
et PlayServicesStorage
. Avec LocalStorage
, votre application gère un index spécifique à l'application qui se trouve dans le répertoire de données de votre application. Avec PlatformStorage
et PlayServicesStorage
, votre application contribue à un indice central à l'échelle du système. L'index de PlatformStorage
est hébergé sur le serveur système, et celui de PlayServicesStorage
est hébergé dans l'espace de stockage du service Google Play. L'accès aux données dans ces index centraux est limité aux données que votre application a fournies et aux données qui ont été explicitement partagées avec vous par une autre application. Toutes ces options de stockage partagent la même API et peuvent être interchangées en fonction de la version de l'appareil:
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())); } }
À l'aide de PlatformStorage
et PlayServicesStorage
, votre application peut partager des données de manière sécurisée avec d'autres applications pour leur permettre de rechercher également dans les données de votre application. Le partage des données d'application en lecture seule est accordé à l'aide d'un échange de certificats pour s'assurer que l'autre application est autorisée à lire les données. Pour en savoir plus sur cette API, consultez la documentation de setSchemaTypeVisibilityForPackage()
.
De plus, avec PlatformStorage
, les données indexées peuvent être affichées sur les surfaces de l'UI du système. Les applications peuvent désactiver l'affichage de certaines ou de toutes leurs données sur les surfaces de l'UI système. Pour en savoir plus sur cette API, consultez la documentation sur setSchemaTypeDisplayedBySystem()
.
Fonctionnalités | LocalStorage (compatible avec Android 5.0 et versions ultérieures) |
PlatformStorage (compatible avec Android 12 et versions ultérieures) |
PlayServicesStorage (compatible avec Android 5.0 et versions ultérieures) |
---|---|---|---|
Recherche en texte intégral efficace | |||
Compatibilité multilingue | |||
Taille binaire réduite | |||
Partage de données entre applications | |||
Possibilité d'afficher des données sur les surfaces de l'UI du système | |||
Vous pouvez indexer un nombre illimité de documents et de tailles. | |||
Opérations plus rapides sans latence de liaison supplémentaire |
D'autres compromis doivent être pris en compte lorsque vous choisissez entre LocalStorage
et PlatformStorage
. Étant donné que PlatformStorage
encapsule les API Jetpack sur le service système AppSearch, l'impact sur la taille de l'APK est minimal par rapport à l'utilisation de LocalStorage. Toutefois, cela signifie également que les opérations AppSearch entraînent une latence de liaison supplémentaire lors de l'appel du service système AppSearch. Avec PlatformStorage
, AppSearch limite le nombre et la taille des documents qu'une application peut indexer pour garantir un index central efficace. PlayServicesStorage
présente également les mêmes limites que PlatformStorage
et n'est compatible qu'avec les appareils dotés des services Google Play.
Premiers pas avec AppSearch
L'exemple de cette section montre comment utiliser les API AppSearch pour intégrer une application de prise de notes hypothétique.
Écrire une classe de document
La première étape de l'intégration à AppSearch consiste à écrire une classe de documents pour décrire les données à insérer dans la base de données. Marquez une classe comme classe de documents à l'aide de l'annotation @Document
.Vous pouvez utiliser des instances de la classe de documents pour y placer des documents et les récupérer à partir de la base de données.
Le code suivant définit une classe de documents Note avec un champ annoté @Document.StringProperty
pour indexer le texte d'un objet Note.
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; } }
Ouvrir une base de données
Vous devez créer une base de données avant de pouvoir travailler avec des documents. Le code suivant crée une base de données avec le nom notes_app
et obtient un ListenableFuture
pour un AppSearchSession
, qui représente la connexion à la base de données et fournit les API pour les opérations de base de données.
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() );
Définir un schéma
Vous devez définir un schéma avant de pouvoir insérer et récupérer des documents dans la base de données. Le schéma de la base de données se compose de différents types de données structurées, appelés "types de schémas". Le code suivant définit le schéma en fournissant la classe de document en tant que type de schéma.
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);
Placer un document dans la base de données
Une fois qu'un type de schéma a été ajouté, vous pouvez ajouter des documents de ce type à la base de données.
Le code suivant crée un document de type de schéma Note
à l'aide du compilateur de classe de document Note
. Il définit l'espace de noms de document user1
pour représenter un utilisateur arbitraire de cet exemple. Le document est ensuite inséré dans la base de données et un écouteur est associé pour traiter le résultat de l'opération 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);
Recherche
Vous pouvez rechercher des documents indexés à l'aide des opérations de recherche décrites dans cette section. Le code suivant effectue des requêtes pour le terme "fruit" dans la base de données pour les documents appartenant à l'espace de noms 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);
Itérer les SearchResults
Les recherches renvoient une instance SearchResults
, qui permet d'accéder aux pages des objets SearchResult
. Chaque SearchResult
contient son GenericDocument
correspondant, la forme générale d'un document dans lequel tous les documents sont convertis. Le code suivant récupère la première page des résultats de recherche et convertit le résultat en document 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);
Supprimer un document
Lorsque l'utilisateur supprime une note, l'application supprime le document Note
correspondant de la base de données. Vous vous assurez ainsi que la note ne s'affichera plus dans les requêtes. Le code suivant envoie une requête explicite pour supprimer le document Note
de la base de données par 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);
Persistance sur disque
Les mises à jour d'une base de données doivent être conservées régulièrement sur disque en appelant requestFlush()
. Le code suivant appelle requestFlush()
avec un écouteur pour déterminer si l'appel a réussi.
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);
Fermer une session
Un AppSearchSession
doit être fermé lorsqu'une application n'appelle plus d'opérations de base de données. Le code suivant ferme la session AppSearch précédemment ouverte et conserve toutes les mises à jour sur le disque.
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);
Ressources supplémentaires
Pour en savoir plus sur AppSearch, consultez les ressources supplémentaires suivantes:
Exemples
- Exemple d'application Android AppSearch (Kotlin), une application de prise de notes qui utilise AppSearch pour indexer les notes d'un utilisateur et lui permettre de les rechercher.
Envoyer des commentaires
Faites-nous part de vos commentaires et de vos idées via les ressources suivantes :
Signalez les bugs pour que nous puissions les corriger.