Ricerca applicazione

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.

Diagramma che illustra l'indicizzazione e la ricerca in AppSearch

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.

Diagramma
panoramica di un'applicazione client e le sue interazioni con i seguenti elementi
Concetti di AppSearch: database, schema, tipi di schema, documenti di AppSearch
sessione e ricerca. Figura 1. Diagramma dei concetti di base di AppSearch: database AppSearch, schema tipi di schemi, documenti, sessione e ricerca.

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.

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);

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

Fornisci feedback

Condividi i tuoi commenti e le tue idee con noi attraverso queste risorse:

Issue Tracker

Segnala i bug per consentirci di risolverli.