AppSearch è una soluzione di ricerca on-device ad alte prestazioni per la gestione dei dati strutturati memorizzati localmente. Contiene API per l'indicizzazione e il recupero dei dati utilizzando la ricerca a testo intero. Le applicazioni possono utilizzare AppSearch per offrire funzionalità di ricerca in-app personalizzate, consentendo agli utenti di cercare contenuti anche offline.
![Diagramma che illustra l'indicizzazione e la ricerca in AppSearch](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?authuser=0&hl=it)
AppSearch fornisce le seguenti funzionalità:
- Un'implementazione di archiviazione rapida e mobile-first con un utilizzo ridotto di I/O
- Indicizzazione e query altamente efficienti su grandi set di dati
- Supporto multilingue, ad esempio inglese e spagnolo
- Classifica della pertinenza e punteggio di utilizzo
Grazie a un utilizzo inferiore dell'I/O, AppSearch offre una latenza inferiore per l'indicizzazione e la ricerca su set di dati di grandi dimensioni rispetto a SQLite. AppSearch semplifica le query tra tipi supportando le query singole, mentre SQLite unisce i risultati di più tabelle.
Per illustrare le funzionalità di AppSearch, prendiamo l'esempio di un'applicazione musicale che gestisce i brani preferiti degli utenti e consente loro di cercarli facilmente. Gli utenti ascoltano musica di tutto il mondo con titoli dei brani in lingue diverse, per i quali AppSearch supporta in modo nativo l'indicizzazione e le query. Quando l'utente cerca un brano per titolo o nome dell'artista, l'applicazione passa semplicemente la richiesta ad AppSearch per recuperare in modo rapido ed efficiente i brani corrispondenti. L'applicazione mostra i risultati, consentendo agli utenti di iniziare rapidamente a riprodurre i loro brani preferiti.
Configura
Per utilizzare AppSearch nella tua applicazione, aggiungi le seguenti dipendenze al file build.gradle
dell'applicazione:
Alla moda
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") }
Concetti di AppSearch
Il seguente diagramma illustra i concetti di AppSearch e le relative interazioni.
Figura 1. Diagramma dei concetti di AppSearch: database, schema, tipi di schema, documenti, sessione e ricerca di AppSearch.
Database e sessione
Un database AppSearch è una raccolta di documenti conformi allo schema del database. Le applicazioni client creano un database fornendo il contesto dell'applicazione e un nome del database. I database possono essere aperti solo dall'applicazione che li ha creati. Quando viene aperto un database, viene restituita una sessione per interagire con il database. La sessione è il punto di contatto per chiamare le API AppSearch e rimane aperta finché non viene chiusa dall'applicazione client.
Schema e tipi di schema
Uno schema rappresenta la struttura organizzativa dei dati all'interno di un database AppSearch.
Lo schema è composto da tipi di schema che rappresentano tipi di dati univoci. I tipi di schema sono costituiti da proprietà che contengono un nome, un tipo di dati e la cardinalità. Una volta aggiunto un tipo di schema allo schema del database, i documenti di quel tipo di schema possono essere creati e aggiunti al database.
Documenti
In AppSearch, un'unità di dati è rappresentata come un documento. Ogni documento in un database AppSearch è identificato in modo univoco dal proprio spazio dei nomi e dal proprio ID. I nomi di spazio vengono utilizzati per separare i dati di origini diverse quando è necessario eseguire query su una sola origine, ad esempio gli account utente.
I documenti contengono un timestamp di creazione, un TTL (Time to Live) e un punteggio che può essere utilizzato per il ranking durante il recupero. A un documento viene assegnato anche un tipo di schema che descrive le proprietà di dati aggiuntive che deve avere il documento.
Una classe di documenti è un'astrazione di un documento. Contiene campi annotati che rappresentano i contenuti di un documento. Per impostazione predefinita, il nome della classe del documento imposta il nome del tipo di schema.
Cerca
I documenti vengono indicizzati e possono essere cercati specificando una query. Un documento viene trovato e incluso nei risultati di ricerca se contiene i termini della query o corrisponde a un'altra specifica di ricerca. I risultati sono ordinati in base al loro voto e alla strategia di ranking. I risultati di ricerca sono rappresentati da pagine che puoi recuperare in sequenza.
AppSearch offre personalizzazioni per la ricerca, come filtri, configurazione delle dimensioni della pagina e snippeting.
Spazio di archiviazione della piattaforma, spazio di archiviazione locale o spazio di archiviazione di Play Services
AppSearch offre tre soluzioni di archiviazione: LocalStorage
, PlatformStorage
e
PlayServicesStorage
. Con LocalStorage
, la tua applicazione gestisce un indice specifico per l'app che si trova nella directory dei dati dell'applicazione. Con PlatformStorage
e PlayServicesStorage
, la tua applicazione contribuisce a un indice centrale a livello di sistema. L'indice di PlatformStorage
è ospitato nel
server di sistema e l'indice di PlayServicesStorage
è ospitato nello
stoccaggio di Google Play Services. L'accesso ai dati all'interno di questi indici centrali è limitato ai dati che la tua applicazione ha contribuito a fornire e a quelli che sono stati condivisi esplicitamente con te da un'altra applicazione. Tutte queste opzioni di archiviazione condividono la stessa API e possono essere scambiate in base alla versione del 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())); } }
Utilizzando PlatformStorage
e PlayServicesStorage
, la tua applicazione può condividere in modo sicuro i dati con altre applicazioni per consentire loro di eseguire ricerche anche nei dati della tua app. La condivisione dei dati delle applicazioni in sola lettura viene concessa utilizzando un handshake del certificato per garantire che l'altra applicazione abbia l'autorizzazione a leggere i dati. Scopri di più su questa API nella documentazione per setSchemaTypeVisibilityForPackage()
.
Inoltre, con PlatformStorage
, i dati indicizzati possono essere visualizzati sulle piattaforme dell'interfaccia utente di sistema. Le applicazioni possono disattivare la visualizzazione di alcuni o di tutti i propri dati sulle piattaforme dell'interfaccia utente di sistema. Scopri di più su questa API nella documentazione di setSchemaTypeDisplayedBySystem()
.
Funzionalità | LocalStorage (compatibile con Android 5.0 e versioni successive) |
PlatformStorage (compatibile con Android 12 e versioni successive) |
PlayServicesStorage (compatibile con Android 5.0 e versioni successive) |
---|---|---|---|
Ricerca a testo intero efficiente | |||
Supporto di più lingue | |||
Dimensioni binarie ridotte | |||
Condivisione di dati da un'applicazione all'altra | |||
Possibilità di visualizzare i dati sulle piattaforme dell'interfaccia utente di sistema | |||
È possibile indicizzare un numero e dimensioni illimitati di documenti | |||
Operazioni più rapide senza latenza aggiuntiva del binder |
Esistono altri compromessi da considerare quando si sceglie tra LocalStorage
e PlatformStorage
. Poiché PlatformStorage
esegue il wrapping delle API Jetpack sul servizio di sistema AppSearch, l'impatto sulle dimensioni dell'APK è minimo rispetto all'utilizzo di LocalStorage. Tuttavia, ciò significa anche che le operazioni di AppSearch comportano una latenza aggiuntiva del binder quando viene chiamato il servizio di sistema AppSearch. Con PlatformStorage
, AppSearch limita il numero e le dimensioni dei documenti che un'applicazione può indicizzare per garantire un indice centrale efficiente. PlayServicesStorage
presenta anche le stesse limitazioni di PlatformStorage
ed è supportato solo sui dispositivi con Google Play Services.
Inizia a utilizzare AppSearch
L'esempio in questa sezione mostra come utilizzare le API AppSearch per eseguire l'integrazione con un'ipotetica applicazione per la gestione delle note.
Scrivere una classe di documenti
Il primo passaggio per l'integrazione con AppSearch consiste nello scrivere una classe di documenti per
descrivere i dati da inserire nel database. Contrassegna una classe come classe di documenti utilizzando l'annotazione @Document
.Puoi utilizzare istanze della classe di documenti per inserire documenti nel database e recuperarli.
Il codice seguente definisce una classe di documenti Nota con un
campo annotated
@Document.StringProperty
per l'indicizzazione del testo di un oggetto 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; } }
Aprire un database
Devi creare un database prima di lavorare con i documenti. Il codice seguente
crea un nuovo database con il nome notes_app
e recupera un ListenableFuture
per un AppSearchSession
,
che rappresenta la connessione al database e fornisce le API per
le operazioni del database.
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() );
Imposta uno schema
Devi impostare uno schema prima di poter inserire e recuperare i documenti dal database. Lo schema del database è costituito da diversi tipi di dati strutturati, denominati "tipi di schema". Il seguente codice imposta lo schema fornendo la classe del documento come tipo di schema.
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);
Inserire un documento nel database
Una volta aggiunto un tipo di schema, puoi aggiungere documenti di quel tipo al database.
Il seguente codice crea un documento di tipo di schema Note
utilizzando il generatore di classi di documenti Note
. Imposta lo spazio dei nomi del documento user1
per rappresentare un
utente arbitrario di questo sample. Il documento viene quindi inserito nel database e viene associato un listener per elaborare il risultato dell'operazione 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);
Cerca
Puoi cercare i documenti indicizzati utilizzando le operazioni di ricerca descritte in questa sezione. Il seguente codice esegue query per il termine "frutto" nel database per i documenti che appartengono allo spazio dei nomi 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);
Esegui l'iterazione di SearchResults
Le ricerche restituiscono un'istanza SearchResults
, che consente di accedere alle pagine degli oggetti SearchResult
. Ogni SearchResult
contiene il relativo GenericDocument
corrispondente, la forma generale di un
documento in cui vengono convertiti tutti i documenti. Il seguente codice recupera la prima pagina dei risultati di ricerca e reconverte il risultato in un 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);
Rimuovere un documento
Quando l'utente elimina una nota, l'applicazione elimina il Note
documento corrispondente dal database. In questo modo, la nota non verrà più visualizzata nelle query. Il seguente codice effettua una richiesta esplicita per rimuovere il documento Note
dal database in base all'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);
Mantieni su disco
Gli aggiornamenti di un database devono essere sottoposti a persistenza periodicamente sul disco chiamando
requestFlush()
. Il codice seguente chiama requestFlush()
con un ascoltatore per determinare se la chiamata è andata a buon fine.
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);
Chiudere una sessione
Un AppSearchSession
debe essere chiuso quando un'applicazione non chiama più operazioni di database. Il codice seguente chiude la sessione AppSearch aperta precedentemente e rende permanenti tutti gli aggiornamenti su 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);
Risorse aggiuntive
Per scoprire di più su AppSearch, consulta le seguenti risorse aggiuntive:
Campioni
- Android AppSearch Sample (Kotlin), un'app per prendere appunti che utilizza AppSearch per indicizzare le note di un utente e consente agli utenti di eseguire ricerche nelle loro note.
Fornisci feedback
Condividi con noi i tuoi feedback e le tue idee tramite queste risorse:
Segnala i bug per consentirci di risolverli.