AppSearch

AppSearch 是高效能的裝置端搜尋解決方案,可讓使用者在本機管理 儲存的資料其中包含用於索引資料及擷取資料的 API 。應用程式可以利用 AppSearch 提供自訂的應用程式內功能 搜尋功能讓使用者可以搜尋內容,即使處於離線狀態也沒問題。

圖表:說明 AppSearch 中建立索引和搜尋功能

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 的概念及其互動方式。

示意圖
用戶端應用程式的概述內容,以及應用程式與下列項目的互動方式
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,請參閱下列其他資源:

範例

提供意見

歡迎透過下列資源與我們分享意見和想法:

Issue Tracker

回報錯誤,方便我們修正。