企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
> 编写:[awong1900](https://github.com/awong1900) - 原文:[http://developer.android.com/training/tv/discovery/searchable.html](http://developer.android.com/training/tv/discovery/searchable.html) 安卓TV使用安卓[搜索接口](http://developer.android.com/guide/topics/search/index.html)从安装的应用中检索内容数据并且释放搜索结果给用户。你的应用内容数据能被包含在这些结果中,去给用户即时访问应用程序中的内容。 你的应用必须提供安卓TV数据字段,它是用户在搜索框中输入字符生成的建议搜索结果。去做这个,你的应用必须实现[Content Provider](http://developer.android.com/guide/topics/providers/content-providers.html),在[searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)配置文件描述content provider和其他必要的安卓TV信息。你也需要一个[activity](# "An activity represents a single screen with a user interface.")在用户选择一个建议的搜索结果时处理intent的触发。所有的这些被描述在[Adding Custom Suggestions](http://developer.android.com/guide/topics/search/adding-custom-suggestions.html)。本文描述安卓TV应用搜索的关键点。 这节课展示安卓中搜索的知识,展示如何使你的应用在安卓TV里是可被搜索的。确信你熟悉[Search API guide](http://developer.android.com/guide/topics/search/index.html)的解释。在下面的这节课程之前,查看[Adding Search Functionality](http://developer.android.com/training/search/index.html)训练。 这个讨论描述了一些代码,从[安卓Leanback示例代码](https://github.com/googlesamples/androidtv-Leanback)摘出。代码可以在Github上找到。 ### 识别列 [SearchManager](http://developer.android.com/reference/android/app/SearchManager.html)描述了数据字段,它被代表为SOLite数据库的列。不管你的数据格式,你必须把你的数据字段填到那些列,通常用存取你的内容数据的类。更多信息,查看[Building a suggestion table()](http://developer.android.com/guide/topics/search/adding-custom-suggestions.html#SuggestionTable)。 SearchManager类为安卓TV包含了几个列。下面是重要的一些列: | 值 | 描述 | |-----|-----| | `SUGGEST_COLUMN_TEXT_1` | 内容名字 **(required)** | | `SUGGEST_COLUMN_TEXT_2` | 内容的文本描述 | | `SUGGEST_COLUMN_RESULT_CARD_IMAGE` | 图片/封面 | | `SUGGEST_COLUMN_CONTENT_TYPE` | 媒体的MIME类型 **(required)** | | `SUGGEST_COLUMN_VIDEO_WIDTH` | 媒体的分辨率宽度`SUGGEST_COLUMN_VIDEO_HEIGHT` | 媒体的分辨率高度 | | `SUGGEST_COLUMN_PRODUCTION_YEAR` | 内容的产品年份 **(required)** | | `SUGGEST_COLUMN_DURATION` | 媒体的时间长度 | 搜索framework需要以下的列: - [SUGGEST_COLUMN_TEXT_1](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_TEXT_1) - [SUGGEST_COLUMN_CONTENT_TYPE](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_CONTENT_TYPE) - [SUGGEST_COLUMN_PRODUCTION_YEAR](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_PRODUCTION_YEAR) 当这些内容的列的值匹配Google服务的providers提供的的值时,系统提供一个[深链接](http://developer.android.com/training/app-indexing/deep-linking.html)到你的应用,用于详情查看,以及指向应用的其他Providers的链接。更多讨论在[在详情页显示内容](http://developer.android.com/training/tv/discovery/searchable.html#details)。 你的应用的数据库类可能定义以下的列: ~~~ 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](http://developer.android.com/reference/android/app/SearchManager.html)列填充到你的数据字段时,你也必须定义[_ID](http://developer.android.com/reference/android/provider/BaseColumns.html#_ID)去获得每行的独一无二的ID。 ~~~ ... private static HashMap buildColumnMap() { HashMap map = new HashMap(); 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](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_DATA_ID)字段。这是URI的一部分,指向独一无二的内容到这一列的数据,那是URI描述的内容被存储的最后部分。在URI的第一部分,与所有表格的列同样,是设置[在searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)文件,用[android:searchSuggestIntentData](http://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestIntentData)属性。属性被描述在[Handle Search Suggestions](http://developer.android.com/training/tv/discovery/searchable.html#suggestions)。 如果URI的第一部分是不同于表格的每一列,你填充[SUGGEST_COLUMN_INTENT_DATA](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_DATA)字段的值。当用户选择这个内容时,这个intent被启动依据[SUGGEST_COLUMN_INTENT_DATA_ID](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_DATA_ID)的混合intent数据或者`android:searchSuggestIntentData`属性和[SUGGEST_COLUMN_INTENT_DATA](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_DATA)字段值之一。 ### 提供搜索建议数据 实现一个[Content Provider](http://developer.android.com/guide/topics/providers/content-providers.html)去返回搜索术语建议到安卓TV搜索框。系统需要你的内容容器提供建议,通过调用每次一个字母类型[query()](http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String))方法。在[query()](http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String))的实现中,你的内容容器搜索你的建议数据并且返回一个光标指向你已经指定的建议列。 ~~~ @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 mVideoDatabase.getWordMatch(query, columns); } ... ~~~ 在你的manifest文件中,内容容器接受特殊处理。相比被标记为一个[activity](# "An activity represents a single screen with a user interface."),它是被描述为[[provider](http://developer.android.com/guide/topics/manifest/provider-element.html)](#)。provider包括`android:searchSuggestAuthority`属性去告诉系统你的内容容器的名字空间。并且,你必须设置它的`android:exported`属性为`"true"`,这样安卓全局搜索能用它返回的搜索结果。 ~~~ <provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" /> ~~~ ### 处理搜索建议 你的应用必须包括[res/xml/searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)文件去配置搜索建议设置。它包括[android:searchSuggestAuthority](http://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestAuthority)属性去告诉系统内容容器的名字空间。这必须匹配在`AndroidManifest.xml`文件的[[provider](http://developer.android.com/guide/topics/manifest/provider-element.html)](#)元素的[android:authorities](http://developer.android.com/guide/topics/manifest/provider-element.html#auth) 属性的字符串值。 [searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)文件必须也包含在`"android.intent.action.VIEW"`的[android:searchSuggestIntentAction](http://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestIntentAction)值去定义提供自定义建议的intent action。这与提供一个搜索术语的intent action不同,下面解释。查看[Declaring the intent action](http://developer.android.com/guide/topics/search/adding-custom-suggestions.html#IntentAction) 用另一种方式去定义建议的intent action。 同intent action一起,你的应用必须提供你定义的[android:searchSuggestIntentData](http://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestIntentData)属性的intent数据。这是指向内容的URI的第一部分。它描述在填充的内容表格中URI所有共同列的部分。URI的独一无二的部分用 [SUGGEST_COLUMN_INTENT_DATA_ID](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_INTENT_DATA_ID)字段建立每一列,以上被描述在[识别列](http://developer.android.com/training/tv/discovery/searchable.html#columns)。查看[Declaring the intent data](http://developer.android.com/guide/topics/search/adding-custom-suggestions.html#IntentData)用另一种方式去定义建议的intent数据。 并且,注意`android:searchSuggestSelection="?"`属性为特定的值。这个值作为[query()](http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String))方法`selection`参数。方法的问题标记(?)值被代替为请求文本。 最后,你也必须包含[android:includeInGlobalSearch](http://developer.android.com/guide/topics/search/searchable-config.html#includeInGlobalSearch)属性值为`"true"`。这是一个[searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)文件的例子: ~~~ <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> ~~~ ### 处理搜索术语 一旦搜索框有一个字匹配到了应用列中的一个(被描述在上文的[识别列](http://developer.android.com/training/tv/discovery/searchable.html#identifying)),系统启动[ACTION_SEARCH](http://developer.android.com/reference/android/content/Intent.html#ACTION_SEARCH) intent。你应用的[activity](# "An activity represents a single screen with a user interface.")处理intent搜索列的给定的字段资源,并且返回一个那些内容项的列表。在你的`AndroidManifest.xml`文件中,你指定的[activity](# "An activity represents a single screen with a user interface.")处理[ACTION_SEARCH](http://developer.android.com/reference/android/content/Intent.html#ACTION_SEARCH) intent,像这样: ~~~ ... <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](# "An activity represents a single screen with a user interface.")必须参考[searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)文件描述可搜索的设置。用[全局搜索框](http://developer.android.com/guide/topics/search/search-dialog.html),manifest必须描述[activity](# "An activity represents a single screen with a user interface.")应该收到的搜索请求。manifest必须描述[[provider](http://developer.android.com/guide/topics/manifest/provider-element.html)](#)元素,详细被描述在[searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html)文件。 ### 深链接到应用的详情页 如果你有设置[处理搜索建议](http://developer.android.com/training/tv/discovery/searchable.html#suggestions)描述的搜索配置和填充 [SUGGEST_COLUMN_TEXT_1](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_TEXT_1),[SUGGEST_COLUMN_CONTENT_TYPE](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_CONTENT_TYPE)和[SUGGEST_COLUMN_PRODUCTION_YEAR](http://developer.android.com/reference/android/app/SearchManager.html#SUGGEST_COLUMN_PRODUCTION_YEAR)字段到[识别列](http://developer.android.com/training/tv/discovery/searchable.html#columns),一个[深链接](http://developer.android.com/training/app-indexing/deep-linking.html)去查看详情页的内容。当用户选择一个搜索结果时,详情页将打开。如图1。 ![deep-link](https://box.kancloud.cn/2015-07-28_55b72474b172b.png) **图1** 详情页显示一个深链接为Google(Leanback)的视频代码。Sintel: © copyright Blender Foundation, www.sintel.org. 当用户选择你的应用链接,`“Available On”`按钮被标识在详情页,系统启动[activity](# "An activity represents a single screen with a user interface.")处理[ACTION_VIEW](http://developer.android.com/reference/android/content/Intent.html#ACTION_VIEW)(在[searchable.xml](http://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestIntentAction)文件设置[android:searchSuggestIntentAction](http://developer.android.com/guide/topics/search/searchable-config.html#searchSuggestIntentAction)值为`"android.intent.action.VIEW"`)。 你也能设置用户intent去启动你的[activity](# "An activity represents a single screen with a user interface."),这个在[在安卓Leanback示例代码应用](https://github.com/googlesamples/androidtv-Leanback)中演示。注意示例应用启动它自己的`LeanbackDetailsFragment`去显示被选择媒体的详情,但是你应该启动[activity](# "An activity represents a single screen with a user interface.")去播放媒体。立即去保存用户的另一次或两次点击。 [下一节: 使TV应用是可被搜索的](#)