Android TV использует интерфейс поиска Android для получения данных о контенте из установленных приложений и доставки результатов поиска пользователю. Данные контента вашего приложения могут быть включены в эти результаты, чтобы предоставить пользователю мгновенный доступ к контенту вашего приложения.
Ваше приложение должно предоставить Android TV поля данных, из которых Android TV может генерировать предлагаемые результаты поиска по мере того, как пользователь вводит символы в диалоговом окне поиска. Для этого в вашем приложении должен быть реализован поставщик контента , который предоставляет предложения вместе с файлом конфигурации searchable.xml
, который описывает поставщика контента и другую важную информацию для Android TV. Вам также понадобится действие, которое обрабатывает намерение, которое срабатывает, когда пользователь выбирает предложенный результат поиска. Более подробную информацию см. в разделе Добавление предложений пользовательского поиска . В этом руководстве рассматриваются основные моменты, относящиеся к приложениям Android TV.
Прежде чем читать это руководство, убедитесь, что вы знакомы с концепциями, описанными в руководстве по 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, система предоставляет глубокую ссылку на ваше приложение в подробном представлении контента, а также ссылки на приложения других поставщиков. . Подробнее это обсуждается в глубокой ссылке на ваше приложение в разделе экрана сведений .
Класс базы данных вашего приложения может определять столбцы следующим образом:
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
...
}
...
}
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
, чтобы присвоить каждой строке уникальный идентификатор.
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)
)
}
}
...
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
. Когда пользователь выбирает это содержимое, сгенерированное намерение предоставляет данные о намерении из комбинации SUGGEST_COLUMN_INTENT_DATA_ID
и либо атрибута android:searchSuggestIntentData
, либо значения поля SUGGEST_COLUMN_INTENT_DATA
.
Предоставьте данные поисковых предложений
Внедрите поставщика контента , чтобы возвращать предложения по поисковым запросам в диалоговое окно поиска Android TV. Система запрашивает у вашего поставщика контента предложения, вызывая метод query()
каждый раз при вводе буквы. В вашей реализации query()
ваш контент-провайдер ищет данные вашего предложения и возвращает Cursor
, указывающий на строки, которые вы назначили для предложений.
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)
}
@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);
}
...
В файле манифеста поставщику контента уделяется особое внимание. Вместо того, чтобы быть помеченным как действие, оно описывается как <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
чтобы сообщить системе пространство имен вашего поставщика контента. Оно должно соответствовать строковому значению, указанному вами в атрибуте android:authorities
элемента <provider>
в файле AndroidManifest.xml
.
Также добавьте метку , которая является названием приложения. Настройки системного поиска используют эту метку при перечислении приложений, доступных для поиска.
Файл searchable.xml
также должен включать android:searchSuggestIntentAction
со значением "android.intent.action.VIEW"
чтобы определить действие намерения для предоставления специального предложения. Это отличается от намерения ввести поисковый запрос , как описано в следующем разделе. Другие способы объявления действия с намерением для предложений см. в разделе Объявление действия с намерением .
Наряду с действием намерения ваше приложение должно предоставить данные намерения, которые вы указываете с помощью атрибута android:searchSuggestIntentData
. Это первая часть URI, указывающая на контент, которая описывает часть URI, общую для всех строк в таблице сопоставления этого контента. Часть URI, уникальная для каждой строки, устанавливается с помощью поля SUGGEST_COLUMN_INTENT_DATA_ID
, как описано в разделе «Идентификация столбцов» . Другие способы объявления данных о намерениях для предложений см. в разделе Объявление данных о намерениях .
android:searchSuggestSelection=" ?"
Атрибут указывает значение, переданное в качестве параметра selection
метода query()
. Значение вопросительного знака ( ?
) заменяется текстом запроса.
Наконец, вы также должны включить атрибут 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
. Действие в вашем приложении, которое обрабатывает это намерение, ищет в репозитории столбцы с заданным словом в значениях и возвращает список элементов контента с этими столбцами. В файле AndroidManifest.xml
вы указываете действие, которое обрабатывает намерение ACTION_SEARCH
, как показано в следующем примере:
...
<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" />
...
Действие также должно описывать конфигурацию с возможностью поиска со ссылкой на файл searchable.xml
. Чтобы использовать диалог глобального поиска , в манифесте должно быть описано, какое действие должно получать поисковые запросы. Манифест также должен описывать элемент <provider>
точно так, как он описан в файле searchable.xml
.
Глубокая ссылка на ваше приложение на экране сведений
Если вы настроили конфигурацию поиска, как описано в разделе «Обработка поисковых предложений» , и сопоставили поля SUGGEST_COLUMN_TEXT_1
, SUGGEST_COLUMN_PRODUCTION_YEAR
и SUGGEST_COLUMN_DURATION
, как описано в разделе «Определение столбцов» , на экране сведений появится глубокая ссылка на действие по просмотру вашего контента. запускается, когда пользователь выбирает результат поиска:

Когда пользователь выбирает ссылку на ваше приложение, обозначенную кнопкой **Доступно** на экране сведений, система запускает действие, которое обрабатывает ACTION_VIEW
, установленный как android:searchSuggestIntentAction
со значением "android.intent.action.VIEW"
в файле searchable.xml
.
Вы также можете настроить собственное намерение для запуска своей деятельности. Это продемонстрировано в примере приложения Leanback . Обратите внимание, что пример приложения запускает собственный LeanbackDetailsFragment
чтобы отобразить сведения о выбранном носителе; в своих приложениях немедленно запустите действие, которое воспроизводит мультимедиа, чтобы сэкономить пользователю еще один или два клика.
Поисковое поведение
Поиск доступен на Android TV с главного экрана и из приложения. Результаты поиска в этих двух случаях различны.
Поиск с главного экрана
Когда пользователь выполняет поиск с главного экрана, первый результат появляется в карточке объекта. Если есть приложения, которые могут воспроизводить контент, ссылка на каждое из них появится внизу карточки:

Вы не можете программно поместить приложение в карточку объекта. Чтобы быть включенными в качестве параметра воспроизведения, результаты поиска приложения должны соответствовать названию, году и продолжительности искомого контента.
Дополнительные результаты поиска могут быть доступны под карточкой. Чтобы увидеть их, пользователь должен нажать на пульте и прокрутить вниз. Результаты для каждого приложения отображаются в отдельной строке. Вы не можете контролировать порядок строк. Приложения, поддерживающие действия просмотра, указаны первыми.

Поиск из вашего приложения
Пользователь также может начать поиск из вашего приложения, включив микрофон с пульта дистанционного управления или контроллера игровой панели. Результаты поиска отображаются в одной строке поверх содержимого приложения. Ваше приложение генерирует результаты поиска, используя собственную глобальную поисковую систему .

Узнать больше
Чтобы узнать больше о поиске в приложении для телевизора, прочтите разделы «Интеграция функций поиска Android в ваше приложение» и «Добавление функций поиска» .
Дополнительные сведения о том, как настроить поиск в приложении с помощью SearchFragment
, см. в статье Поиск в приложениях для ТВ .