AppSearch là một giải pháp tìm kiếm hiệu suất cao trên thiết bị để quản lý dữ liệu có cấu trúc được lưu trữ cục bộ. Thư viện này chứa các API để lập chỉ mục dữ liệu và truy xuất dữ liệu bằ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 các chức năng tìm kiếm tuỳ chỉnh trong ứng dụng, 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ạ việc lập chỉ mục và tìm kiếm trong AppSearch](https://developer.android.google.cn/static/images/guide/topics/search/appsearch.png?authuser=4&hl=vi)
AppSearch cung cấp các tính năng sau:
- Phương thức triển khai bộ nhớ nhanh, ưu tiên thiết bị di động với mức sử dụng I/O thấp
- Tạo chỉ mục và truy vấn hiệu quả cao trên các tập dữ liệu lớn
- Hỗ trợ nhiều 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 năng tính điểm mức sử dụng
Do mức sử dụng I/O thấp hơn, AppSearch mang lại độ trễ thấp hơn để 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 trên nhiều loại 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ề một ứng dụng âm nhạc 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 các bài hát đó. Người dùng thưởng thức âm nhạc từ khắp nơi trên thế giới với tên bài hát bằng nhiều ngôn ngữ. AppSearch hỗ trợ lập chỉ mục và truy vấn các ngôn ngữ này ngay từ đầu. Khi người dùng tìm kiếm một bài hát theo tên hoặc tên nghệ sĩ, ứng dụng chỉ cần chuyển yêu cầu đến AppSearch để truy xuất nhanh chóng và hiệu quả các bài hát trùng khớp. Ứng dụng hiển thị kết quả, cho phép người dùng nhanh chóng bắt đầu phát bài hát yêu thích.
Thiết lập
Để sử dụng AppSearch trong ứng dụng, 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") }
Các khái niệm về AppSearch
Sơ đồ sau đây minh hoạ các khái niệm của AppSearch và cách tương tác giữa các khái niệm đó.
Hình 1. Sơ đồ các khái niệm về AppSearch: cơ sở dữ liệu AppSearch, giản đồ, 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 tuân thủ giản đồ cơ sở dữ liệu. Ứng dụng khách tạo cơ sở dữ liệu bằng cách cung cấp ngữ cảnh ứng dụng và tên cơ sở dữ liệu. Chỉ ứng dụng tạo cơ sở dữ liệu mới có thể mở cơ sở dữ liệu. Khi mở một cơ sở dữ liệu, một phiên sẽ được trả về để tương tác với cơ sở dữ liệu đó. Phiên là điểm truy cập để gọi các API AppSearch và vẫn mở cho đến khi ứng dụng khách đóng phiên.
Giản đồ và loại giản đồ
Lược đồ thể hiện cấu trúc tổ chức của dữ liệu trong cơ sở dữ liệu AppSearch.
Giản đồ bao gồm các loại giản đồ đại diện cho các loại dữ liệu riêng biệt. Các 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, bạn có thể tạo và thêm các tài liệu thuộc loại giản đồ đó vào cơ sở dữ liệu.
Tài liệu
Trong AppSearch, một đơn vị dữ liệu được biểu thị dưới dạng một tài liệu. Mỗi tài liệu trong cơ sở dữ liệu AppSearch được xác định duy nhất 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 từ các nguồn khác nhau khi chỉ cần truy vấn một nguồ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ố có thể được dùng để xếp hạng trong quá trình truy xuất. Một tài liệu cũng được chỉ định một loại giản đồ mô tả các thuộc tính dữ liệu bổ sung mà tài liệu phải 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ú thích đại diện cho nội dung của tài liệu. Theo mặc định, tên của lớp tài liệu sẽ đặt tên cho loại giản đồ.
Tìm kiếm
Các tài liệu được lập chỉ mục và có thể được tìm kiếm bằng cách cung cấp một truy vấn. Một tài liệu sẽ được khớp và đưa vào kết quả tìm kiếm nếu tài liệu đó chứa các cụm từ trong truy vấn hoặc khớp với một thông số kỹ thuật tìm kiếm khác. Các kết quả được sắp xếp dựa trên điểm số và chiến lược xếp hạng. Kết quả tìm kiếm được biểu thị bằng các trang mà bạn có thể truy xuất theo tuần tự.
AppSearch cung cấp các tính năng tuỳ chỉnh cho tính năng 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.
Bộ nhớ nền tảng, Bộ nhớ cục bộ hoặc Bộ nhớ Dịch vụ Play
AppSearch cung cấp ba giải pháp lưu trữ: LocalStorage
, PlatformStorage
và PlayServicesStorage
. Với LocalStorage
, ứng dụng của bạn sẽ quản lý một chỉ mục dành riêng cho ứng dụng nằm trong thư mục dữ liệu ứng dụng. Với cả PlatformStorage
và PlayServicesStorage
, ứng dụng của bạn sẽ đóng góp vào một chỉ mục trung tâm trên toàn hệ thống. Chỉ mục của PlatformStorage
được lưu trữ trong máy chủ hệ thống và chỉ mục của PlayServicesStorage
được lưu trữ trong bộ nhớ của Dịch vụ Google Play. Quyền truy cập dữ liệu trong các chỉ mục trung tâm này chỉ được cấp cho dữ liệu mà ứng dụng của bạn đã đóng góp và dữ liệu mà một ứng dụng khác đã chia sẻ rõ ràng với bạn. Tất cả các tuỳ chọn bộ nhớ này đều dùng chung một API và có thể được hoán đổi dựa trên phiên bản của thiết bị:
Kotlin
if (BuildCompat.isAtLeastS()) { appSearchSessionFuture.setFuture( PlatformStorage.createSearchSession( PlatformStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build() ) ) } else { if (usePlayServicesStorageBelowS) { appSearchSessionFuture.setFuture( PlayServicesStorage.createSearchSession( PlayServicesStorage.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 { if (usePlayServicesStorageBelowS) { mAppSearchSessionFuture.setFuture(PlayServicesStorage.createSearchSession( new PlayServicesStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } else { mAppSearchSessionFuture.setFuture(LocalStorage.createSearchSession( new LocalStorage.SearchContext.Builder(mContext, DATABASE_NAME) .build())); } }
Khi sử dụng PlatformStorage
và PlayServicesStorage
, ứng dụng của bạn có thể chia sẻ dữ liệu một cách an toàn với các ứng dụng khác để cho phép các ứng dụng đó tìm kiếm trong dữ liệu của ứng dụng. Việc chia sẻ dữ liệu ứng dụng chỉ có thể đọc được cấp bằng cách bắt tay chứng chỉ để đảm bảo rằng ứng dụng khác có quyền đọc dữ liệu. Đọc thêm về API này trong tài liệu về setSchemaTypeVisibilityForPackage()
.
Ngoài ra, với PlatformStorage
, dữ liệu được lập chỉ mục có thể hiển thị trên các 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 chúng trên các nền tảng Giao diện người dùng hệ thống. Đọc thêm về API này trong tài liệu về setSchemaTypeDisplayedBySystem()
.
Tính năng | LocalStorage (tương thích với Android 5.0 trở lên) |
PlatformStorage (tương thích với Android 12 trở lên) |
PlayServicesStorage (tương thích với Android 5.0 trở lên) |
---|---|---|---|
Tìm kiếm toàn bộ văn bản hiệu quả | |||
Hỗ trợ nhiều ngôn ngữ | |||
Giảm kích thước tệp nhị phân | |||
Chia sẻ dữ liệu giữa các ứng dụng | |||
Khả năng hiển thị dữ liệu trên các nền tảng Giao diện người dùng hệ thống | |||
Có thể lập chỉ mục số lượng và kích thước tài liệu không giới hạn | |||
Các thao tác nhanh hơn mà không cần thêm độ trễ liên kết |
Có một số điểm đánh đổi khác cần cân nhắc khi chọn giữa LocalStorage
và PlatformStorage
. Vì PlatformStorage
gói các API Jetpack qua dịch vụ hệ thống AppSearch, nên tác động đến kích thước APK là tối thiểu so với việc sử dụng LocalStorage. Tuy nhiên, điều này cũng có nghĩa là các thao tác AppSearch sẽ phải chịu 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 tài liệu mà một ứng dụng có thể lập chỉ mục để đảm bảo chỉ mục trung tâm hoạt động hiệu quả. PlayServicesStorage
cũng có các giới hạn giống như PlatformStorage
và chỉ được hỗ trợ trên các thiết bị có Dịch vụ Google Play.
Bắt đầu sử dụng AppSearch
Ví dụ trong phần này minh hoạ cách sử dụng các API AppSearch để tích hợp với một ứng dụng ghi chú giả định.
Viế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 để 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 chú giải @Document
.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 Note (Ghi chú) có trường chú thích @Document.StringProperty
để lập chỉ mục văn bản của đối tượng 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; } }
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 tạo một cơ sở dữ liệu mới có tên notes_app
và nhận ListenableFuture
cho AppSearchSession
, đại diện cho kết nối với cơ sở dữ liệu và cung cấp các API cho các thao tác 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 thiết lập giản đồ trước khi có thể đưa tài liệu vào và truy xuất tài liệu từ 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 đặt 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);
Đưa 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 các tài liệu thuộc loại đó vào cơ sở dữ liệu.
Mã sau đây tạo một tài liệu thuộc loại giản đồ Note
bằng trình tạo lớp tài liệu Note
. Hàm này đặt không gian tên tài liệu user1
để đại diện cho một người dùng tuỳ ý 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 được đính kèm để xử lý kết quả của thao tác đặt.
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);
Tìm kiếm
Bạn có thể tìm kiếm các tài liệu được lập chỉ mục bằ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 truy vấn cho cụm từ "fruit" (trái cây) trên 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 qua SearchResults
Các lượt tìm kiếm sẽ trả về một thực thể SearchResults
, cho phép truy cập vào các trang của đối tượng SearchResult
. Mỗi SearchResult
chứa GenericDocument
đã so khớp, đây là dạng chung của tài liệu mà tất cả tài liệu được chuyển đổi sang. Mã sau đây sẽ lấy trang kết quả tìm kiếm đầu tiên 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á tài liệu Note
tương ứng khỏi cơ sở dữ liệu. Thao tác này đảm bảo rằng ghi chú sẽ không còn xuất hiện trong các truy vấn. Mã sau đây đưa ra yêu cầu rõ ràng để xoá tài liệu Note
khỏi cơ sở dữ liệu theo mã nhận dạng.
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 vào ổ đĩa
Bạn nên định kỳ lưu các nội dung cập nhật vào cơ sở dữ liệu bằng cách gọi requestFlush()
. Mã sau đây gọi requestFlush()
bằng trình nghe để xác định xem lệnh gọi có thành công hay khô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
Bạn nên đóng AppSearchSession
khi ứng dụng không còn gọi bất kỳ thao tác cơ sở dữ liệu nào nữa. Mã sau đây sẽ đóng phiên AppSearch đã mở trước đó và lưu tất cả nội dung 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 các tài nguyên bổ sung sau:
Mẫu
- Mẫu AppSearch cho Android (Kotlin), 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 trong 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:
Báo cáo lỗi để chúng tôi có thể khắc phục.