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](https://developer.android.com/static/images/guide/topics/search/appsearch.png?authuser=9&hl=vi)
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-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") }
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 đó.
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ìm kiếm
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);
Tìm kiếm
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:
Báo cáo lỗi để chúng tôi có thể khắc phục.