Android TV używa interfejsu wyszukiwania Androida do pobierania danych o treściach z zainstalowanych aplikacji i wyświetlania wyników wyszukiwania użytkownikowi. Dane o treściach z Twojej aplikacji mogą być uwzględniane w tych wynikach, aby użytkownik miał natychmiastowy dostęp do treści w Twojej aplikacji.
Twoja aplikacja musi udostępniać Androidowi TV pola danych, na podstawie których Android TV może generować sugerowane wyniki wyszukiwania, gdy użytkownik wpisuje znaki w oknie wyszukiwania. Aby to zrobić, Twoja aplikacja musi zaimplementować
dostawcę treści, który udostępnia
sugestie wraz z plikiem konfiguracyjnym
searchable.xml opisującym dostawcę treści
i inne ważne informacje dla Androida TV. Potrzebujesz też aktywności, która obsługuje intencję uruchamianą, gdy użytkownik wybierze sugerowany wynik wyszukiwania. Więcej informacji znajdziesz w artykule Dodawanie
niestandardowych sugestii wyszukiwania. Ten przewodnik zawiera najważniejsze informacje dotyczące aplikacji na Androida TV.
Zanim przeczytasz ten przewodnik, zapoznaj się z pojęciami wyjaśnionymi w przewodniku po interfejsie Search API. Zapoznaj się też z artykułem Dodawanie funkcji wyszukiwania.
Przykładowy kod w tym przewodniku pochodzi z przykładowej aplikacji Leanback .
Określanie kolumn
Klasa SearchManager opisuje pola danych, których oczekuje, przedstawiając je jako kolumny lokalnej bazy danych. Niezależnie od formatu danych musisz zmapować pola danych na te kolumny, zwykle w klasie, która uzyskuje dostęp do danych o treściach. Informacje o tworzeniu
klasy, która mapuje istniejące dane na wymagane pola, znajdziesz w artykule
Tworzenie tabeli sugestii.
Klasa SearchManager zawiera kilka kolumn dla Androida TV. Niektóre z ważniejszych kolumn zostały opisane w tabeli poniżej.
| Wartość | Opis |
|---|---|
SUGGEST_COLUMN_TEXT_1 |
Nazwa treści (wymagane) |
SUGGEST_COLUMN_TEXT_2 |
Tekstowy opis treści |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
Obraz, plakat lub okładka treści |
SUGGEST_COLUMN_CONTENT_TYPE |
Typ MIME multimediów |
SUGGEST_COLUMN_VIDEO_WIDTH |
Szerokość rozdzielczości multimediów |
SUGGEST_COLUMN_VIDEO_HEIGHT |
Wysokość rozdzielczości multimediów |
SUGGEST_COLUMN_PRODUCTION_YEAR |
Rok produkcji treści (wymagane) |
SUGGEST_COLUMN_DURATION |
Czas trwania multimediów w milisekundach (wymagane) |
Platforma wyszukiwania wymaga tych kolumn:
Gdy wartości tych kolumn dla Twoich treści są zgodne z wartościami tych samych treści od innych dostawców znalezionych przez serwery Google, system udostępnia precyzyjny link do Twojej aplikacji w widoku szczegółów treści oraz linki do aplikacji innych dostawców. Więcej informacji znajdziesz w sekcji Precyzyjny link do Twojej aplikacji na ekranie szczegółów.
Klasa bazy danych Twojej aplikacji może definiować kolumny w ten sposób:
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; ...
Gdy tworzysz mapę z kolumn SearchManager na pola danych, musisz też określić _ID, aby każdy wiersz miał unikalny identyfikator.
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; } ...
W poprzednim przykładzie zwróć uwagę na mapowanie na pole SUGGEST_COLUMN_INTENT_DATA_ID. Jest to część identyfikatora URI, która wskazuje treści unikalne dla danych w tym wierszu – ostatnia część identyfikatora URI, która opisuje, gdzie są przechowywane treści. Pierwsza część identyfikatora URI,
jeśli jest wspólna dla wszystkich wierszy w tabeli, jest ustawiana w pliku
searchable.xml jako atrybut
android:searchSuggestIntentData, jak opisano w sekcji
Obsługa sugestii wyszukiwania.
Jeśli pierwsza część identyfikatora URI jest inna dla każdego wiersza w tabeli, zmapuj tę wartość na pole SUGGEST_COLUMN_INTENT_DATA.
Gdy użytkownik wybierze te treści, uruchomiona intencja udostępni dane intencji z
połączenia SUGGEST_COLUMN_INTENT_DATA_ID
oraz atrybutu android:searchSuggestIntentData lub wartości pola
SUGGEST_COLUMN_INTENT_DATA.
Udostępnianie danych sugestii wyszukiwania
Zaimplementuj dostawcę treści
, aby zwracać sugestie wyszukiwanych haseł do okna wyszukiwania Androida TV. System wysyła zapytania do dostawcy treści o sugestie, wywołując metodę query() za każdym razem, gdy użytkownik wpisze literę. W implementacji query() dostawca treści przeszukuje dane sugestii i zwraca Cursor, który wskazuje wiersze wyznaczone jako sugestie.
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); } ...
W pliku manifestu dostawca treści jest traktowany w specjalny sposób. Zamiast być
oznaczony jako aktywność, jest opisany jako
<provider>. Dostawca zawiera atrybut android:authorities, który informuje system o przestrzeni nazw dostawcy treści. Musisz też ustawić jego atrybut android:exported na wartość "true", aby wyszukiwanie globalne Androida mogło korzystać z zwracanych przez niego wyników.
<provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" />
Obsługa sugestii wyszukiwania
Twoja aplikacja musi zawierać plik
res/xml/searchable.xml, aby skonfigurować ustawienia sugestii wyszukiwania.
W pliku res/xml/searchable.xml uwzględnij
atrybut
android:searchSuggestAuthority, aby poinformować system o przestrzeni nazw dostawcy treści. Musi on być zgodny z wartością ciągu znaków określoną w
android:authorities
atrybucie elementu <provider>
w pliku AndroidManifest.xml.
Uwzględnij też etykietę, czyli nazwę aplikacji. Ustawienia wyszukiwania systemowego używają tej etykiety podczas wyliczania aplikacji, w których można wyszukiwać.
Plik searchable.xml musi też zawierać atrybut
android:searchSuggestIntentAction z wartością "android.intent.action.VIEW"
, aby zdefiniować działanie intencji w przypadku udostępniania niestandardowej sugestii. Różni się to od działania intencji
w przypadku udostępniania wyszukiwanego hasła, jak opisano w następnej sekcji.
Inne sposoby deklarowania działania intencji w przypadku sugestii,
zobacz Deklarowanie
działania intencji.
Oprócz działania intencji Twoja aplikacja musi udostępniać dane intencji, które określasz za pomocą atrybutu
android:searchSuggestIntentData. Jest to pierwsza część identyfikatora URI, która wskazuje treści i opisuje część identyfikatora URI wspólnego dla wszystkich wierszy w tabeli mapowania tych treści. Część identyfikatora URI, która jest unikalna dla każdego wiersza, jest określana za pomocą pola SUGGEST_COLUMN_INTENT_DATA_ID,
jak opisano w sekcji Określanie kolumn.
Inne sposoby deklarowania danych intencji w przypadku sugestii znajdziesz w artykule
Deklarowanie
danych intencji.
Atrybut android:searchSuggestSelection=" ?" określa wartość przekazywaną jako parametr selection metody query(). Znak zapytania (?) jest zastępowany tekstem zapytania.
Na koniec musisz też uwzględnić atrybut
android:includeInGlobalSearch z wartością "true". Oto przykład pliku 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>
Obsługa wyszukiwanych haseł
Gdy tylko w oknie wyszukiwania pojawi się słowo, które pasuje do wartości w jednej z kolumn Twojej aplikacji, jak
opisano w sekcji Określanie kolumn, system uruchomi intencję
ACTION_SEARCH.
Aktywność w Twojej aplikacji, która obsługuje tę intencję, przeszukuje repozytorium pod kątem kolumn z danym słowem w ich wartościach i zwraca listę elementów treści z tymi kolumnami. W pliku AndroidManifest.xml wyznacz aktywność, która obsługuje intencję ACTION_SEARCH, jak pokazano w tym przykładzie:
... <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" /> ...
Aktywność musi też opisywać konfigurację wyszukiwania za pomocą odniesienia do pliku
searchable.xml.
Aby można było używać okna wyszukiwania globalnego,
manifest musi opisywać, która aktywność ma otrzymywać zapytania. Manifest musi też
opisywać element <provider>
dokładnie tak, jak jest on opisany w pliku searchable.xml.
Precyzyjny link do Twojej aplikacji na ekranie szczegółów
Jeśli skonfigurujesz wyszukiwanie zgodnie z opisem w sekcji Obsługa sugestii i zmapujesz pola SUGGEST_COLUMN_TEXT_1,
SUGGEST_COLUMN_PRODUCTION_YEAR, i
SUGGEST_COLUMN_DURATION zgodnie z opisem w
sekcji Określanie kolumn, na ekranie szczegółów, który pojawi się, gdy
użytkownik wybierze wynik wyszukiwania, pojawi się
precyzyjny link do działania oglądania Twoich treści:
Gdy użytkownik wybierze link do Twojej aplikacji, oznaczony przyciskiem **Dostępne w** na
ekranie szczegółów, system uruchomi aktywność, która obsługuje ACTION_VIEW
ustawioną jako
android:searchSuggestIntentAction z wartością "android.intent.action.VIEW" w
pliku searchable.xml.
Możesz też skonfigurować niestandardową intencję, aby uruchamiać aktywność. Pokazujemy to w
przykładowej aplikacji Leanback
. Pamiętaj, że przykładowa aplikacja uruchamia własny fragment LeanbackDetailsFragment, aby wyświetlić szczegóły wybranych multimediów. W swoich aplikacjach uruchamiaj aktywność, która odtwarza multimedia, aby użytkownik nie musiał klikać dodatkowo.
Zachowania związane z wyszukiwaniem
Wyszukiwanie jest dostępne na Androidzie TV na ekranie głównym i w Twojej aplikacji. Wyniki wyszukiwania są różne w tych 2 przypadkach.
Wyszukiwanie z ekranu głównego
Gdy użytkownik wyszukuje z ekranu głównego, pierwszy wynik pojawia się na karcie encji. Jeśli są aplikacje, które mogą odtwarzać treści, u dołu karty pojawi się link do każdej z nich:
Nie możesz programowo umieścić aplikacji na karcie encji. Aby aplikacja była uwzględniana jako opcja odtwarzania, wyniki wyszukiwania muszą pasować do tytułu, roku i czasu trwania wyszukiwanych treści.
Pod kartą mogą być dostępne dodatkowe wyniki wyszukiwania. Aby je zobaczyć, użytkownik musi nacisnąć przycisk w dół na pilocie i przewinąć. Wyniki dla każdej aplikacji pojawiają się w osobnym wierszu. Nie możesz kontrolować kolejności wierszy. Najpierw wyświetlają się aplikacje, które obsługują działania oglądania.
Wyszukiwanie z aplikacji
Użytkownik może też rozpocząć wyszukiwanie z poziomu aplikacji, uruchamiając mikrofon za pomocą pilota lub gamepada. Wyniki wyszukiwania są wyświetlane w jednym wierszu u góry treści aplikacji. Twoja aplikacja generuje wyniki wyszukiwania za pomocą własnego dostawcy wyszukiwania globalnego.
Więcej informacji
Więcej informacji o wyszukiwaniu w aplikacji na telewizor znajdziesz w artykułach Integrowanie funkcji wyszukiwania Androida z aplikacją i Dodawanie funkcji wyszukiwania.
Więcej informacji o dostosowywaniu wyszukiwania w aplikacji za pomocą SearchFragment znajdziesz w artykule
Wyszukiwanie w aplikacjach na telewizor.