App-Suche

AppSearch ist eine leistungsstarke On-Device-Suchlösung für die lokale Verwaltung gespeicherten, strukturierten Daten. Es enthält APIs zum Indexieren von Daten und zum Abrufen von Daten. mit Volltextsuche. Apps können über AppSearch benutzerdefinierte In-App-Anzeigen anbieten Suchfunktionen, mit denen Nutzer auch offline nach Inhalten suchen können.

Diagramm zur Indexierung und Suche in AppSearch

AppSearch bietet die folgenden Funktionen:

  • Eine schnelle Mobile-First-Speicherimplementierung mit geringer E/A-Nutzung
  • Hocheffiziente Indexierung und Abfrage großer Datensätze
  • Unterstützung in mehreren Sprachen, z. B. Englisch und Spanisch
  • Relevanzranking und Nutzungsbewertung

Aufgrund der geringeren E/A-Nutzung bietet AppSearch eine geringere Latenz bei der Indexierung und Suche. im Vergleich zu SQLite durchführen. AppSearch vereinfacht typübergreifende Abfragen durch Unterstützung einzelner Abfragen, während SQLite Ergebnisse aus mehreren Tabellen zusammenführt.

Nehmen wir zur Veranschaulichung der Funktionen von AppSearch am Beispiel einer Musik, App, die die Lieblingssongs der Nutzer verwaltet und Nutzern die einfache Suche ermöglicht für sie. Nutzer mögen Musik aus der ganzen Welt mit Songtiteln in verschiedenen Sprachen, für die AppSearch nativ die Indexierung und Abfrage unterstützt. Wenn der Parameter nach einem Titel anhand des Titels oder Künstlernamens sucht, wird die App die Anfrage an AppSearch, um schnell und effizient übereinstimmende Titel zu finden. Die Anwendung präsentiert die Ergebnisse, sodass die Nutzer schnell mit dem Spielen beginnen können. ihre Lieblingssongs zu hören.

Einrichten

Fügen Sie zur Verwendung von AppSearch in Ihrer Anwendung die folgenden Abhängigkeiten zu Ihrem Datei build.gradle der Anwendung:

Groovy

dependencies {
    def appsearch_version = "1.1.0-alpha06"

    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-alpha06"

    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")
}

AppSearch-Konzepte

Das folgende Diagramm veranschaulicht AppSearch-Konzepte und ihre Interaktionen.

Diagramm
Darstellung einer Clientanwendung und ihrer Interaktionen mit
AppSearch-Konzepte: AppSearch-Datenbank, -Schema, Schematypen, Dokumente
Sitzung und Suche. Abbildung 1: Diagramm der AppSearch-Konzepte: AppSearch-Datenbank, -Schema, Schematypen, Dokumente, Sitzung und Suche.

Datenbank und Sitzung

Eine AppSearch-Datenbank ist eine Sammlung von Dokumenten, die der Datenbank entspricht Schema. Clientanwendungen erstellen eine Datenbank, indem sie ihre Anwendung bereitstellen Kontext und einen Datenbanknamen. Datenbanken können nur von der Anwendung geöffnet werden der sie erstellt hat. Beim Öffnen einer Datenbank wird eine Sitzung zurückgegeben, um zu interagieren mit der Datenbank. Die Sitzung ist der Einstiegspunkt für den Aufruf der AppSearch APIs und bleibt geöffnet, bis sie von der Clientanwendung geschlossen wird.

Schema und Schematypen

Ein Schema stellt die Organisationsstruktur der Daten in einer AppSearch dar Datenbank.

Das Schema besteht aus Schematypen, die eindeutige Datentypen darstellen. Schematypen bestehen aus Attributen, die einen Namen, einen Datentyp und Kardinalität. Sobald dem Datenbankschema ein Schematyp hinzugefügt wurde, kann dieser Schematyp erstellt und der Datenbank hinzugefügt werden.

Dokumente

In AppSearch wird eine Dateneinheit als Dokument dargestellt. Jedes Dokument in einem Die AppSearch-Datenbank wird durch ihren Namespace und ihre ID eindeutig identifiziert. Namespaces werden verwendet, um Daten aus verschiedenen Quellen zu trennen, wenn nur eine Quelle abgefragt werden sollen, z. B. Nutzerkonten.

Die Dokumente enthalten einen Erstellungszeitstempel, eine Gültigkeitsdauer (TTL) und einen Wert, der kann beim Abruf für das Ranking verwendet werden. Einem Dokument wird auch ein Schema zugewiesen -Typ, der zusätzliche Dateneigenschaften beschreibt, die das Dokument haben muss.

Eine Dokumentklasse ist eine Abstraktion eines Dokuments. Enthält annotierte Felder die den Inhalt eines Dokuments darstellen. Standardmäßig wird der Name des Dokuments -Klasse legt den Namen des Schematyps fest.

Dokumente werden indexiert und können über eine Abfrage durchsucht werden. Ein Dokument ist als Übereinstimmung und in den Suchergebnissen enthalten, wenn sie die Begriffe aus der Suchanfrage enthält. oder einer anderen Suchspezifikation entspricht. Die Ergebnisse werden nach Faktor- und Ranking-Strategie. Suchergebnisse werden durch Seiten dargestellt, die Sie sequenziell abrufen können.

AppSearch bietet Anpassungen für die Suche, z. B. Filter, Konfiguration der Seitengröße und Snippeting.

Plattformspeicher vs. lokaler Speicher

AppSearch bietet zwei Speicherlösungen: LocalStorage und PlatformStorage. Mit LocalStorage verwaltet Ihre Anwendung einen anwendungsspezifischen Index, der sich Ihr Anwendungsdatenverzeichnis. Mit PlatformStorage kann Ihre Anwendung zu einem systemweiten zentralen Index beiträgt. Datenzugriff im zentralen Index ist auf Daten beschränkt, die Ihre Anwendung beigesteuert hat, sowie auf Daten, die die von einer anderen Anwendung explizit für Sie freigegeben wurden. Sowohl LocalStorage als auch PlatformStorage nutzt dieselbe API und kann je nach Version:

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

Mit PlatformStorage kann Ihre Anwendung Daten sicher mit anderen um auch die Daten Ihrer App zu durchsuchen. Schreibgeschützt Die Freigabe von Anwendungsdaten wird über einen Zertifikat-Handshake gewährt, um sicherzustellen, hat die andere App die Berechtigung, die Daten zu lesen. Weitere Informationen zu dieser API in der Dokumentation zu setSchemaTypeVisibilityForPackage().

Außerdem können indexierte Daten auf System-UI-Oberflächen angezeigt werden. Apps können die Anzeige einiger oder aller ihrer Daten im System deaktivieren UI-Oberflächen. Weitere Informationen zu dieser API finden Sie in der Dokumentation zu setSchemaTypeDisplayedBySystem().

Funktionen 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

Es gibt zusätzliche Kompromisse, die bei der Wahl zwischen LocalStorage und und PlatformStorage. Da PlatformStorage die Jetpack-APIs über die AppSearch-Systemdienst verwenden, ist die Auswirkung auf die APK-Größe im Vergleich zur Verwendung LocalStorage auf. Das bedeutet jedoch auch, dass AppSearch-Vorgänge zusätzliche Binder-Latenz beim Aufrufen des AppSearch-Systemdienstes. Mit PlatformStorage AppSearch schränkt die Anzahl der Dokumente und die Größe der Dokumente in einer Anwendung ein um einen effizienten zentralen Index zu gewährleisten.

Erste Schritte mit AppSearch

Das Beispiel in diesem Abschnitt zeigt, wie AppSearch APIs zur Integration von mit einer hypothetischen Notizen-Anwendung.

Dokumentklasse schreiben

Der erste Schritt zur Integration mit AppSearch besteht darin, eine Dokumentklasse beschreiben die Daten, die in die Datenbank eingefügt werden sollen. Kurse als Dokumentklasse markieren mit dem @Document Anmerkung.Sie können Instanzen der Dokumentklasse verwenden, um Dokumente in Dokumente aus der Datenbank abzurufen.

Der folgende Code definiert eine Note-Dokumentklasse mit einem @Document.StringProperty kommentiert -Feld zum Indexieren des Textes eines Notizobjekts.

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

Datenbank öffnen

Sie müssen eine Datenbank erstellen, bevor Sie mit Dokumenten arbeiten. Der folgende Code erstellt eine neue Datenbank mit dem Namen notes_app und ruft eine ListenableFuture ab für AppSearchSession die die Verbindung zur Datenbank darstellt und die APIs für die Datenbankvorgänge.

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

Schema festlegen

Sie müssen ein Schema festlegen, bevor Sie Dokumente einfügen und abrufen können aus der Datenbank. Das Datenbankschema besteht aus verschiedenen Typen von strukturierten Daten, die als "Schematypen" bezeichnet werden. Mit dem folgenden Code wird die Schema, indem Sie die Dokumentklasse als Schematyp angeben.

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

Dokument in die Datenbank speichern

Nachdem ein Schematyp hinzugefügt wurde, können Sie der Datenbank Dokumente dieses Typs hinzufügen. Mit dem folgenden Code wird ein Dokument des Schematyps Note mithilfe von Note erstellt Document Class Builder. Er legt den Dokument-Namespace user1 so fest, dass er eine Nutzer dieses Beispiels. Das Dokument wird dann in die Datenbank eingefügt. und ein Listener wird angehängt, um das Ergebnis des Put-Vorgangs zu verarbeiten.

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

Sie können mit den in den folgenden Abschnitten beschriebenen Suchvorgängen nach Dokumenten suchen, die indexiert wurden: diesem Abschnitt. Mit dem folgenden Code werden Abfragen nach dem Begriff „Obst“ durchgeführt. über den Datenbank für Dokumente, die zum Namespace user1 gehören.

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

Über SearchResults iterieren

Bei der Suche wird ein SearchResults zurückgegeben -Instanz, die Zugriff auf die Seiten von SearchResult-Objekten gewährt. Jeweils SearchResult enthält die übereinstimmenden GenericDocument, die allgemeine Form eines Dokument, in das alle Dokumente konvertiert werden. Mit dem folgenden Code wird die erste Seite der Suchergebnisse und wandelt das Ergebnis wieder in ein Note-Dokument um.

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

Dokument entfernen

Wenn der Nutzer einen Hinweis löscht, löscht die Anwendung die entsprechende Note. aus der Datenbank. Dadurch wird die Notiz nicht mehr in Abfragen. Mit dem folgenden Code wird eine explizite Anfrage zum Entfernen des Note gesendet. Dokument aus der Datenbank nach 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);

Auf Laufwerk beibehalten

Aktualisierungen einer Datenbank sollten regelmäßig auf dem Laufwerk gespeichert werden. Rufen Sie dazu folgenden Befehl auf: requestFlush() Die der folgende Code ruft requestFlush() mit einem Listener auf, um festzustellen, ob der Aufruf war erfolgreich.

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

Sitzung schließen

Ein AppSearchSession sollte geschlossen werden, wenn eine Anwendung keine Datenbank mehr aufruft Geschäftsabläufe. Mit dem folgenden Code wird die geöffnete AppSearch-Sitzung geschlossen und speichert alle Updates auf dem Laufwerk.

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

Weitere Informationen

Weitere Informationen zu AppSearch finden Sie in den folgenden zusätzlichen Ressourcen:

Produktproben

  • Android AppSearch-Beispiel (Kotlin) eine Notizen-App, die mithilfe von AppSearch die Notizen eines Nutzers indexiert und es Nutzenden ermöglicht um ihre Notizen zu durchsuchen.

Feedback geben

Wir freuen uns über dein Feedback und deine Ideen:

Problemverfolgung

Melde Fehler, damit wir sie beheben können.