AppSearch 是高效能的裝置端搜尋解決方案,可讓使用者在本機管理 儲存的資料其中包含用於索引資料及擷取資料的 API 。應用程式可以利用 AppSearch 提供自訂的應用程式內功能 搜尋功能讓使用者可以搜尋內容,即使處於離線狀態也沒問題。
![圖表:說明 AppSearch 中建立索引和搜尋功能](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?authuser=0000&hl=zh-tw)
AppSearch 提供以下功能:
- 快速導入行動裝置優先的儲存空間實作項目,同時低 I/O 用量
- 更有效率地為大型資料集建立索引及查詢資料
- 支援多種語言,例如英文和西班牙文
- 關聯性排名和使用評分
AppSearch 降低了 I/O 的使用率,因此能縮短建立索引和搜尋的延遲時間。 比起 SQLite 適合大量資料集AppSearch 可簡化跨類型查詢 支援單一查詢,而 SQLite 則會合併多個資料表的結果。
我們以音樂為例,說明 AppSearch 的功能 這款應用程式可讓使用者輕鬆搜尋喜愛的歌曲,並輕鬆搜尋 。使用者喜歡有不同歌名,而且喜歡世界各地的音樂 AppSearch 原本就支援建立索引和查詢的語言。當 使用者搜尋歌曲的歌名或藝人姓名時,應用程式只會傳送 向 AppSearch 提出要求,以便快速有效地擷取相符的歌曲。 應用程式會顯示結果,方便使用者快速開始播放 他們最愛的歌曲
設定
如要在應用程式中使用 AppSearch,請將下列依附元件新增至
應用程式的 build.gradle
檔案:
Groovy
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") }
AppSearch 概念
下圖說明 AppSearch 的概念及其互動方式。
圖 1. AppSearch 概念圖表:AppSearch 資料庫、結構定義、
以及結構定義類型、文件、工作階段和搜尋
資料庫和工作階段
AppSearch 資料庫是符合資料庫的一組文件。 結構定義。用戶端應用程式透過提供自己的應用程式來建立資料庫 結構定義與資料庫名稱資料庫只能由應用程式開啟 打造而成資料庫開啟時,系統會傳回工作階段來進行互動 與資料庫互動工作階段是呼叫 AppSearch API 的進入點 並保持開啟狀態,直到用戶端應用程式關閉為止。
結構定義與結構定義類型
結構定義代表 AppSearch 內的資料機構架構 資料庫
結構定義由代表不重複資料類型的結構定義類型組成。 結構定義類型包含包含名稱、資料類型和 基數。將結構定義類型新增至資料庫結構定義後, 則可以建立結構定義類型並新增至資料庫
文件
在 AppSearch 中,資料單位是以文件表示。「 AppSearch 資料庫主要由其命名空間和 ID 識別。命名空間 只有在只有一個來源需要時,才會區分出不同來源的資料 例如使用者帳戶
文件含有建立時間戳記、存留時間 (TTL),以及 可在擷取期間進行排名文件已獲派結構定義 說明文件必須具備的其他資料屬性的型別。
文件類別是文件的抽象化機制,其中包含已加註的欄位 代表文件內容根據預設,文件名稱 類別會設定結構定義類型的名稱。
搜尋
文件已編入索引,且可透過提供查詢搜尋。文件是 時,如果其中包含查詢的字詞,就會納入搜尋結果 或符合其他搜尋規格結果是依據 分數和排名策略搜尋結果會以網頁呈現 依序擷取
AppSearch 提供自訂功能 ,例如篩選器、網頁大小設定和程式碼片段
平台儲存空間與本機儲存空間
AppSearch 提供兩種儲存空間解決方案:LocalStorage 和 PlatformStorage。 應用程式可透過 LocalStorage 管理位於 應用程式資料目錄有了 PlatformStorage 產生了整個系統的中央索引。集中式索引中的資料存取權 只能使用 由其他應用程式明確指定與你分享的項目。LocalStorage 和 PlatformStorage 共用相同的 API,且可根據裝置的 版本:
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())); }
使用 PlatformStorage 時,您的應用程式可安全地與他人分享資料 ,讓他們搜尋您應用程式的資料。唯讀 應用程式資料分享是透過憑證握手授權,以確保 其他應用程式擁有讀取資料的權限。進一步瞭解這個 API setSchemaTypeVisibilityForPackage()說明文件。
此外,已建立索引的資料會顯示在系統 UI 介面上。 應用程式可以選擇不顯示其部分或全部資料。 使用者介面介面。如要進一步瞭解這個 API,請參閱 setSchemaTypeDisplayedBySystem() 的說明文件。
功能 | 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 |
選擇 LocalStorage 時,需考量其他優缺點 以及 PlatformStorage由於 PlatformStorage 會納入 AppSearch 系統服務,比起使用 本機儲存空間:不過,這也表示 AppSearch 作業會產生額外的 呼叫 AppSearch 系統服務時的繫結器延遲。透過 PlatformStorage AppSearch 會限制應用程式的文件數量和文件大小 能夠建立索引,確保有效率的中央索引
開始使用 AppSearch
本節範例說明如何使用 AppSearch API 進行整合 以假定的筆記應用程式為例
撰寫文件類別
與 AppSearch 整合的第一步是編寫文件類別
描述要插入資料庫的資料將課程標示為文件類別
使用 @Document
註解。您可以使用文件類別的執行個體,將文件移入和
從資料庫擷取文件
以下程式碼使用
已加註 @Document.StringProperty
欄位,用來為 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; } }
開啟資料庫
您必須先建立資料庫,才能處理文件。以下程式碼
建立名為 notes_app
的新資料庫,並取得 ListenableFuture
適用於 AppSearchSession
,
代表與資料庫的連線
和資料庫作業
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() );
設定結構定義
您必須先設定結構定義,才能將文件放入和擷取 建立來自資料庫的多個文件資料庫結構定義包含多種類型 稱為「結構定義類型」下方程式碼會設定 結構定義類型來提供文件類別。
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);
將文件放到資料庫中
新增結構定義類型後,即可將該類型的文件新增至資料庫。
以下程式碼使用 Note
建構結構定義類型為 Note
的文件
文件類別建構工具這會將文件命名空間 user1
設為
此範例的任意使用者接著,系統會將文件插入資料庫
系統會附加一個事件監聽器,用於處理 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);
搜尋
您可以使用下列搜尋作業,搜尋已建立索引的文件:
本節下列程式碼會查詢「水果」一詞期間
新增至 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);
透過 SearchResults 疊代
搜尋會傳回 SearchResults
執行個體,用於存取 SearchResult
物件頁面。每SearchResult
包含相符 GenericDocument
,也就是
所有文件都會轉換為原始文件。下列程式碼會取得
並將結果轉換回 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);
移除文件
當使用者刪除記事時,應用程式會刪除對應的 Note
從資料庫中擷取出文件這可確保記事不會再顯示
舉個簡單的例子,您可以定義情境
並指示 AI 如何回應服務中心查詢以下程式碼發出明確要求來移除 Note
選出來自資料庫的文件
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);
保留至磁碟
建議您透過呼叫
requestFlush()
。
以下程式碼會使用事件監聽器呼叫 requestFlush()
,以判斷呼叫是否
成功。
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);
關閉工作階段
AppSearchSession
如果應用程式不再呼叫任何資料庫,則應關閉
作業。下列程式碼會關閉先前開啟的 AppSearch 工作階段
會保留所有磁碟更新
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);
其他資源
如要進一步瞭解 AppSearch,請參閱下列其他資源:
範例
- Android AppSearch 範例 (Kotlin), 一款記事應用程式,可透過 AppSearch 為使用者的筆記建立索引,並允許使用者 搜尋他們的記事
提供意見
歡迎透過下列資源與我們分享意見和想法:
回報錯誤,方便我們修正。