AppSearch

AppSearch は、ローカルで管理するための高性能なオンデバイス検索ソリューションです 構造化データです。データのインデックス登録とデータの取得を行うための API が含まれています。 全文検索を利用できます。アプリは AppSearch を使用してカスタムのアプリ内を提示できる 検索機能により、ユーザーはオフラインでもコンテンツを検索できます。

AppSearch 内でのインデックス登録と検索の図

AppSearch には次の機能があります。

  • I/O 使用率の低い、高速かつモバイル ファーストなストレージの実装
  • 大規模なデータセットに対する効率性に優れたインデックス登録とクエリ
  • 複数言語のサポート(英語やスペイン語など)
  • 関連性ランキングと使用状況スコア

I/O の使用が少ないため、AppSearch はインデックス登録と検索のレイテンシを短縮 SQLite と比較して大規模なデータセットに対するパフォーマンスが優れています。AppSearch で複数タイプのクエリを簡素化 SQLite は複数のテーブルからの結果を結合しますが、SQLite は単一クエリをサポートします。

AppSearch の機能を説明するために、音楽アプリの ユーザーのお気に入りの曲を管理し、簡単に検索できるようにするアプリケーション できます。ユーザーは世界中のさまざまな曲をさまざまなタイトルで楽しむ AppSearch がインデックスとクエリをネイティブにサポートしている言語です。Google ユーザーがタイトルやアーティスト名で曲を検索すると、アプリケーションは リクエストを AppSearch に送信して、一致する曲を迅速かつ効率的に取得します。「 結果が表示され、ユーザーはすぐにゲームを開始できる おすすめします。

設定

アプリケーションで AppSearch を使用するには、 アプリケーションの build.gradle ファイルを次のように変更します。

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

AppSearch のコンセプト

次の図は、AppSearch のコンセプトと相互作用を示しています。

図
クライアント アプリケーションと次の各アプリケーションとの連携の概要を
AppSearch のコンセプト: AppSearch データベース、スキーマ、スキーマタイプ、ドキュメント、
セッション、検索です。 図 1. AppSearch のコンセプトの図: AppSearch データベース、スキーマ、 各スキーマタイプ、ドキュメント、セッション 検索があります

データベースとセッション

AppSearch データベースは、データベースに準拠するドキュメントのコレクション 説明します。クライアント アプリケーションは、アプリケーションを提供してデータベースを作成します。 コンテキストとデータベース名の組み合わせです。データベースはアプリケーションによってのみ開くことができる 作成しました。データベースが開かれると、セッションが返され 関連付けられていますセッションは AppSearch API を呼び出すためのエントリ ポイントです。 クライアント アプリケーションによって閉じられるまで開いたままになります。

スキーマとスキーマタイプ

スキーマは AppSearch 内のデータの組織構造を表す データベースです

スキーマは、一意のデータタイプを表すスキーマタイプで構成されます。 スキーマタイプはプロパティで構成され、名前、データ型、 基づいています。データベース スキーマにスキーマタイプを追加すると、 スキーマタイプを作成してデータベースに追加できます。

ドキュメント

AppSearch では、データの単位はドキュメントとして表されます。1 つのドキュメントに含まれる AppSearch データベースは名前空間と ID によって一意に識別されます。名前空間 1 つのソースのみが必要な場合に、データを異なるソースから分離するために使用する (ユーザー アカウントなど)を指定します。

ドキュメントには、作成時のタイムスタンプ、有効期間(TTL)、スコアが記載されます。 取得時のランキングに使用できます。ドキュメントにはスキーマも割り当てられる ドキュメントが持つ必要がある追加のデータ プロパティを記述する型。

ドキュメント クラスはドキュメントを抽象化したものです。アノテーション付きのフィールドが含まれています。 オブジェクトを表す名前です。デフォルトでは、ドキュメントの名前は クラスはスキーマタイプの名前を設定します。

ドキュメントはインデックスに登録され、クエリを指定して検索できます。ドキュメントとは 一致したもの(クエリ内の語句を含む場合)が検索結果に含まれる 一致します。結果は、 スコアとランキング戦略が含まれます。検索結果はページで表示されます。 取得する必要があります。

AppSearch のカスタマイズ (フィルタ、ページサイズの設定、スニペットなど)をカスタマイズできます。

プラットフォーム ストレージとローカル ストレージ

AppSearch には、LocalStorage と PlatformStorage の 2 つのストレージ ソリューションがあります。 LocalStorage を使用すると、アプリケーションは、 アプリケーションのデータ ディレクトリに配置します。PlatformStorage では、 システム全体の中央インデックスに寄与します。中央インデックス内のデータアクセス 使用できるデータは、アプリケーションによって提供されたデータと、 明示的に共有することもできます。LocalStorage と PlatformStorage は同じ API を共有し、デバイスの 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()));
}

PlatformStorage を使用すると、アプリケーションは他のクラウド プロバイダと安全にデータを アプリのデータを検索することもできます。読み取り専用 アプリケーションのデータ共有は証明書ハンドシェイクを介して許可され、 相手側アプリはデータの読み取り権限を持っています。この API の詳細 setSchemaTypeVisibilityForPackage() のドキュメントをご覧ください。

また、インデックスに登録されたデータは、システム UI サーフェスに表示できます。 アプリはシステム上に表示されるデータの一部または全部をオプトアウトできます 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 は、すべてのコンテナで Jetpack API をラップするため、 AppSearch システム サービスを使用するため、APK サイズへの影響は、 LocalStorage。ただし、この場合、AppSearch オペレーションによって追加の AppSearch システム サービスを呼び出すときのバインダー レイテンシ。PlatformStorage を使用すると AppSearch は、アプリケーションに含まれるドキュメントの数とサイズを制限する インデックスを作成して、効率的な中央インデックスを確保できます。

AppSearch を使ってみる

このセクションの例では、AppSearch API を使用して アプリケーションの例を見てみましょう。

ドキュメント クラスを作成する

AppSearch を統合するための最初のステップは、 データベースに挿入するデータを記述します。クラスをドキュメント クラスとしてマークする @Document を使用 アノテーション。ドキュメント クラスのインスタンスを使用して、ドキュメントを データベースからドキュメントを取得します

次のコードは、 @Document.StringProperty アノテーション付き フィールドを使用して、メモ オブジェクトのテキストをインデックス登録します。

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

インデックスに登録されているドキュメントは、このモジュールで取り上げている検索オペレーションを使用して検索できます。 見てみましょう。次のコードは、「fruit」という用語に対するクエリを実行します。 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 を削除します。 データベースから抽出しますこれにより、メモは 分析できます次のコードは、Note を削除することを明示的にリクエストします。 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);

ディスクに保存

データベースの更新は、 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

Google が修正できるようにバグを報告してください。