使用 Android 搜索对话框或搜索微件时,您可以提供根据应用中的数据创建的自定义搜索建议。例如,如果您的应用是字典,您可以在用户完成查询输入之前,建议字典中与搜索字段中输入的文字匹配的字词。这些建议很有价值,因为它们可以有效地预测用户想要的内容并让用户能够立即访问该内容。图 1 显示了包含自定义建议的搜索对话框的示例。
提供自定义建议后,您还可以将其提供给系统范围的快速搜索框,从而让用户能够从您的应用外部访问您的内容。
在添加自定义建议之前,请先实现 Android 搜索对话框或搜索微件,以便在您的应用中进行搜索。请参阅创建搜索界面和内容提供程序。
基础知识

图 1. 包含自定义搜索建议的搜索对话框的屏幕截图。
当用户选择自定义建议时,系统会将 Intent
发送到您的可搜索 activity。正常的搜索查询会发送包含 ACTION_SEARCH
操作的 intent,而您可以将自定义建议定义为使用 ACTION_VIEW
(或其他任何 intent 操作),还可以添加与选定建议相关的数据。在字典示例中,当用户选择一条建议时,应用可以立即打开该字词的定义,而不是在字典中搜索匹配项。
如需提供自定义建议,请执行以下步骤:
- 实现一个基本的可搜索 activity,如创建搜索界面中所述。
- 使用有关用于提供自定义建议的内容提供程序的信息来修改可搜索配置。
- 为建议构建一个表格(例如,在
SQLiteDatabase
中),并设置该表格的格式,使其包含所需的列。 - 创建一个可访问建议表格的内容提供程序,并在清单中声明该提供程序。
- 声明要在用户选择某条建议时发送的
Intent
类型(包括自定义操作和自定义数据)。
就像 Android 系统显示搜索对话框一样,它也会显示搜索建议。您需要一个内容提供程序,系统可从其中检索建议。请阅读内容提供程序,了解如何创建内容提供程序。
如果系统发现您的 activity 可搜索并提供搜索建议,在用户输入查询时会发生以下过程:
- 系统获取搜索查询文本(即用户到目前为止已输入的所有内容),并对管理建议的内容提供程序执行查询。
- 内容提供程序返回一个
Cursor
,它指向与搜索查询文本相关的所有建议。 - 系统会显示
Cursor
提供的建议列表。
显示自定义建议后,可能会发生以下情况:
- 如果用户输入其他字母或以任何方式更改查询,系统会重复执行上述步骤,并相应地更新建议列表。
- 如果用户执行搜索,系统会忽略建议,并使用正常的
ACTION_SEARCH
intent 将搜索传递给可搜索 activity。 - 如果用户选择某条建议,系统会将一个 intent 发送到您的可搜索 activity,该 intent 带有自定义操作和自定义数据,以便您的应用可以打开建议的内容。
修改可搜索配置
如需添加对自定义建议的支持,请在可搜索配置文件中将 android:searchSuggestAuthority
属性添加到 <searchable>
元素,如以下示例所示:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider"> </searchable>
您可能需要其他属性,具体取决于您附加到每条建议的 intent 类型,以及您要如何设置对内容提供程序的查询的格式。下面几部分介绍了其他可选属性。
创建内容提供方
如需为自定义建议创建内容提供程序,请先参阅内容提供程序,了解如何创建内容提供程序。自定义建议的内容提供程序与其他任何内容提供程序类似。不过,对于您提供的每条建议,Cursor
中的相应行必须包含系统理解并用来设置建议格式的特定列。
当用户在搜索对话框或搜索微件中输入文字时,用户每次输入一个字母,系统就会通过调用 query()
向内容提供程序查询建议。在 query()
实现中,内容提供程序必须搜索建议数据并返回一个 Cursor
(指向它认为是合适的建议的行)。
下面两部分详细介绍了如何为自定义建议创建内容提供程序:
处理建议查询
当系统从内容提供程序请求建议时,它会调用内容提供程序的 query()
方法。实现此方法可搜索建议数据并返回一个 Cursor
(指向您认为相关的建议)。
下面简要介绍了系统传递给 query()
方法的参数(按顺序列出):
uri
始终为内容
Uri
,格式如下:content://your.authority/optional.suggest.path/
SUGGEST_URI_PATH_QUERY
默认行为是系统传递此 URI 并将查询文本附加到该 URI:
content://your.authority/optional.suggest.path/
SUGGEST_URI_PATH_QUERY
/puppies末尾的查询文本使用 URI 编码规则进行了编码,因此您可能需要在执行搜索之前将其解码。
仅当您在可搜索配置文件中使用
android:searchSuggestPath
属性设置了相应路径时,optional.suggest.path
部分才会包含在 URI 中。仅当您将同一内容提供程序用于多个可搜索 Activity 时,才需要此参数。如果出现这种情况,请消除建议查询来源的歧义。projection
- 始终为 null。
selection
- 在可搜索配置文件的
android:searchSuggestSelection
属性中提供的值;如果您未声明android:searchSuggestSelection
属性,则为 null。下一部分将对此进行进一步讨论。selectionArgs
- 如果您在可搜索配置中声明
android:searchSuggestSelection
属性,则包含搜索查询作为数组的第一个(也是唯一一个)元素。如果您未声明android:searchSuggestSelection
,则此参数为 null。 下一部分将对此进行进一步讨论。sortOrder
- 始终为 null。
系统可以通过两种方式向您发送搜索查询文本。默认方式是包含查询文本作为在 uri
参数中传递的内容 URI 的最后一个路径。不过,如果可搜索配置的 android:searchSuggestSelection
属性中包含选择值,则会将查询文本作为 selectionArgs
字符串数组的第一个元素传递。下面将介绍这两种方式。
在 Uri 中获取查询
默认情况下,查询附加为 uri
参数(Uri
对象)的最后一段。如需在这种情况下检索查询文本,请使用 getLastPathSegment()
,如以下示例所示:
Kotlin
val query: String = uri.lastPathSegment.toLowerCase()
Java
String query = uri.getLastPathSegment().toLowerCase();
此方法将返回 Uri
的最后一段,也就是用户输入的查询文本。
在选择参数中获取查询
您可能决定更合理的做法是让 query()
方法接收执行查询所需的一切内容,并且希望 selection
和 selectionArgs
参数携带适当的值,而不是使用 URI。在这种情况下,请使用 SQLite 选择字符串将 android:searchSuggestSelection
属性添加到可搜索配置。在选择字符串中,添加一个问号 (?) 作为实际搜索查询的占位符。系统调用 query()
时,将选择字符串作为 selection
参数,并将搜索查询作为 selectionArgs
数组中的第一个元素。
例如,您可以按如下方式形成 android:searchSuggestSelection
属性来创建全文搜索语句:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestSelection="word MATCH ?"> </searchable>
借助此配置,您的 query()
方法将 selection
参数作为 "word MATCH ?"
传递,并将 selectionArgs
参数作为搜索查询传递。当您将这些传递给 SQLite query()
方法(作为它们各自的实参)时,它们会合成在一起,也就是说,问号会替换为查询文本。如果您以这种方式接收建议查询,并且需要向查询文本添加通配符,请将其附加到 selectionArgs
参数和/或将其加在该参数前面作为前缀,因为此值会用引号引起来,并且会插入此值来代替问号。
上例中的另一个属性是 android:searchSuggestIntentAction
,该属性定义在用户选择某条建议时随每个 intent 发送的 intent 操作。为建议声明 intent 部分对此进行了进一步讨论。
构建建议表格
当您使用 Cursor
向系统返回建议时,系统要求每行中包含特定的列。无论您将建议数据存储在设备上的 SQLite 数据库中、网络服务器上的数据库中,还是以其他格式存储在设备或网络上,都必须将建议的格式设为表格中的行并使用 Cursor
来显示。
系统理解几列,但其中只有两列是必需的:
_ID
- 每条建议的唯一整数行 ID。系统需要此 ID 是为了在
ListView
中显示建议。 SUGGEST_COLUMN_TEXT_1
- 显示为建议的字符串。
以下列均为可选列。后续部分将详细讨论其中的大多数。
SUGGEST_COLUMN_TEXT_2
- 一个字符串。如果您的
Cursor
包含此列,则所有建议都以两行格式提供。此列中的字符串显示为主要建议文本下面的第二行较小的文本。它可以为 null 或为空,表示没有次要文本。 SUGGEST_COLUMN_ICON_1
- 一个可绘制资源、内容或文件 URI 字符串。如果您的
Cursor
包含此列,则所有建议都以图标加文本格式提供,且可绘制图标在左侧。此列可以为 null 或为 0,表示此行中没有图标。 SUGGEST_COLUMN_ICON_2
- 一个可绘制资源、内容或文件 URI 字符串。如果您的
Cursor
包含此列,则所有建议都以图标加文本格式提供,且图标在右侧。此列可以为 null 或为 0,表示此行中没有图标。 SUGGEST_COLUMN_INTENT_ACTION
- 一个 intent 操作字符串。如果此列存在且在给定行中包含值,则形成建议的 intent 时会使用此处定义的操作。如果未提供该元素,则会从可搜索配置中的
android:searchSuggestIntentAction
字段获取操作。如果您的操作对于所有建议都是相同的,则比较高效的做法是使用android:searchSuggestIntentAction
指定操作而省略此列。 SUGGEST_COLUMN_INTENT_DATA
- 一个数据 URI 字符串。如果此列存在且在给定行中包含值,则这是形成建议的 intent 时使用的数据。如果未提供该元素,则会从可搜索配置中的
android:searchSuggestIntentData
字段获取数据。如果这两个来源均未提供,则 intent 的数据字段为 null。如果您的数据对于所有建议都是相同的,或者可以使用常量部分和特定 ID 进行描述,则比较高效的做法是使用android:searchSuggestIntentData
指定数据而省略此列。 SUGGEST_COLUMN_INTENT_DATA_ID
- 一个 URI 路径字符串。如果此列存在且在给定行中包含值,则会将“/”和此值附加到 intent 中的数据字段。仅当已将由可搜索配置中的
android:searchSuggestIntentData
属性指定的数据字段设为适当的基字符串时,才应使用此列。 SUGGEST_COLUMN_INTENT_EXTRA_DATA
- 任意数据。如果此列存在且在给定行中包含值,则这是形成建议的 intent 时使用的额外数据。
如果未提供,则 intent 的额外数据字段为 null。此列允许建议提供附加数据,这些数据以 extra 形式包含在 intent 的
EXTRA_DATA_KEY
键中。 SUGGEST_COLUMN_QUERY
- 如果此列存在且在给定行中存在此元素,则这是形成建议的查询时使用的数据,这些数据以 extra 形式包含在 intent 的
QUERY
键中。如果建议的操作为ACTION_SEARCH
,则此列是必需的;否则,此列是可选的。 SUGGEST_COLUMN_SHORTCUT_ID
- 仅当为快速搜索框提供建议时,才使用此列。此列指明是否必须将搜索建议存储为快捷方式以及是否必须对其进行验证。当用户点按快速搜索框中的建议时,通常会形成快捷方式。如果缺少此列,则结果会存储为快捷方式且从不刷新。如果设为
SUGGEST_NEVER_MAKE_SHORTCUT
,则结果不会存储为快捷方式。否则,会使用快捷方式 ID 通过SUGGEST_URI_PATH_SHORTCUT
回来检查是否有最新建议。 SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING
- 仅当为快速搜索框提供建议时,才使用此列。此列指定当此建议的快捷方式正在快速搜索框中刷新时,必须显示旋转图标而不是
SUGGEST_COLUMN_ICON_2
中的图标。
下面几部分将详细介绍其中的大多数列。
为建议声明 intent
当用户从显示在搜索对话框或搜索微件下方的列表中选择某条建议时,系统会将一个自定义 Intent
发送到您的可搜索 activity。您必须定义该 intent 的操作和数据。
声明 intent 操作
自定义建议的最常见的 intent 操作是 ACTION_VIEW
,当您要打开某些内容(如某个字词的定义、某人的联系信息或某个网页)时,此操作非常合适。不过,intent 操作可以是其他任何操作,甚至对于每条建议可以有所不同。
根据您是否希望所有建议使用同一 intent 操作,您可以通过两种方式来定义操作:
- 使用可搜索配置文件的
android:searchSuggestIntentAction
属性为所有建议定义操作,如以下示例所示:<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" > </searchable>
- 使用
SUGGEST_COLUMN_INTENT_ACTION
列为各条建议定义操作。为此,请将SUGGEST_COLUMN_INTENT_ACTION
列添加到建议表格,并针对每条建议在其中添加要使用的操作(如"android.intent.action.VIEW"
)。
您还可以结合使用这两种方法。例如,您可以在 android:searchSuggestIntentAction
属性中添加一项默认用于所有建议的操作,然后通过在 SUGGEST_COLUMN_INTENT_ACTION
列中声明其他操作来为某些建议替换此操作。如果您未在 SUGGEST_COLUMN_INTENT_ACTION
列中添加值,则会使用在 android:searchSuggestIntentAction
属性中提供的 intent。
声明 intent 数据
当用户选择某条建议时,您的可搜索 activity 会接收包含您定义的操作的 intent(如上一部分中所述),但 intent 还必须携带数据,您的 activity 才能识别用户选择了哪条建议。具体来说,对于每条建议,数据必须是唯一的,如 SQLite 表格中建议的行 ID。接收 intent 后,您可以使用 getData()
或 getDataString()
来检索附加的数据。
您可以通过两种方式来定义 intent 中包含的数据:
- 在建议表格的
SUGGEST_COLUMN_INTENT_DATA
列中定义每条建议的数据。在建议表格中提供每个 intent 的所有必要数据信息,方法是添加
SUGGEST_COLUMN_INTENT_DATA
列,然后在其中填充每行的唯一数据。此列中的数据将完全按照您在此列中的定义附加到 intent。您随后可以使用getData()
或getDataString()
来检索该数据。 - 将数据 URI 分为两部分:一个是所有建议共有的部分,一个是每条建议独有的部分。将这两部分分别放入可搜索配置的
android:searchSuggestintentData
属性和建议表格的SUGGEST_COLUMN_INTENT_DATA_ID
列。以下示例展示了如何在可搜索配置的
android:searchSuggestIntentData
属性中声明所有建议共有的 URI 部分:<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestIntentData="content://com.example/datatable" > </searchable>
在建议表格的
SUGGEST_COLUMN_INTENT_DATA_ID
列中添加每条建议的最终路径(独有部分)。当用户选择某条建议时,系统会获取android:searchSuggestIntentData
中的字符串,附加一个正斜杠 (/),然后添加SUGGEST_COLUMN_INTENT_DATA_ID
列中的相应值,以形成完整的内容 URI。然后,您可以使用getData()
检索Uri
。
添加更多数据
如果您需要通过 intent 来表达更多信息,您可以再添加一个表格列,例如 SUGGEST_COLUMN_INTENT_EXTRA_DATA
,此列可以存储有关建议的附加信息。此列中保存的数据将放在 intent 的 extra bundle 的 EXTRA_DATA_KEY
中。
处理 intent
现在您已经提供了自定义搜索建议与自定义 intent,接下来您需要让您的可搜索 activity 在用户选择某条建议时处理这些 intent。这是除了处理 ACTION_SEARCH
intent(您的可搜索 activity 已经这样做了)之外,还需要执行的操作。以下示例展示了您如何在 activity 的 onCreate()
回调期间处理 intent:
Kotlin
when(intent.action) { Intent.ACTION_SEARCH -> { // Handle the normal search query case. intent.getStringExtra(SearchManager.QUERY)?.also { query -> doSearch(query) } } Intent.ACTION_VIEW -> { // Handle a suggestions click, because the suggestions all use ACTION_VIEW. showResult(intent.data) } }
Java
Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { // Handle the normal search query case. String query = intent.getStringExtra(SearchManager.QUERY); doSearch(query); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { // Handle a suggestions click, because the suggestions all use ACTION_VIEW. Uri data = intent.getData(); showResult(data); }
在本例中,intent 操作是 ACTION_VIEW
并且数据携带一个完整的 URI,该 URI 指向建议的内容(由 android:searchSuggestIntentData
字符串和 SUGGEST_COLUMN_INTENT_DATA_ID
列合成)。然后,该 URI 会传递给本地 showResult()
方法,该方法会向内容提供程序查询由该 URI 指定的内容。
重写查询文本
默认情况下,如果用户使用方向控件(例如,使用轨迹球或方向键)浏览建议列表,查询文本不会更新。不过,您可以使用与当前获得焦点的建议匹配的查询来暂时重新编写显示在文本框中的用户查询文本。这样,用户就能看到当前建议的查询,接着选择搜索框并修改查询,然后再将其作为搜索进行分派。
您可以通过以下方式重新编写查询文本:
- 向您的可搜索配置添加值为
"queryRewriteFromText"
的android:searchMode
属性。在这种情况下,建议的SUGGEST_COLUMN_TEXT_1
列中的内容会用于重新编写查询文本。 - 向可搜索配置添加值为
"queryRewriteFromData"
的android:searchMode
属性。在这种情况下,建议的SUGGEST_COLUMN_INTENT_DATA
列中的内容会用于重新编写查询文本。这只能与 URI 或会让用户可见的其他数据格式(如 HTTP 网址)一起使用。内部 URI 架构不得用于通过这种方式重新编写查询。 - 在建议表格的
SUGGEST_COLUMN_QUERY
列中提供唯一的查询文本字符串。如果此列存在且包含当前建议的值,则它会用于重新编写查询文本并替换前面的任一实现。
向快速搜索框提供搜索建议
将应用配置为提供自定义搜索建议后,将这些建议提供给全局可访问的快速搜索框非常简单,只需修改可搜索配置以添加 android:includeInGlobalSearch
(值为 "true"
)即可。
只有在一种情况下需要执行额外的工作,那就是当内容提供程序要求读取权限时。在这种情况下,您需要为提供程序添加一个 <path-permission>
元素,以向快速搜索框授予对内容提供程序的读取访问权限,如以下示例所示:
<provider android:name="MySuggestionProvider" android:authorities="com.example.MyCustomSuggestionProvider" android:readPermission="com.example.provider.READ_MY_DATA" android:writePermission="com.example.provider.WRITE_MY_DATA"> <path-permission android:pathPrefix="/search_suggest_query" android:readPermission="android.permission.GLOBAL_SEARCH" /> </provider>
在本例中,提供程序限制对内容进行读写访问。
<path-permission>
元素修改了这项限制,方法是存在 "android.permission.GLOBAL_SEARCH"
权限时授予对 "/search_suggest_query"
路径前缀内的内容的读取访问权限。这样会向快速搜索框授予访问权限,以便它可以向内容提供程序查询建议。
如果内容提供程序未强制要求读取权限,则快速搜索框默认情况下可以读取它。
在设备上启用建议
默认情况下,即使应用配置为在快速搜索框中提供建议,也不会启用此功能。用户可以选择是否在快速搜索框中包含来自应用的建议,方法是打开可搜索项(位于设置 > 搜索中),然后将应用作为可搜索项启用。
可供快速搜索框使用的每个应用在可搜索项设置页面中都有一个对应的条目。该条目包含应用的名称和一段简短说明,这段说明将概括可以从应用中搜索哪些内容并使其成为快速搜索框中的建议。如需为可搜索应用定义说明文本,请将 android:searchSettingsDescription
属性添加到可搜索配置,如下例所示:
<?xml version="1.0" encoding="utf-8"?> <searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/app_label" android:hint="@string/search_hint" android:searchSuggestAuthority="com.example.MyCustomSuggestionProvider" android:searchSuggestIntentAction="android.intent.action.VIEW" android:includeInGlobalSearch="true" android:searchSettingsDescription="@string/search_description" > </searchable>
android:searchSettingsDescription
的字符串应尽可能简洁,并说明可搜索的内容。例如,音乐应用的说明为“音乐人、专辑和曲目”,记事本应用的说明为“保存的记事”。提供此说明非常重要,因为这样能让用户知道会提供什么样的建议。当 android:includeInGlobalSearch
为 true 时,请务必添加此属性。
由于用户必须访问设置菜单才能为您的应用启用搜索建议,因此如果搜索是您的应用的一个重要方面,请考虑如何向用户传达这一点。例如,您可以在用户首次启动应用时提供一条说明,说明如何为快速搜索框启用搜索建议。
管理快速搜索框建议快捷方式
用户从快速搜索框中选择的建议可以自动变为快捷方式。这些是系统从您的内容提供程序复制的建议,因此它可以快速访问相应建议,而无需重新查询您的内容提供程序。
默认情况下,系统会为快速搜索框检索的所有建议启用此功能,但如果您的建议数据随时间发生变化,则您可以请求刷新相应的快捷方式。例如,如果您的建议引用动态数据(如联系人的在线状态),则请求在向用户显示建议快捷方式时对其进行刷新。为此,请在您的建议表格中添加 SUGGEST_COLUMN_SHORTCUT_ID
。您可以使用此列通过以下某种方式来配置每条建议的快捷方式行为:
让快速搜索框向您的内容提供程序重新查询建议快捷方式的最新版本。
在
SUGGEST_COLUMN_SHORTCUT_ID
列中提供一个值,每次显示快捷方式时,都会重新查询建议的最新版本。系统会使用最近可用的数据快速显示快捷方式,直到刷新查询返回结果,届时会使用新信息来刷新建议。系统会使用 URI 路径SUGGEST_URI_PATH_SHORTCUT
(而不是SUGGEST_URI_PATH_QUERY
)将刷新查询发送到您的内容提供程序。您返回的
Cursor
应包含一条建议,它使用的列与原始建议相同;也可以为空,表明快捷方式不再有效(在这种情况下,建议会消失并且快捷方式会被移除)。如果建议引用的数据需要较长时间进行刷新(如基于网络的刷新),您也可以向建议表格中添加
SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING
列(值为 true),以便在右侧显示一个进度旋转图标,直到刷新完成。如果设为除 true 以外的其他任何值,则不会显示进度旋转图标。完全阻止将建议复制到快捷方式。
在
SUGGEST_COLUMN_SHORTCUT_ID
列中提供SUGGEST_NEVER_MAKE_SHORTCUT
值。在这种情况下,绝不会将建议复制到快捷方式。只有在您绝对不希望显示先前复制的建议时,才有必要这样做。如果您为该列提供一个正常值,则只有在刷新查询返回结果后,才会显示建议快捷方式。允许应用默认快捷方式行为。
对于不会发生变化且可保存为快捷方式的每条建议,将
SUGGEST_COLUMN_SHORTCUT_ID
留空。
如果您的所有建议都从不发生变化,则您不需要 SUGGEST_COLUMN_SHORTCUT_ID
列。
关于快速搜索框建议排名
将应用的搜索建议提供给快速搜索框后,快速搜索框排名决定了如何针对特定查询向用户显示这些建议。这可能取决于有多少其他应用可以提供该查询的结果,以及与其他应用提供的结果相比,用户选择您的结果的频率。不能保证如何对您的建议排名,也不能保证是否针对给定的查询显示您的应用的建议。一般来说,如果应用提供的结果质量较高,那么将其建议放在显眼位置的可能性就越高;如果应用提供的建议质量较低,那么建议更有可能排名较低或根本不显示。