AppSearch to wydajne rozwiązanie do wyszukiwania na urządzeniu służące do zarządzania lokalnie przechowywanymi uporządkowanymi danymi. Zawiera interfejsy API do indeksowania danych i pobierania danych za pomocą wyszukiwania pełnotekstowego. Aplikacje mogą korzystać z AppSearch, aby oferować własne funkcje wyszukiwania w aplikacji, dzięki czemu użytkownicy mogą wyszukiwać treści nawet bez połączenia z internetem.
![Diagram pokazujący indeksowanie i wyszukiwanie w AppSearch](https://developer.android.com/static/images/guide/topics/search/appsearch.png?authuser=6&hl=pl)
AppSearch zapewnia następujące funkcje:
- Szybka implementacja pamięci masowej przeznaczona na urządzenia mobilne o niskim wykorzystaniu wejścia/wyjścia
- Efektywne indeksowanie dużych zbiorów danych i wykonywanie na nich zapytań
- obsługa wielu języków, takich jak angielski i hiszpański.
- Ranking trafności i ocena wykorzystania
Ze względu na mniejsze wykorzystanie operacji wejścia-wyjścia usługa AppSearch oferuje krótszy czas oczekiwania przy indeksowaniu i wyszukiwaniu dużych zbiorów danych w porównaniu z SQLite. AppSearch upraszcza zapytania krzyżowe, obsługując pojedyncze zapytania, natomiast SQLite scala wyniki z wielu tabel.
Aby zilustrować funkcje AppSearch, przyjrzyjmy się aplikacji muzycznej, która zarządza ulubionymi utworami użytkowników i umożliwia ich łatwe wyszukiwanie. Użytkownicy słuchają muzyki z całego świata i mają tytuły utworów w różnych językach. AppSearch natywnie obsługuje indeksowanie i wyszukiwanie haseł. Gdy użytkownik wyszukuje utwór według tytułu lub nazwy wykonawcy, aplikacja po prostu przekazuje żądanie do AppSearch, aby szybko i sprawnie pobrać pasujące utwory. Aplikacja wyświetla wyniki, umożliwiając użytkownikom szybkie rozpoczęcie odtwarzania ulubionych utworów.
Skonfiguruj
Aby użyć AppSearch w swojej aplikacji, dodaj te zależności do jej pliku build.gradle
:
Odlotowy
dependencies { def appsearch_version = "1.1.0-alpha04" 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-alpha04" 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") }
Pojęcia związane z AppSearch
Poniższy diagram przedstawia pojęcia związane z AppSearch i ich interakcje.
Rysunek 1. Diagram pojęć związanych z AppSearch: baza danych, schemat, typy schematów, dokumenty, sesja i wyszukiwanie.
Baza danych i sesja
Baza danych AppSearch to zbiór dokumentów zgodnych ze schematem bazy danych. Aplikacje klienckie tworzą bazę danych, podając kontekst aplikacji i nazwę bazy danych. Bazy danych można otwierać tylko przez aplikację, która je utworzyła. Po otwarciu bazy danych zwracana jest sesja umożliwiająca interakcję z bazą danych. Sesja jest punktem wejścia do wywoływania interfejsów AppSearch API i pozostaje otwarta, dopóki aplikacja kliencka nie zamknie jej.
Typy schematów i schematów
Schemat reprezentuje strukturę organizacyjną danych w bazie danych AppSearch.
Schemat składa się z typów schematów, które reprezentują unikalne typy danych. Typy schematów składają się z właściwości zawierających nazwę, typ danych i moc zbioru. Po dodaniu typu schematu do schematu bazy danych można tworzyć dokumenty tego typu i dodawać je do bazy danych.
Dokumenty
W AppSearch jednostka danych jest przedstawiana jako dokument. Każdy dokument w bazie danych AppSearch jest jednoznacznie identyfikowany przez przestrzeń nazw i identyfikator. Przestrzenie nazw służą do oddzielania danych z różnych źródeł, gdy konieczne jest przeszukiwanie tylko jednego źródła, na przykład kont użytkowników.
Dokumenty zawierają sygnaturę czasową utworzenia, czas życia danych (TTL) i wynik, który może służyć do określania pozycji dokumentów w rankingu podczas pobierania. Do dokumentu jest też przypisany typ schematu, który opisuje dodatkowe właściwości danych, jakie musi on mieć.
Klasa dokumentu to abstrakcja dokumentu. Zawiera pola z adnotacjami, które odpowiadają zawartości dokumentu. Domyślnie nazwa klasy dokumentu określa nazwę typu schematu.
Szukaj
Dokumenty są indeksowane i można je wyszukiwać, podając zapytanie. Dokument jest uwzględniany i uwzględniany w wynikach wyszukiwania, jeśli zawiera wyrażenia w zapytaniu lub spełnia inne kryteria wyszukiwania. Wyniki są uporządkowane na podstawie wyniku i strategii ustalania pozycji. Wyniki wyszukiwania są reprezentowane przez strony, które można pobierać sekwencyjnie.
AppSearch udostępnia dostosowania wyszukiwania, takie jak filtry, konfiguracja rozmiaru strony i fragmenty kodu.
Miejsce na dane na platformie a pamięć lokalna
AppSearch oferuje dwa rozwiązania do przechowywania danych: LocalStorage i PlatformStorage. Dzięki LocalStorage aplikacja zarządza indeksem dotyczącym aplikacji, który znajduje się w katalogu danych aplikacji. Dzięki PlatformStorage Twoja aplikacja wpływa na centralny indeks całego systemu. Dostęp do danych w indeksie centralnym jest ograniczony do danych udostępnionych przez Twoją aplikację oraz danych udostępnionych Ci bezpośrednio przez inną aplikację. Lokalna pamięć masowa i PlatformStorage korzystają z tego samego interfejsu API i mogą być wymieniane w zależności od wersji urządzenia:
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())); }
Dzięki PlatformStorage Twoja aplikacja może bezpiecznie udostępniać dane innym aplikacjom, aby umożliwić im przeszukiwanie danych aplikacji. Udostępnianie danych aplikacji tylko do odczytu odbywa się przez uzgadnianie połączenia certyfikatu, aby zapewnić, że inna aplikacja ma uprawnienia do odczytu danych. Więcej informacji o tym interfejsie API znajdziesz w dokumentacji funkcji setSchemaTypeAvailabilityForPackage().
Dodatkowo zindeksowane dane mogą być wyświetlane na platformach UI systemu. Aplikacje mogą zrezygnować z wyświetlania niektórych lub wszystkich swoich danych na platformach UI systemu. Więcej informacji o tym interfejsie API znajdziesz w dokumentacji funkcji setSchemaTypeDisplayedBySystem().
Funkcje | 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 |
Wybór między usługami LocalStorage i PlatformStorage wymaga rozważenia dodatkowych kompromisów. Platform Storage pakuje interfejsy API Jetpack z usługą systemową AppSearch, więc wpływ na rozmiar pliku APK jest minimalny w porównaniu z użyciem LocalStorage. Oznacza to jednak również, że operacje AppSearch ponoszą dodatkowe opóźnienie podczas wywoływania usługi systemowej AppSearch. Dzięki PlatformStorage AppSearch ogranicza liczbę dokumentów i rozmiar dokumentów, które aplikacja może indeksować, aby zapewnić wydajny indeks centralny.
Wprowadzenie do AppSearch
Przykład w tej sekcji pokazuje, jak za pomocą interfejsów API AppSearch przeprowadzić integrację z hipotetyczną aplikacją do przechowywania notatek.
Napisz klasę dokumentu
Pierwszym krokiem do przeprowadzenia integracji z AppSearch jest napisanie klasy dokumentu do opisania danych, które mają zostać wstawione do bazy danych. Oznacz klasę jako klasę dokumentu za pomocą adnotacji @Document
.Za pomocą instancji klasy dokumentu możesz umieszczać dokumenty i pobierać dokumenty z bazy danych.
Poniższy kod określa klasę dokumentu Notatnika z polem z adnotacją @Document.StringProperty
do indeksowania tekstu obiektu notatki.
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; } }
Otwieranie bazy danych
Przed rozpoczęciem pracy z dokumentami musisz utworzyć bazę danych. Poniższy kod tworzy nową bazę danych o nazwie notes_app
i pobiera ListenableFuture
dla elementu AppSearchSession
, który reprezentuje połączenie z bazą danych i udostępnia interfejsy API do operacji na bazie danych.
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() );
Ustaw schemat
Aby umieszczać dokumenty w bazie danych i pobierać je z bazy danych, musisz skonfigurować schemat. Schemat bazy danych składa się z różnych typów uporządkowanych danych nazywanych „typami schematu”. Poniższy kod określa schemat, podając klasę dokumentu jako typ schematu.
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);
Umieść dokument w bazie danych
Po dodaniu schematu możesz dodać do bazy danych dokumenty tego typu.
Poniższy kod umożliwia utworzenie dokumentu typu schematu Note
za pomocą konstruktora klas dokumentu Note
. Ustawia ona przestrzeń nazw dokumentu user1
, która reprezentuje dowolnego użytkownika tego przykładu. Następnie dokument jest wstawiany do bazy danych i dołączany do odbiornika, który przetwarza wynik operacji „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);
Szukaj
Dokumenty zindeksowane możesz wyszukiwać, korzystając z operacji wyszukiwania opisanych w tej sekcji. Poniższy kod wykonuje zapytania o termin „owoc” nad bazą danych dokumentów należących do przestrzeni nazw 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);
Powtórzenie wyników wyszukiwania
Wyszukiwania zwracają wystąpienie SearchResults
, które zapewnia dostęp do stron obiektów SearchResult
. Każdy SearchResult
zawiera dopasowane do siebie GenericDocument
, czyli ogólną formę dokumentu, na który są konwertowane wszystkie dokumenty. Ten kod pobiera pierwszą stronę wyników wyszukiwania i konwertuje wynik z powrotem na dokument 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);
Usuwanie dokumentu
Gdy użytkownik usunie notatkę, aplikacja usunie z bazy danych odpowiedni dokument Note
. Dzięki temu notatka nie będzie się już pojawiać w zapytaniach. Ten kod wysyła jawne żądanie usunięcia dokumentu Note
z bazy danych za pomocą jego identyfikatora.
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);
Zachowuj na dysku
Aktualizacje bazy danych powinny być okresowo zapisywane na dysku przez wywołanie requestFlush()
. Ten kod wywołuje funkcję requestFlush()
za pomocą detektora, aby określić, czy wywołanie się powiodło.
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);
Zamykanie sesji
Obiekt AppSearchSession
powinien być zamknięty, gdy aplikacja nie będzie już wywoływać żadnych operacji bazy danych. Poniższy kod zamyka wcześniej otwartą sesję AppSearch i utrzymuje wszystkie aktualizacje dysku.
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);
Dodatkowe materiały
Więcej informacji o AppSearch znajdziesz w tych dodatkowych materiałach:
Próbki
- Android AppSearch Sample (Kotlin) – aplikacja do robienia notatek, która używa AppSearch do indeksowania notatek użytkownika i umożliwia wyszukiwanie ich.
Prześlij opinię
Podziel się z nami swoją opinią i pomysłami, korzystając z tych zasobów:
Zgłoś błędy, abyśmy mogli je naprawić.