AppSearch

AppSearch là một giải pháp tìm kiếm trên thiết bị có hiệu suất cao để quản lý cục bộ được lưu trữ, có cấu trúc. Tệp này chứa các API để lập chỉ mục dữ liệu và truy xuất dữ liệu bằng cách sử dụng phương thức tìm kiếm toàn bộ văn bản. Các ứng dụng có thể sử dụng AppSearch để cung cấp tìm kiếm, cho phép người dùng tìm kiếm nội dung ngay cả khi không có mạng.

Sơ đồ minh hoạ hoạt động lập chỉ mục và tìm kiếm trong AppSearch

AppSearch cung cấp các tính năng sau:

  • Triển khai bộ nhớ nhanh, ưu tiên thiết bị di động với ít sử dụng I/O
  • Lập chỉ mục và truy vấn mang lại hiệu quả cao trên các tập dữ liệu lớn
  • Hỗ trợ đa ngôn ngữ, chẳng hạn như tiếng Anh và tiếng Tây Ban Nha
  • Xếp hạng mức độ liên quan và tính điểm sử dụng

Do sử dụng I/O ít hơn, AppSearch giảm độ trễ cho việc lập chỉ mục và tìm kiếm trên các tập dữ liệu lớn so với SQLite. AppSearch đơn giản hoá các truy vấn loại chéo bằng cách hỗ trợ các truy vấn đơn lẻ, trong khi SQLite hợp nhất kết quả từ nhiều bảng.

Để minh hoạ các tính năng của AppSearch, hãy lấy ví dụ về tệp âm nhạc ứng dụng quản lý các bài hát yêu thích của người dùng và cho phép người dùng dễ dàng tìm kiếm cho họ. Người dùng thưởng thức âm nhạc trên khắp thế giới với tiêu đề bài hát khác nhau mà AppSearch vốn hỗ trợ việc lập chỉ mục và truy vấn. Khi người dùng tìm kiếm bài hát theo tiêu đề hoặc tên nghệ sĩ, ứng dụng chỉ chuyển yêu cầu đến AppSearch để truy xuất các bài hát trùng khớp một cách nhanh chóng và hiệu quả. Chiến lược phát hành đĩa đơn ứng dụng sẽ cho thấy các kết quả, giúp người dùng nhanh chóng bắt đầu chơi những bài hát họ yêu thích.

Thiết lập

Để sử dụng AppSearch trong ứng dụng của bạn, hãy thêm các phần phụ thuộc sau vào tệp build.gradle của ứng dụng:

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

Khái niệm về AppSearch

Biểu đồ dưới đây minh hoạ các khái niệm của AppSearch và các hoạt động tương tác của các khái niệm đó.

Sơ đồ
đường viền về ứng dụng khách và các tương tác của ứng dụng đó với
Các khái niệm của AppSearch: Cơ sở dữ liệu AppSearch, giản đồ, loại giản đồ, tài liệu,
phiên hoạt động và tìm kiếm. Hình 1. Sơ đồ về các khái niệm của AppSearch: Cơ sở dữ liệu, giản đồ, cơ sở dữ liệu AppSearch loại giản đồ, tài liệu, phiên và tìm kiếm.

Cơ sở dữ liệu và phiên

Cơ sở dữ liệu AppSearch là một tập hợp các tài liệu phù hợp với cơ sở dữ liệu giản đồ. Ứng dụng tạo cơ sở dữ liệu bằng cách cung cấp ứng dụng ngữ cảnh và tên cơ sở dữ liệu. Chỉ ứng dụng mới có thể mở cơ sở dữ liệu đã tạo ra chúng. Khi cơ sở dữ liệu được mở, một phiên hoạt động sẽ được trả về để tương tác với cơ sở dữ liệu. Phiên này là điểm truy cập để gọi các API AppSearch và vẫn mở cho đến khi ứng dụng đóng lại.

Các loại giản đồ và giản đồ

Giản đồ thể hiện cấu trúc tổ chức của dữ liệu trong một AppSearch cơ sở dữ liệu.

Giản đồ bao gồm các loại giản đồ đại diện cho các loại dữ liệu duy nhất. Loại giản đồ bao gồm các thuộc tính chứa tên, loại dữ liệu và số lượng giá trị riêng biệt. Sau khi thêm một loại giản đồ vào giản đồ cơ sở dữ liệu, các tài liệu về loại giản đồ đó có thể được tạo và thêm vào cơ sở dữ liệu.

Tài liệu

Trong AppSearch, một đơn vị dữ liệu được trình bày dưới dạng một tài liệu. Mỗi tài liệu trong một Cơ sở dữ liệu AppSearch được xác định riêng bằng không gian tên và mã nhận dạng. Không gian tên được dùng để tách dữ liệu khỏi nhiều nguồn khi chỉ một nguồn cần được truy vấn, chẳng hạn như tài khoản người dùng.

Tài liệu chứa dấu thời gian tạo, thời gian tồn tại (TTL) và điểm số mà có thể được sử dụng để xếp hạng trong quá trình truy xuất. Một tài liệu cũng được gán một giản đồ loại mô tả các thuộc tính dữ liệu khác mà tài liệu cần có.

Lớp tài liệu là một bản tóm tắt của tài liệu. Tệp này chứa các trường có chú giải biểu thị nội dung của một tài liệu. Theo mặc định, tên của tài liệu lớp sẽ đặt tên cho loại giản đồ.

Tài liệu được lập chỉ mục và có thể tìm kiếm được bằng cách cung cấp truy vấn. Một tài liệu là khớp và được đưa vào kết quả tìm kiếm nếu có chứa các cụm từ trong truy vấn hoặc khớp với một thông số tìm kiếm khác. Kết quả được sắp xếp dựa trên điểm số và thứ hạng. Kết quả tìm kiếm được trình bày bằng các trang mà bạn có thể truy xuất tuần tự.

AppSearch cung cấp các tuỳ chỉnh cho tìm kiếm, chẳng hạn như bộ lọc, cấu hình kích thước trang và trích đoạn nội dung.

Bộ nhớ nền tảng so với bộ nhớ cục bộ

AppSearch cung cấp 2 giải pháp lưu trữ: LocalStorage và PlatformStorage. Với LocalStorage, ứng dụng của bạn quản lý chỉ mục dành riêng cho ứng dụng nằm trong thư mục dữ liệu ứng dụng của bạn. Với PlatformStorage, ứng dụng của bạn đóng góp vào chỉ mục trung tâm trên toàn hệ thống. Quyền truy cập dữ liệu trong chỉ mục trung tâm bị hạn chế ở dữ liệu mà ứng dụng của bạn đã đóng góp và dữ liệu đã được được một ứng dụng khác chia sẻ rõ ràng với bạn. Cả LocalStorage và PlatformStorage có cùng API và có thể thay thế cho nhau dựa trên phiên bản:

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

Khi sử dụng PlatformStorage, ứng dụng của bạn có thể chia sẻ dữ liệu một cách an toàn với ứng dụng để cho phép chúng tìm kiếm trên dữ liệu của ứng dụng. Chỉ đọc thì việc chia sẻ dữ liệu ứng dụng sẽ được cấp qua quá trình bắt tay chứng chỉ để đảm bảo rằng ứng dụng còn lại có quyền đọc dữ liệu. Đọc thêm về API này trong tài liệu cho setSchemaTypeVisibilityForPackage().

Ngoài ra, dữ liệu đã lập chỉ mục có thể xuất hiện trên nền tảng Giao diện người dùng hệ thống. Các ứng dụng có thể chọn không hiển thị một số hoặc tất cả dữ liệu của mình trên Hệ thống Nền tảng giao diện người dùng. Đọc thêm về API này trong tài liệu cho setSchemaTypeDisplayedBySystem().

Tính năng 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

Có một số sự đánh đổi khác cần xem xét khi chọn giữa LocalStorage và PlatformStorage. Vì PlatformStorage bao bọc API Jetpack trong Dịch vụ hệ thống AppSearch, tác động đối với kích thước tệp APK là rất nhỏ so với việc sử dụng Bộ nhớ cục bộ. Tuy nhiên, điều này cũng có nghĩa là các hoạt động AppSearch phát sinh thêm độ trễ liên kết khi gọi dịch vụ hệ thống AppSearch. Với PlatformStorage, AppSearch giới hạn số lượng tài liệu và kích thước của tài liệu trong một ứng dụng có thể lập chỉ mục để đảm bảo một chỉ mục trung tâm hiệu quả.

Bắt đầu sử dụng AppSearch

Ví dụ trong phần này trình bày cách sử dụng AppSearch API để tích hợp bằng ứng dụng lưu giữ ghi chú giả định.

Viết một lớp tài liệu

Bước đầu tiên để tích hợp với AppSearch là viết một lớp tài liệu vào mô tả dữ liệu cần chèn vào cơ sở dữ liệu. Đánh dấu một lớp là lớp tài liệu bằng cách sử dụng @Document chú thích.Bạn có thể sử dụng các thực thể của lớp tài liệu để đưa tài liệu vào và truy xuất tài liệu từ cơ sở dữ liệu.

Đoạn mã sau đây xác định một lớp tài liệu Ghi chú có phần tử Đã chú thích @Document.StringProperty để lập chỉ mục văn bản của đối tượng Ghi chú.

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

Mở cơ sở dữ liệu

Bạn phải tạo cơ sở dữ liệu trước khi làm việc với tài liệu. Mã sau đây sẽ tạo một cơ sở dữ liệu mới có tên là notes_app và nhận một ListenableFuture cho AppSearchSession, đại diện cho kết nối đến cơ sở dữ liệu và cung cấp các API để các thao tác với cơ sở dữ liệu.

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

Đặt giản đồ

Bạn phải đặt giản đồ trước khi có thể đặt tài liệu và truy xuất tài liệu khỏi cơ sở dữ liệu. Giản đồ cơ sở dữ liệu bao gồm nhiều loại dữ liệu có cấu trúc, được gọi là "loại giản đồ". Mã sau đây sẽ thiết lập giá trị giản đồ bằng cách cung cấp lớp tài liệu dưới dạng loại giản đồ.

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

Đặt tài liệu vào cơ sở dữ liệu

Sau khi thêm một loại giản đồ, bạn có thể thêm tài liệu thuộc loại đó vào cơ sở dữ liệu. Mã sau đây tạo tài liệu thuộc loại giản đồ Note bằng cách sử dụng Note trình tạo lớp tài liệu. Thao tác này thiết lập không gian tên tài liệu user1 để đại diện cho người dùng tùy ý của mẫu này. Sau đó, tài liệu được chèn vào cơ sở dữ liệu và một trình nghe sẽ được đính kèm để xử lý kết quả của thao tác đưa.

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

Bạn có thể tìm kiếm tài liệu được lập chỉ mục bằng cách sử dụng các thao tác tìm kiếm được đề cập trong phần này. Mã sau đây thực hiện các truy vấn cho cụm từ "trái cây" trong cơ sở dữ liệu cho các tài liệu thuộc không gian tên 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);

Lặp lại thông qua Search Results

Các lượt tìm kiếm trả về SearchResults Cấp quyền truy cập vào các trang của đối tượng SearchResult. Mỗi SearchResult chứa GenericDocument trùng khớp, dạng chung của mà tất cả tài liệu được chuyển đổi sang. Mã sau đây lấy mã đầu tiên trang kết quả tìm kiếm và chuyển đổi kết quả trở lại thành tài liệu 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);

Xoá tài liệu

Khi người dùng xoá một ghi chú, ứng dụng đó sẽ xoá Note tương ứng ra khỏi cơ sở dữ liệu. Việc này giúp đảm bảo ghi chú sẽ không còn xuất hiện trong truy vấn. Mã sau đây đưa ra một yêu cầu rõ ràng về việc xoá Note tài liệu từ cơ sở dữ liệu theo 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);

Lưu trữ vào ổ đĩa

Các bản cập nhật cho cơ sở dữ liệu phải được duy trì định kỳ vào ổ đĩa bằng cách gọi requestFlush(). Chiến lược phát hành đĩa đơn đoạn mã sau gọi requestFlush() bằng một trình nghe để xác định xem lệnh gọi đã thành công.

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

Đóng phiên

AppSearchSession sẽ đóng khi ứng dụng không còn gọi bất kỳ cơ sở dữ liệu nào các toán tử. Mã sau đây đóng phiên AppSearch đã mở trước đó và duy trì tất cả bản cập nhật vào ổ đĩa.

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

Tài nguyên khác

Để tìm hiểu thêm về AppSearch, hãy xem thêm các tài nguyên sau đây:

Mẫu

  • Mẫu AppSearch (Kotlin) trên Android, một ứng dụng ghi chú sử dụng AppSearch để lập chỉ mục ghi chú của người dùng và cho phép người dùng để tìm kiếm trên ghi chú của họ.

Gửi ý kiến phản hồi

Hãy chia sẻ phản hồi và ý kiến của bạn với chúng tôi thông qua các tài nguyên sau:

Công cụ theo dõi lỗi

Báo cáo lỗi để chúng tôi có thể khắc phục.