AppSearch è una soluzione di ricerca on-device ad alte prestazioni per la gestione locale archiviati e strutturati. Contiene API per l'indicizzazione e il recupero dei dati utilizzando la ricerca a testo intero. Le applicazioni possono usare AppSearch per offrire servizi in-app personalizzati che consentono agli utenti di cercare contenuti anche offline.
AppSearch offre le seguenti funzionalità:
- Implementazione rapida dell'archiviazione mobile-first con un basso utilizzo di I/O
- Indicizzazione e query ad alta efficienza su set di dati di grandi dimensioni
- Supporto di più lingue, ad esempio inglese e spagnolo
- Ranking di pertinenza e punteggio di utilizzo
Grazie al minore utilizzo di I/O, AppSearch offre una latenza minore per l'indicizzazione e la ricerca su set di dati di grandi dimensioni rispetto a SQLite. AppSearch semplifica le query tra tipi diversi supportando singole query, mentre SQLite unisce i risultati di più tabelle.
Per illustrare le funzioni di AppSearch, prendiamo l'esempio di un file un'applicazione che gestisce i brani preferiti degli utenti e consente loro di effettuare ricerche per loro. Gli utenti ascoltano musica da tutto il mondo con titoli dei brani in diversi linguaggi di programmazione, per i quali AppSearch supporta l'indicizzazione e le query in modo nativo. 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. La un'applicazione mostra i risultati, consentendo agli utenti di iniziare rapidamente a giocare le loro canzoni preferite.
Configura
Per utilizzare AppSearch nella tua applicazione, aggiungi le seguenti dipendenze al
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.
Database e sessione
Un database AppSearch è una raccolta di documenti conforme al database . Le applicazioni client creano un database fornendo la loro applicazione contesto e un nome del database. I database possono essere aperti solo dall'applicazione che li ha creati. Quando un database viene aperto, viene restituita una sessione per interagire con il database. La sessione è il punto di accesso per chiamare le API AppSearch e rimane aperto finché non viene chiuso dall'applicazione client.
Tipi di schema e schema
Uno schema rappresenta la struttura organizzativa dei dati all'interno di un AppSearch per configurare un database.
Lo schema è composto da tipi di schemi che rappresentano tipi di dati univoci. I tipi di schema sono costituiti da proprietà che contengono un nome, un tipo di dati e cardinalità. Una volta aggiunto un tipo di schema allo schema del database, i documenti il tipo di schema può essere creato e aggiunto al database.
Documenti
In AppSearch, un'unità di dati è rappresentata come un documento. Ogni documento in un Il database AppSearch è identificato in modo univoco dallo spazio dei nomi e dall'ID. Spazi dei nomi vengono utilizzati per separare i dati da origini diverse quando una sola fonte richiede come gli account utente.
I documenti contengono un timestamp di creazione, una durata (TTL) e un punteggio può essere utilizzata per il ranking durante il recupero. A un documento viene anche assegnato uno schema. che descrive le proprietà aggiuntive dei dati che il documento deve avere.
Una classe di documento è un'astrazione di un documento. Contiene campi annotati che rappresentano i contenuti di un documento. Per impostazione predefinita, il nome del documento imposta il nome del tipo di schema.
Cerca
I documenti sono indicizzati e possono essere cercati fornendo una query. Un documento è corrisponde e viene incluso nei risultati di ricerca se contiene i termini della query o corrisponde a un'altra specifica di ricerca. I risultati vengono ordinati in base punteggio e strategia di ranking. I risultati di ricerca sono rappresentati da pagine che puoi in sequenza.
AppSearch offre personalizzazioni per la ricerca, ad esempio filtri, configurazione delle dimensioni della pagina e snippet.
Confronto tra Platform Storage e Local Storage
AppSearch offre due soluzioni di archiviazione: LocalStorage e PlatformStorage. Con LocalStorage, la tua applicazione gestisce un indice specifico dell'app che risiede della directory dei dati dell'applicazione. Con PlatformStorage, la tua applicazione contribuisce a un indice centrale a livello di sistema. Accesso ai dati all'interno dell'indice centrale è limitato ai dati forniti dall'applicazione e ai dati che sono stati esplicitamente condiviso con te da un'altra applicazione. Sia LocalStorage che PlatformStorage condivide la stessa API e può essere scambiata in base alle caratteristiche versione:
Kotlin
if (BuildCompat.isAtLeastS()) { appSearchSessionFuture.setFuture( PlatformStorage.createSearchSession( PlatformStorage.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 { mAppSearchSessionFuture.setFuture(LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); }
Con PlatformStorage, la tua applicazione può condividere dati in modo sicuro per consentire anche loro di eseguire ricerche nei dati delle app. Sola lettura la condivisione dei dati dell'applicazione viene concessa tramite un handshake dei certificati per garantire l'altra applicazione dispone dell'autorizzazione per leggere i dati. Scopri di più su questa API nella documentazione per setSchemaTypeVisibilityForPackage().
Inoltre, i dati indicizzati possono essere visualizzati sulle piattaforme UI di sistema. Le applicazioni possono disattivare la visualizzazione di alcuni o di tutti i dati sul sistema Piattaforme UI. Scopri di più su questa API nella documentazione relativa a setSchemaTypeDisplayedBySystem().
Funzionalità | LocalStorage (compatible with Android 4.0+) |
PlatformStorage (compatible with Android 12+) |
---|---|---|
Efficient full-text search |
||
Multi-language support |
||
Reduced binary size |
||
Application-to-application data sharing |
||
Capability to display data on System UI surfaces |
||
Unlimited document size and count can be indexed |
||
Faster operations without additional binder latency |
Ci sono altri compromessi da considerare nella scelta tra LocalStorage e PlatformStorage. Poiché PlatformStorage esegue il wrapping delle API Jetpack AppSearch, l'impatto sulle dimensioni degli APK è minimo rispetto all'uso LocalStorage. Tuttavia, ciò significa anche che le operazioni AppSearch comportano di latenza di binder quando si chiama il servizio di sistema AppSearch. Con PlatformStorage, AppSearch limita il numero e la dimensione dei documenti di un'applicazione possono indicizzare per garantire un indice centrale efficiente.
Inizia a utilizzare AppSearch
L'esempio in questa sezione mostra come utilizzare le API AppSearch per integrare con un'applicazione ipotetica per la creazione di note.
Scrivi una classe di documenti
Il primo passaggio per l'integrazione con AppSearch è scrivere una classe documento in
descrivere i dati da inserire nel database. Contrassegnare un corso come corso di documenti
utilizzando @Document
annotazioni.Puoi utilizzare istanze della classe document per inserire documenti
per recuperare documenti dal database.
Il codice seguente definisce una classe di documenti Note con un
@Document.StringProperty
annotato
campo per l'indicizzazione del testo di un oggetto 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; } }
Apri un database
Devi creare un database prima di lavorare con i documenti. Il seguente codice
crea un nuovo database con il nome notes_app
e ottiene un ListenableFuture
per una AppSearchSession
,
che rappresenta la connessione al database e fornisce le API per
e operazioni di 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 documenti dal database. Lo schema del database è composto da diversi tipi una quantità di dati strutturati, chiamati "tipi di schema". Il seguente codice imposta fornendo la classe 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);
Inserisci un documento nel database
Dopo aver 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 Note
strumento per la creazione di classi di documenti. Imposta lo spazio dei nomi del documento user1
per rappresentare un
un utente arbitrario di questo campione. Il documento viene quindi inserito nel database
e viene allegato un listener per l'elaborazione del 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 documenti indicizzati utilizzando le operazioni di ricerca descritte in
questa sezione. Il seguente codice esegue query per il termine "frutto" nel
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 tramite i risultati di ricerca
Le ricerche restituiscono un valore SearchResults
che dà accesso alle pagine degli oggetti SearchResult
. Ogni SearchResult
contiene il suo GenericDocument
corrispondente, la forma generale di
documento in cui vengono convertiti tutti i documenti. Il seguente codice riceve il primo
pagina dei risultati di ricerca e converte 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
corrispondente
un documento dal database. In questo modo la nota non verrà più visualizzata
query. Il seguente codice invia una richiesta esplicita di rimozione di Note
documento dal database per 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 resi periodicamente permanenti su disco chiamando
requestFlush()
La
il seguente codice chiama requestFlush()
con un listener 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
dovrebbe essere chiuso quando un'applicazione non chiamerà più alcun database
operazioni. Il seguente codice chiude la sessione AppSearch aperta
in precedenza e tutti gli aggiornamenti
permangono sul 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 saperne di più su AppSearch, consulta le seguenti risorse aggiuntive:
Campioni
- Esempio di Android AppSearch (Kotlin), un'app per prendere appunti che utilizza AppSearch per indicizzare le note di un utente e consente agli utenti per cercare nelle note.
Fornisci feedback
Condividi i tuoi commenti e le tue idee con noi attraverso queste risorse:
Segnala i bug per consentirci di risolverli.