添加自定义搜索建议

使用 Android 搜索对话框或搜索部件时,您可以提供从应用数据创建的自定义搜索建议。例如,如果您的应用是字典,您可以在用户完成输入查询之前,建议与搜索字段中输入的文本匹配的字典中的单词。这些建议很有价值,因为它们可以有效地预测用户想要什么并提供即时访问。图 1 显示了带有自定义建议的搜索对话框示例。

提供自定义建议后,您还可以将其提供给系统范围的快速搜索框,以便从应用外部访问您的内容。

在添加自定义建议之前,请为应用中的搜索实现 Android 搜索对话框或搜索部件。请参阅 创建搜索界面内容提供程序

基础知识

图 1. 带有自定义搜索建议的搜索对话框屏幕截图。

当用户选择自定义建议时,系统会向您的可搜索活动发送一个 Intent。与发送带有 ACTION_SEARCH 操作的 intent 的普通搜索查询不同,您可以改为定义您的自定义建议以使用 ACTION_VIEW(或任何其他 intent 操作),并包含与所选建议相关的数据。在字典示例中,当用户选择建议时,应用可以立即打开该单词的定义,而不是搜索字典以查找匹配项。

要提供自定义建议,请执行以下步骤

  • 实现基本的可搜索活动,如 创建搜索界面 中所述。
  • 使用有关提供自定义建议的内容提供程序的信息修改可搜索配置。
  • 为您的建议构建一个表,例如在 SQLiteDatabase 中,并使用所需的列格式化该表。
  • 创建一个 内容提供程序,该提供程序可以访问您的建议表,并在清单中声明该提供程序。
  • 声明用户选择建议时要发送的 Intent 类型,包括自定义操作和自定义数据。

就像 Android 系统显示搜索对话框一样,它还会显示您的搜索建议。您需要一个内容提供程序,系统可以从中检索您的建议。阅读 内容提供程序 以了解如何创建内容提供程序。

当系统识别您的活动是可搜索的并提供搜索建议时,当用户输入查询时,将执行以下过程

  1. 系统获取搜索查询文本(即,到目前为止输入的任何内容),并对管理您建议的内容提供程序执行查询。
  2. 您的内容提供程序返回一个 Cursor,该游标指向与搜索查询文本相关的所有建议。
  3. 系统显示 Cursor 提供的建议列表。

一旦显示自定义建议,可能会发生以下情况

  • 如果用户输入另一个字母或以任何方式更改查询,则重复上述步骤,并且建议列表相应更新。
  • 如果用户执行搜索,则会忽略建议,并使用正常的 ACTION_SEARCH intent 将搜索传递到您的可搜索活动。
  • 如果用户选择建议,则会将 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,该游标指向它认为是好建议的行。

以下两节讨论了为自定义建议创建内容提供程序的详细信息

处理建议查询
系统如何向您的内容提供程序发送请求以及如何处理这些请求。
构建建议表
如何定义系统在每次查询返回的 Cursor 中期望的列。

处理建议查询

当系统从您的内容提供程序请求建议时,它会调用内容提供程序的 query() 方法。实现此方法以搜索您的建议数据并返回一个 Cursor,该游标指向您认为相关的建议。

以下是系统按顺序传递给您的 query() 方法的参数摘要

  1. uri

    始终为内容 Uri,格式如下

    content://your.authority/optional.suggest.path/SUGGEST_URI_PATH_QUERY
    

    默认行为是系统传递此 URI 并将查询文本附加到其中

    content://your.authority/optional.suggest.path/SUGGEST_URI_PATH_QUERY/puppies
    

    末尾的查询文本使用 URI 编码规则进行编码,因此您可能需要在执行搜索之前对其进行解码。

    optional.suggest.path 部分仅在您使用 android:searchSuggestPath 属性在可搜索配置文件中设置此路径时包含在 URI 中。仅当您将相同的内容提供程序用于多个可搜索活动时才需要它。如果是这种情况,请区分建议查询的来源。

  2. projection
    始终为 null。
  3. selection
    在可搜索配置文 件的android:searchSuggestSelection属性中提供的 value,或者如果您未声明android:searchSuggestSelection属性,则为 null。以下部分将对此进行更深入的讨论。
  4. selectionArgs
    如果在可搜索配置中声明了android:searchSuggestSelection属性,则包含搜索查询作为数组中的第一个且唯一的元素。如果您未声明android:searchSuggestSelection,则此参数为 null。以下部分将对此进行更深入的讨论。
  5. sortOrder
    始终为 null。

系统可以通过两种方式向您发送搜索查询文本。默认方式是将查询文本作为传递给uri参数的 content URI 的最后一个路径包含在内。但是,如果在可搜索配置的android:searchSuggestSelection属性中包含 selection 值,则查询文本将作为selectionArgs字符串数组的第一个元素传递。接下来将描述这两个选项。

在 Uri 中获取查询

默认情况下,查询将作为uri参数(一个Uri对象)的最后一个片段附加。在这种情况下,要检索查询文本,请使用getLastPathSegment(),如下例所示

Kotlin

val query: String = uri.lastPathSegment.toLowerCase()

Java

String query = uri.getLastPathSegment().toLowerCase();

这将返回Uri的最后一个片段,即用户输入的查询文本。

在 selection 参数中获取查询

与其使用 URI,不如让您的query()方法接收执行查找所需的所有内容更有意义,并且您可能希望selectionselectionArgs参数携带相应的值。在这种情况下,将android:searchSuggestSelection属性添加到可搜索配置中,并使用您的 SQLite selection 字符串。在 selection 字符串中,包含一个问号(?)作为实际搜索查询的占位符。系统使用 selection 字符串作为selection参数,并使用搜索查询作为selectionArgs数组中的第一个元素来调用query()

例如,以下是如何形成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 数据库中、Web 服务器上的数据库中,还是设备或 Web 上的其他格式中,都请将建议格式化为表中的行,并使用Cursor呈现它们。

系统理解多个列,但只有两个是必需的

_ID
每个建议的唯一整数行 ID。系统需要此 ID 在ListView中呈现建议。
SUGGEST_COLUMN_TEXT_1
作为建议显示的字符串。

以下列都是可选的。大多数将在以下部分中进一步讨论。

SUGGEST_COLUMN_TEXT_2
一个字符串。如果您的Cursor包含此列,则所有建议都将以两行格式提供。此列中的字符串将显示为主建议文本下方较小的第二行文本。它可以为 null 或为空,表示没有辅助文本。
SUGGEST_COLUMN_ICON_1
一个可绘制资源、内容或文件 URI 字符串。如果您的Cursor包含此列,则所有建议都将以图标加文本的格式提供,左侧为可绘制图标。这可以为 null 或零,表示此行中没有图标。
SUGGEST_COLUMN_ICON_2
一个可绘制资源、内容或文件 URI 字符串。如果您的Cursor包含此列,则所有建议都将以图标加文本的格式提供,右侧为图标。这可以为 null 或零,表示此行中没有图标。
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。此列允许建议提供作为 intent 的EXTRA_DATA_KEY键中的额外数据包含的附加数据。
SUGGEST_COLUMN_QUERY
如果此列存在并且此元素存在于给定行中,则这是在形成建议的查询时使用的数据,作为 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。您必须定义 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 数据

当用户选择建议时,您的可搜索活动将接收具有您定义的操作的 intent(如上一节所述),但 intent 还必须携带数据,以便您的活动识别选择了哪个建议。具体来说,数据必须是每个建议的唯一内容,例如 SQLite 表中建议的行 ID。收到 intent 后,您可以使用getData()getDataString()检索附加的数据。

您可以通过两种方式定义包含在 intent 中的数据

  • 在建议表中 SUGGEST_COLUMN_INTENT_DATA 列内定义每个建议的数据。

    通过包含 SUGGEST_COLUMN_INTENT_DATA 列并在其中填充每行的唯一数据,为建议表中的每个意图提供所有必要的数据信息。此列中的数据将完全按照您在此列中定义的方式附加到意图中。然后,您可以使用 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 列的相应值以形成完整的 content URI。然后,您可以使用 getData() 检索 Uri

添加更多数据

如果您需要使用意图表达更多信息,可以添加另一个表列,例如 SUGGEST_COLUMN_INTENT_EXTRA_DATA,它可以存储有关建议的其他信息。保存在此列中的数据将放置在意图的额外 bundle 的 EXTRA_DATA_KEY 中。

处理意图

在您使用自定义意图提供自定义搜索建议后,您需要您的可搜索活动在用户选择建议时处理这些意图。这除了处理您的可搜索活动已经处理的 ACTION_SEARCH 意图之外。以下是如何在活动的 onCreate() 回调期间处理意图的示例。

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

在此示例中,意图操作为 ACTION_VIEW,数据携带指向建议项目的完整 URI,由 android:searchSuggestIntentData 字符串和 SUGGEST_COLUMN_INTENT_DATA_ID 列合成。然后,URI 传递到查询内容提供程序以获取 URI 指定的项目的本地 showResult() 方法。

重写查询文本

默认情况下,如果用户使用方向控件(例如轨迹球或方向键)浏览建议列表,则查询文本不会更新。但是,您可以暂时将用户在文本框中显示的查询文本重写为与焦点建议匹配的查询。这允许用户查看建议的查询,并且他们可以在将其分派为搜索之前选择搜索框并编辑查询。

您可以通过以下方式重写查询文本

  • android:searchMode 属性添加到您的可搜索配置中,并使用 "queryRewriteFromText" 值。在这种情况下,建议的 SUGGEST_COLUMN_TEXT_1 列中的内容用于重写查询文本。
  • android:searchMode 属性添加到您的可搜索配置中,并使用 "queryRewriteFromData" 值。在这种情况下,建议的 SUGGEST_COLUMN_INTENT_DATA 列中的内容用于重写查询文本。仅对旨在供用户查看的 URI 或其他数据格式(例如 HTTP URL)使用此方法。不要以这种方式使用内部 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 列。

关于快速搜索框建议排名

一旦您将应用的搜索建议提供给快速搜索框,快速搜索框排名就会决定如何在特定查询中向用户显示这些建议。这可能取决于有多少其他应用针对该查询提供了结果,以及用户选择您的结果与选择其他应用结果的频率。我们无法保证您的建议如何排名,也无法保证您的应用建议在给定查询中是否会显示。通常,提供高质量的结果会增加您的应用建议出现在突出位置的可能性,而提供低质量建议的应用更有可能排名较低或不显示。