AppSearch 是一种高性能的设备端搜索解决方案,用于在本地进行管理 结构化数据。它包含用于将数据编入索引和检索数据的 API 搜索。应用可以使用 AppSearch 提供自定义应用内 搜索功能,使用户即使在离线状态下也能搜索内容。
AppSearch 提供以下功能:
- 一种 I/O 占用率低的移动设备优先快速存储实现
- 快速高效地对大型数据集进行索引编制和查询
- 多语言支持,如英语和西班牙语
- 相关性排名和使用情况评分
由于 I/O 使用量较少,AppSearch 可缩短编入索引和搜索的延迟时间 大型数据集处理能力的提升。AppSearch 可简化跨类型查询 而 SQLite 会合并多个表中的结果。
为了说明 AppSearch 的功能,我们以音乐为例来说明 该应用管理用户喜爱的歌曲,并允许用户轻松搜索 。用户欣赏来自世界各地的音乐,歌曲名称各不相同 语言,AppSearch 原生支持编入索引和查询。当 当用户按歌名或音乐人姓名搜索歌曲时,应用只需传递 向 AppSearch 发出的请求,以快速、高效地检索匹配的歌曲。通过 应用会显示结果,让用户能够快速开始游戏 和喜爱的歌曲
设置
如需在应用中使用 AppSearch,请将以下依赖项添加到您的
应用的 build.gradle
文件:
Groovy
dependencies { def appsearch_version = "1.1.0-alpha07" 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-alpha07" 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 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() 文档)。
此外,已编入索引的数据可能会显示在系统界面 surface 上。 应用程序可以选择不在“系统”上显示其部分或全部数据 界面 surface。如需详细了解此 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 系统服务,与使用 LocalStorage 类。不过,这也意味着 AppSearch 操作 调用 AppSearch 系统服务时 binder 延迟时间。借助 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);
搜索
您可以使用下文介绍的搜索操作来搜索已编入索引的文档,
部分。以下代码对字词“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
的请求
从数据库中提取文档。
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,请参阅下面列出的其他资源:
示例
- Android AppSearch 示例 (Kotlin) 一个记事应用,使用 AppSearch 将用户的笔记编入索引并允许用户 搜索记事
提供反馈
通过以下资源与我们分享您的反馈和想法:
报告错误,以便我们进行修复。