Android TV 使用 Android 搜索界面从已安装的应用中检索内容数据,并向用户提供搜索结果。应用的内容数据可以包含在这些结果中,以便用户即时访问应用中的内容。
应用必须为 Android TV 提供数据字段,当用户在搜索对话框中输入字符时,Android TV 可以根据这些字段生成建议的搜索结果。为此,您的应用必须实现用于提供建议的 Content Provider,以及描述 Content Provider 和 Android TV 的其他重要信息的
searchable.xml
配置文件。您还需要一个 activity,用于处理用户选择建议的搜索结果时触发的 intent。如需了解详情,请参阅添加自定义搜索建议。本指南涵盖了特定于 Android TV 应用的要点。
在阅读本指南之前,请确保您已熟悉 Search API 指南中介绍的概念。另请参阅添加搜索功能。
本指南中的示例代码来自 Leanback 示例应用。
标识列
SearchManager
通过将预期数据字段表示为本地数据库的列来描述这些数据字段。无论数据采用何种格式,都必须将数据字段映射到这些列(通常位于访问内容数据的类中)。如需了解如何构建一个将现有数据映射到所需字段的类,请参阅
构建建议表。
SearchManager
类包含多个 Android TV 列。下表介绍了一些更重要的列。
值 | 说明 |
---|---|
SUGGEST_COLUMN_TEXT_1 |
内容的名称(必填) |
SUGGEST_COLUMN_TEXT_2 |
内容的文本说明 |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
内容的图片、海报或封面 |
SUGGEST_COLUMN_CONTENT_TYPE |
媒体的 MIME 类型 |
SUGGEST_COLUMN_VIDEO_WIDTH |
媒体的分辨率宽度 |
SUGGEST_COLUMN_VIDEO_HEIGHT |
媒体的分辨率高度 |
SUGGEST_COLUMN_PRODUCTION_YEAR |
内容的制作年份(必需) |
SUGGEST_COLUMN_DURATION |
媒体的持续时间(以毫秒为单位)(必需) |
搜索框架需要以下列:
当您的内容的这些列的值与 Google 服务器发现的其他提供方的相同内容的值匹配时,系统会在内容的详情视图中提供指向您应用的深层链接,并提供指向其他提供方的应用的链接。如需了解详情,请参阅详情屏幕中指向应用的深层链接部分。
应用的数据库类可能会按如下所示定义列:
Kotlin
class VideoDatabase { companion object { // The columns we'll include in the video database table val KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1 val KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2 val KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE val KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE val KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE val KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH val KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT val KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG val KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE val KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE val KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE val KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE val KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR val KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION val KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION ... } ... }
Java
public class VideoDatabase { // The columns we'll include in the video database table public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1; public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2; public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE; public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE; public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE; public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH; public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT; public static final String KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG; public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE; public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE; public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE; public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE; public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR; public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION; public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION; ...
构建从 SearchManager
列到数据字段的映射时,您还必须指定 _ID
以便为每一行指定一个唯一 ID。
Kotlin
companion object { .... private fun buildColumnMap(): Map<String, String> { return mapOf( KEY_NAME to KEY_NAME, KEY_DESCRIPTION to KEY_DESCRIPTION, KEY_ICON to KEY_ICON, KEY_DATA_TYPE to KEY_DATA_TYPE, KEY_IS_LIVE to KEY_IS_LIVE, KEY_VIDEO_WIDTH to KEY_VIDEO_WIDTH, KEY_VIDEO_HEIGHT to KEY_VIDEO_HEIGHT, KEY_AUDIO_CHANNEL_CONFIG to KEY_AUDIO_CHANNEL_CONFIG, KEY_PURCHASE_PRICE to KEY_PURCHASE_PRICE, KEY_RENTAL_PRICE to KEY_RENTAL_PRICE, KEY_RATING_STYLE to KEY_RATING_STYLE, KEY_RATING_SCORE to KEY_RATING_SCORE, KEY_PRODUCTION_YEAR to KEY_PRODUCTION_YEAR, KEY_COLUMN_DURATION to KEY_COLUMN_DURATION, KEY_ACTION to KEY_ACTION, BaseColumns._ID to ("rowid AS " + BaseColumns._ID), SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID), SearchManager.SUGGEST_COLUMN_SHORTCUT_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID) ) } }
Java
... private static HashMap<String, String> buildColumnMap() { HashMap<String, String> map = new HashMap<String, String>(); map.put(KEY_NAME, KEY_NAME); map.put(KEY_DESCRIPTION, KEY_DESCRIPTION); map.put(KEY_ICON, KEY_ICON); map.put(KEY_DATA_TYPE, KEY_DATA_TYPE); map.put(KEY_IS_LIVE, KEY_IS_LIVE); map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH); map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT); map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG); map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE); map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE); map.put(KEY_RATING_STYLE, KEY_RATING_STYLE); map.put(KEY_RATING_SCORE, KEY_RATING_SCORE); map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR); map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION); map.put(KEY_ACTION, KEY_ACTION); map.put(BaseColumns._ID, "rowid AS " + BaseColumns._ID); map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); return map; } ...
在前面的示例中,请注意到 SUGGEST_COLUMN_INTENT_DATA_ID
字段的映射。这是 URI 的一部分,指向该行中数据独有的内容(URI 的最后一部分),用于描述内容的存储位置。URI 的第一部分(如果表中的所有行都通用)在 searchable.xml
文件中设置为
android:searchSuggestIntentData
属性,如处理搜索建议部分中所述。
如果 URI 第一部分对于表中每一行都是不同的,请使用 SUGGEST_COLUMN_INTENT_DATA
字段映射该值。当用户选择此内容时,触发的 intent 会提供 SUGGEST_COLUMN_INTENT_DATA_ID
与 android:searchSuggestIntentData
属性或 SUGGEST_COLUMN_INTENT_DATA
字段值的组合中的 intent 数据。
提供搜索建议数据
实现 Content Provider,以将搜索字词建议返回到 Android TV 搜索对话框。每次输入字母时,系统都会通过调用 query()
方法向您的 content provider 查询建议。在您的 query()
实现中,content provider 会搜索建议数据并返回 Cursor
,它指向您指定用于提供建议的行。
Kotlin
fun query(uri: Uri, projection: Array<String>, selection: String, selectionArgs: Array<String>, sortOrder: String): Cursor { // Use the UriMatcher to see what kind of query we have and format the db query accordingly when (URI_MATCHER.match(uri)) { SEARCH_SUGGEST -> { Log.d(TAG, "search suggest: ${selectionArgs[0]} URI: $uri") if (selectionArgs == null) { throw IllegalArgumentException( "selectionArgs must be provided for the Uri: $uri") } return getSuggestions(selectionArgs[0]) } else -> throw IllegalArgumentException("Unknown Uri: $uri") } } private fun getSuggestions(query: String): Cursor { val columns = arrayOf<String>( BaseColumns._ID, VideoDatabase.KEY_NAME, VideoDatabase.KEY_DESCRIPTION, VideoDatabase.KEY_ICON, VideoDatabase.KEY_DATA_TYPE, VideoDatabase.KEY_IS_LIVE, VideoDatabase.KEY_VIDEO_WIDTH, VideoDatabase.KEY_VIDEO_HEIGHT, VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, VideoDatabase.KEY_PURCHASE_PRICE, VideoDatabase.KEY_RENTAL_PRICE, VideoDatabase.KEY_RATING_STYLE, VideoDatabase.KEY_RATING_SCORE, VideoDatabase.KEY_PRODUCTION_YEAR, VideoDatabase.KEY_COLUMN_DURATION, VideoDatabase.KEY_ACTION, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID ) return videoDatabase.getWordMatch(query.toLowerCase(), columns) }
Java
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Use the UriMatcher to see what kind of query we have and format the db query accordingly switch (URI_MATCHER.match(uri)) { case SEARCH_SUGGEST: Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri); if (selectionArgs == null) { throw new IllegalArgumentException( "selectionArgs must be provided for the Uri: " + uri); } return getSuggestions(selectionArgs[0]); default: throw new IllegalArgumentException("Unknown Uri: " + uri); } } private Cursor getSuggestions(String query) { query = query.toLowerCase(); String[] columns = new String[]{ BaseColumns._ID, VideoDatabase.KEY_NAME, VideoDatabase.KEY_DESCRIPTION, VideoDatabase.KEY_ICON, VideoDatabase.KEY_DATA_TYPE, VideoDatabase.KEY_IS_LIVE, VideoDatabase.KEY_VIDEO_WIDTH, VideoDatabase.KEY_VIDEO_HEIGHT, VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, VideoDatabase.KEY_PURCHASE_PRICE, VideoDatabase.KEY_RENTAL_PRICE, VideoDatabase.KEY_RATING_STYLE, VideoDatabase.KEY_RATING_SCORE, VideoDatabase.KEY_PRODUCTION_YEAR, VideoDatabase.KEY_COLUMN_DURATION, VideoDatabase.KEY_ACTION, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID }; return videoDatabase.getWordMatch(query, columns); } ...
在您的清单文件中,内容提供程序会受到特殊对待。它不会被标记为 activity,而是被描述为 <provider>
。提供程序包含 android:authorities
属性,用于将内容提供程序的命名空间告知系统。此外,必须将其 android:exported
属性设置为 "true"
,以便 Android 全局搜索可以使用从中返回的结果。
<provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" />
处理搜索建议
您的应用必须包含
res/xml/searchable.xml
文件以配置搜索建议设置。
在 res/xml/searchable.xml
文件中,添加
android:searchSuggestAuthority
属性,将内容提供程序的命名空间告知系统。此字符串值必须与您在 AndroidManifest.xml
文件中 <provider>
元素的 android:authorities
属性中指定的字符串值一致。
此外,还要添加一个标签,这是应用的名称。系统搜索设置在枚举可搜索应用时使用此标签。
searchable.xml
文件还必须包含值为 "android.intent.action.VIEW"
的
android:searchSuggestIntentAction
,以定义用于提供自定义建议的 intent 操作。这与用于提供搜索字词的 intent 操作不同,如以下部分所述。如需了解为建议声明 intent 操作的其他方式,请参阅声明 intent 操作。
应用还必须随 intent 操作一起提供 intent 数据,这可以使用
android:searchSuggestIntentData
属性指定。这是指向相应内容的 URI 的第一部分,用于说明该内容映射表中所有行共有的 URI 部分。URI 中每行的唯一部分通过 SUGGEST_COLUMN_INTENT_DATA_ID
字段确定,如标识列部分中所述。如需了解声明 intent 数据以提供建议的其他方式,请参阅声明 intent 数据。
android:searchSuggestSelection=" ?"
属性指定作为 query()
方法的 selection
参数传递的值。系统会将问号 (?
) 值替换为查询文本。
最后,您还必须添加
android:includeInGlobalSearch
属性并将值设为 "true"
。下面是一个 searchable.xml
文件示例:
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label" android:hint="@string/search_hint" android:searchSettingsDescription="@string/settings_description" android:searchSuggestAuthority="com.example.android.tvleanback" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback" android:searchSuggestSelection=" ?" android:searchSuggestThreshold="1" android:includeInGlobalSearch="true"> </searchable>
处理搜索字词
只要搜索对话框中包含的字词与应用的其中一个列中的值匹配(如标识列部分中所述),系统就会触发 ACTION_SEARCH
intent。应用中负责处理该 intent 的 activity 会在代码库中搜索值中包含指定字词的列,并返回包含这些列的内容项列表。在 AndroidManifest.xml
文件中,指定用于处理 ACTION_SEARCH
intent 的 activity,如以下示例所示:
... <activity android:name="com.example.android.tvleanback.DetailsActivity" android:exported="true"> <!-- Receives the search request. --> <intent-filter> <action android:name="android.intent.action.SEARCH" /> <!-- No category needed, because the Intent will specify this class component --> </intent-filter> <!-- Points to searchable meta data. --> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> </activity> ... <!-- Provides search suggestions for keywords against video meta data. --> <provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" /> ...
activity 还必须描述引用了 searchable.xml
文件的可搜索配置。如需使用全局搜索对话框,清单必须描述哪个 activity 应收到搜索查询。清单还必须完全按照 searchable.xml
文件中描述的方式描述 <provider>
元素。
详情屏幕中指向应用的深层链接
如果您已按照处理搜索建议部分中的说明设置了搜索配置,并映射了 SUGGEST_COLUMN_TEXT_1
、SUGGEST_COLUMN_PRODUCTION_YEAR
和 SUGGEST_COLUMN_DURATION
字段(如标识列部分中所述),那么当用户选择搜索结果时,系统会启动一个指向您内容的观看操作的
深层链接:
当用户选择您的应用的链接(通过详情屏幕中的 **Available On** 按钮标识)时,系统会启动用于处理 ACTION_VIEW
在 searchable.xml
文件中设置为
android:searchSuggestIntentAction
且值为 "android.intent.action.VIEW"
的 activity。
您还可以设置自定义 intent 来启动您的 activity。
Leanback 示例应用中对此进行了演示。请注意,示例应用会启动自己的 LeanbackDetailsFragment
来显示所选媒体的详细信息;在您的应用中,启动可立即播放媒体的 activity,让用户再点击一两次。
搜索行为
在 Android TV 中,可以从主屏幕和应用内部进行搜索。这两种情况的搜索结果有所不同。
在主屏幕上搜索
当用户从主屏幕进行搜索时,第一条结果会显示在实体卡片中。如果存在可以播放内容的应用,卡片底部会显示每个应用的链接:
您无法以程序化方式将应用放入实体卡片中。若要作为播放选项包含在内,应用的搜索结果必须与搜索内容的标题、年份和时长匹配。
卡片下方可能会提供更多搜索结果。若要查看它们,用户必须按下遥控器并向下滚动。每个应用的结果会显示在单独的一行中。您无法控制行排序。首先列出的是支持观看操作的应用。
在应用中搜索
用户还可以通过遥控器或游戏手柄控制器启动麦克风,从您的应用内启动搜索。搜索结果显示在应用内容顶部的一行中。 您的应用会使用自己的全局搜索提供程序生成搜索结果。
了解详情
如需详细了解如何搜索 TV 应用,请阅读将 Android 搜索功能集成到您的应用中和添加搜索功能。
如需详细了解如何使用 SearchFragment
自定义应用内搜索体验,请阅读在 TV 应用内搜索。