AppSearch adalah solusi penelusuran di perangkat berperforma tinggi untuk mengelola secara lokal data yang terstruktur dan tersimpan. Berisi API untuk mengindeks data dan mengambil data menggunakan pencarian teks lengkap. Aplikasi dapat menggunakan AppSearch untuk menawarkan dalam aplikasi khusus kemampuan penelusuran, yang memungkinkan pengguna menelusuri konten bahkan saat offline.
AppSearch menyediakan fitur berikut:
- Implementasi penyimpanan yang cepat dan berorientasi seluler dengan penggunaan I/O yang rendah
- Pengindeksan dan pembuatan kueri yang sangat efisien melalui set data yang besar
- Dukungan multibahasa, seperti bahasa Inggris dan Spanyol
- Peringkat relevansi dan skor penggunaan
Karena penggunaan I/O yang lebih rendah, AppSearch menawarkan latensi yang lebih rendah untuk pengindeksan dan penelusuran pada set data besar dibandingkan SQLite. AppSearch menyederhanakan kueri lintas jenis dengan mendukung kueri tunggal, sementara SQLite menggabungkan hasil dari beberapa tabel.
Untuk menggambarkan fitur AppSearch, mari kita ambil contoh sebuah musik yang mengelola lagu favorit pengguna dan memungkinkan pengguna untuk mencari kepada mereka. Pengguna menikmati musik dari seluruh dunia dengan judul lagu dalam berbagai bahasa, yang secara native mendukung pengindeksan dan kueri oleh AppSearch. Jika pengguna menelusuri lagu berdasarkan judul atau nama artis, aplikasi akan meneruskan permintaan ke AppSearch untuk mengambil lagu yang cocok dengan cepat dan efisien. Tujuan aplikasi memunculkan hasil, memungkinkan penggunanya untuk mulai bermain dengan cepat lagu favorit mereka.
Penyiapan
Untuk menggunakan AppSearch di aplikasi Anda, tambahkan dependensi berikut ke
file build.gradle
aplikasi:
Groovy
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") }
Konsep AppSearch
Diagram berikut mengilustrasikan konsep AppSearch dan interaksinya.
Database dan sesi
Database AppSearch adalah kumpulan dokumen yang sesuai dengan database skema. Aplikasi klien membuat database dengan menyediakan aplikasi mereka konteks dan nama database. Database hanya dapat dibuka oleh aplikasi yang membuatnya. Saat database dibuka, sesi ditampilkan untuk berinteraksi dengan {i>database<i}. Sesi ini adalah titik entri untuk memanggil API AppSearch dan tetap terbuka hingga ditutup oleh aplikasi klien.
Jenis skema dan skema
Skema mewakili struktur organisasi data dalam AppSearch di skrip untuk menyiapkan database.
Skema terdiri dari jenis skema yang merepresentasikan jenis data yang unik. Jenis skema terdiri dari properti yang berisi nama, tipe data, dan kardinalitas. Setelah jenis skema ditambahkan ke skema {i>database<i}, dokumen dari jenis skema itu dapat dibuat dan ditambahkan ke {i>database<i}.
Dokumen
Di AppSearch, unit data direpresentasikan sebagai dokumen. Setiap dokumen dalam sebuah Database AppSearch diidentifikasi secara unik berdasarkan namespace dan ID-nya. Namespace digunakan untuk memisahkan data dari sumber yang berbeda pada saat hanya satu sumber yang membutuhkan yang akan dikueri, seperti akun pengguna.
Dokumen berisi stempel waktu pembuatan, {i>time to live<i} (TTL), dan skor yang dapat digunakan untuk pemberian peringkat selama pengambilan. Dokumen juga diberi skema yang mendeskripsikan properti data tambahan yang harus dimiliki dokumen.
Class dokumen merupakan abstraksi dari dokumen. Berisi kolom yang dianotasi yang mewakili isi dokumen. Secara default, nama dokumen menetapkan nama jenis skema.
Telusuri
Dokumen diindeks dan dapat ditelusuri dengan memberikan kueri. Dokumen adalah cocok dan dimasukkan dalam hasil penelusuran jika berisi istilah yang ditelusuri atau cocok dengan spesifikasi penelusuran lain. Hasil diurutkan berdasarkan skor dan strategi peringkat Anda. Hasil penelusuran diwakili oleh halaman yang dapat Anda diambil secara berurutan.
AppSearch menawarkan penyesuaian untuk penelusuran, seperti filter, konfigurasi ukuran halaman, dan cuplikan.
Penyimpanan Platform vs Penyimpanan Lokal
AppSearch menawarkan dua solusi penyimpanan: LocalStorage dan PlatformStorage. Dengan LocalStorage, aplikasi Anda mengelola indeks khusus aplikasi yang ada di direktori data aplikasi Anda. Dengan PlatformStorage, aplikasi Anda berkontribusi pada indeks pusat di seluruh sistem. Akses data dalam indeks pusat dibatasi untuk data yang telah dikontribusikan oleh aplikasi Anda dan data yang telah secara eksplisit dibagikan dengan Anda oleh aplikasi lain. {i>LocalStorage<i} dan PlatformStorage memiliki API yang sama dan dapat diganti berdasarkan versi:
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())); }
Dengan PlatformStorage, aplikasi Anda dapat berbagi data secara aman dengan aplikasi agar mereka juga dapat menelusuri data aplikasi Anda. Hanya-baca berbagi data aplikasi diberikan melalui jabatan sertifikat untuk memastikan bahwa aplikasi lain memiliki izin untuk membaca data. Baca selengkapnya tentang API ini dalam dokumentasi untuk setSchemaTypeVisibilityForPackage().
Selain itu, data yang diindeks dapat ditampilkan di platform UI Sistem. Aplikasi dapat memilih untuk tidak mengizinkan sebagian atau semua datanya yang ditampilkan di Sistem platform UI. Baca selengkapnya tentang API ini dalam dokumentasi untuk setSchemaTypeDisplayedBySystem().
Fitur | 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 |
Ada beberapa konsekuensi tambahan yang perlu dipertimbangkan saat memilih antara LocalStorage dan PlatformStorage. Karena PlatformStorage menggabungkan Jetpack API melalui Layanan sistem AppSearch, dampak ukuran APK minimal dibandingkan dengan menggunakan {i>LocalStorage<i}. Namun, hal ini juga berarti operasi AppSearch menimbulkan latensi binder saat memanggil layanan sistem AppSearch. Dengan PlatformStorage, AppSearch membatasi jumlah dokumen dan ukuran dokumen aplikasi dapat diindeks untuk memastikan sebuah indeks pusat yang efisien.
Mulai menggunakan AppSearch
Contoh di bagian ini menunjukkan cara menggunakan API AppSearch untuk mengintegrasikan dengan aplikasi pencatatan hipotetis.
Menulis class dokumen
Langkah pertama untuk mengintegrasikan dengan AppSearch adalah menulis class dokumen ke
menjelaskan data yang akan
dimasukkan ke dalam {i>database<i}. Menandai kelas sebagai class dokumen
menggunakan @Document
anotasi.Anda dapat menggunakan instance dari class dokumen untuk memasukkan dokumen dan
mengambil dokumen dari {i>database<i}.
Kode berikut mendefinisikan class dokumen Note dengan
@Document.StringProperty
dianotasi
untuk mengindeks teks objek Catatan.
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; } }
Membuka database
Anda harus membuat database sebelum menggunakan dokumen. Kode berikut
membuat database baru dengan nama notes_app
dan mendapatkan ListenableFuture
untuk AppSearchSession
,
yang mewakili koneksi ke database dan menyediakan API untuk
operasi {i>database<i}.
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() );
Menetapkan skema
Anda harus menetapkan skema sebelum dapat menyimpan dan mengambil dokumen dokumen dari {i>database<i}. Skema database terdiri dari berbagai jenis data terstruktur, yang disebut sebagai "jenis skema". Kode berikut menetapkan skema dengan menyediakan class dokumen sebagai jenis skema.
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);
Menempatkan dokumen di database
Setelah jenis skema ditambahkan, Anda dapat menambahkan dokumen jenis tersebut ke database.
Kode berikut membangun dokumen dengan jenis skema Note
menggunakan Note
pembuat class dokumen. Perintah ini menetapkan namespace dokumen user1
untuk mewakili
pengguna arbitrer dari sampel ini. Dokumen kemudian disisipkan ke dalam database
dan pemroses dilampirkan untuk memproses hasil operasi 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);
Telusuri
Anda dapat mencari dokumen yang diindeks
menggunakan operasi pencarian yang tercakup dalam
bagian ini. Kode berikut menjalankan kueri untuk istilah "fruit" selama
untuk dokumen yang termasuk dalam namespace 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);
Melakukan iterasi melalui SearchResults
Penelusuran menampilkan SearchResults
, yang memberikan akses ke halaman objek SearchResult
. Setiap SearchResult
menyimpan GenericDocument
yang cocok, bentuk umum
dokumen tempat semua dokumen dikonversi. Kode berikut mengambil gambar
halaman hasil penelusuran dan mengonversi hasilnya kembali menjadi dokumen 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);
Menghapus dokumen
Saat pengguna menghapus catatan, aplikasi akan menghapus Note
yang terkait
dokumen dari {i>database<i}. Hal ini memastikan catatan tidak akan lagi muncul di
terhadap kueri. Kode berikut membuat permintaan eksplisit untuk menghapus Note
dokumen dari database menurut 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);
Mempertahankan ke disk
Pembaruan pada database harus dipertahankan secara berkala ke disk dengan memanggil
requestFlush()
Tujuan
kode berikut memanggil requestFlush()
dengan pemroses untuk menentukan apakah panggilan tersebut
berhasil.
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);
Menutup sesi
AppSearchSession
harus ditutup ketika aplikasi tidak lagi memanggil database apa pun
operasional bisnis. Kode berikut menutup sesi AppSearch yang dibuka
sebelumnya dan mempertahankan
semua pembaruan ke {i>disk<i}.
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);
Referensi lainnya
Untuk mempelajari AppSearch lebih lanjut, lihat referensi tambahan berikut:
Contoh
- Contoh Android AppSearch (Kotlin), aplikasi pembuat catatan yang menggunakan AppSearch untuk mengindeks catatan pengguna dan memungkinkan untuk menelusuri catatan mereka.
Berikan masukan
Sampaikan masukan dan ide Anda kepada kami melalui resource berikut:
Laporkan bug agar kami dapat memperbaikinya.