Android TV sử dụng giao diện tìm kiếm của Android để truy xuất dữ liệu nội dung từ các ứng dụng đã cài đặt và cung cấp kết quả tìm kiếm cho người dùng. Bạn có thể đưa dữ liệu nội dung của ứng dụng vào các kết quả này để cho phép người dùng truy cập tức thì vào nội dung trong ứng dụng.
Ứng dụng của bạn phải cung cấp cho Android TV các trường dữ liệu mà từ đó Android TV có thể tạo kết quả tìm kiếm đề xuất khi người dùng nhập các ký tự vào hộp thoại tìm kiếm. Để làm được điều đó, ứng dụng của bạn phải triển khai một Trình cung cấp nội dung nhằm đưa ra các đề xuất cùng với tệp cấu hình
searchable.xml
mô tả trình cung cấp nội dung và các thông tin quan trọng khác cho Android TV. Bạn cũng cần một hoạt động xử lý ý định sẽ kích hoạt khi người dùng chọn kết quả tìm kiếm đề xuất. Để biết thêm thông tin chi tiết, vui lòng xem phần Thêm cụm từ tìm kiếm tuỳ chỉnh được đề xuất. Hướng dẫn này trình bày các điểm chính dành riêng cho ứng dụng Android TV.
Trước khi đọc hướng dẫn này, hãy đảm bảo bạn đã nắm rõ các khái niệm được giải thích trong hướng dẫn về API Tìm kiếm. Ngoài ra, hãy tham khảo bài viết Thêm chức năng tìm kiếm.
Mã mẫu trong hướng dẫn này được lấy từ ứng dụng mẫu Leanback.
Xác định cột
SearchManager
mô tả các trường dữ liệu mà nó dự kiến bằng cách biểu thị các trường đó dưới dạng cột của cơ sở dữ liệu cục bộ. Bất kể định dạng dữ liệu là gì, bạn phải liên kết các trường dữ liệu với các cột này, thường là trong lớp truy cập vào dữ liệu nội dung của bạn. Để biết thông tin về cách tạo một lớp ánh xạ dữ liệu hiện có của bạn tới các trường bắt buộc, hãy xem phần
Xây dựng bảng đề xuất.
Lớp SearchManager
bao gồm một số cột cho Android TV. Một số cột quan trọng hơn được mô tả trong bảng sau.
Giá trị | Nội dung mô tả |
---|---|
SUGGEST_COLUMN_TEXT_1 |
Tên nội dung của bạn (bắt buộc) |
SUGGEST_COLUMN_TEXT_2 |
Phần mô tả bằng văn bản về nội dung của bạn |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
Hình ảnh, áp phích hoặc bìa cho nội dung của bạn |
SUGGEST_COLUMN_CONTENT_TYPE |
Loại MIME của nội dung đa phương tiện |
SUGGEST_COLUMN_VIDEO_WIDTH |
Chiều rộng độ phân giải của nội dung nghe nhìn |
SUGGEST_COLUMN_VIDEO_HEIGHT |
Chiều cao độ phân giải của nội dung nghe nhìn |
SUGGEST_COLUMN_PRODUCTION_YEAR |
Năm sản xuất nội dung của bạn (bắt buộc) |
SUGGEST_COLUMN_DURATION |
Thời lượng tính bằng mili giây của nội dung nghe nhìn (bắt buộc) |
Khung tìm kiếm cần có các cột sau:
Khi giá trị của những cột này cho nội dung của bạn khớp với giá trị của cùng một nội dung từ những nhà cung cấp khác mà máy chủ của Google tìm thấy, hệ thống sẽ cung cấp một đường liên kết sâu đến ứng dụng của bạn trong chế độ xem chi tiết cho nội dung, cùng với các đường liên kết đến ứng dụng của các nhà cung cấp khác. Vấn đề này sẽ được thảo luận thêm trong phần Đường liên kết sâu đến ứng dụng trong màn hình chi tiết.
Lớp cơ sở dữ liệu của ứng dụng có thể xác định các cột như sau:
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; ...
Khi tạo bản đồ từ cột SearchManager
đến các trường dữ liệu, bạn cũng phải chỉ định _ID
để cung cấp cho mỗi hàng một mã nhận dạng duy nhất.
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; } ...
Trong ví dụ trước, hãy chú ý đến mối liên kết với trường SUGGEST_COLUMN_INTENT_DATA_ID
. Đây là phần URI trỏ đến nội dung dành riêng cho dữ liệu trong hàng này – phần cuối cùng của URI, mô tả nơi lưu trữ nội dung. Phần đầu tiên của URI (thường là đối với tất cả các hàng trong bảng) sẽ được đặt trong tệp searchable.xml
làm thuộc tính
android:searchSuggestIntentData
, như mô tả trong phần Xử lý các cụm từ tìm kiếm được đề xuất.
Nếu phần đầu tiên của URI là khác nhau đối với mỗi hàng trong bảng, hãy ánh xạ giá trị đó với trường SUGGEST_COLUMN_INTENT_DATA
.
Khi người dùng chọn nội dung này, ý định kích hoạt sẽ cung cấp dữ liệu ý định từ tổ hợp SUGGEST_COLUMN_INTENT_DATA_ID
và thuộc tính android:searchSuggestIntentData
hoặc giá trị trường SUGGEST_COLUMN_INTENT_DATA
.
Cung cấp dữ liệu cụm từ tìm kiếm được đề xuất
Triển khai Content Provider để trả về các đề xuất cụm từ tìm kiếm vào hộp thoại tìm kiếm của Android TV. Hệ thống sẽ truy vấn trình cung cấp nội dung của bạn để nhận các đề xuất bằng cách gọi phương thức query()
mỗi khi nhập một chữ cái. Trong quá trình triển khai query()
, trình cung cấp nội dung của bạn sẽ tìm kiếm dữ liệu đề xuất và trả về Cursor
trỏ đến các hàng bạn đã chỉ định cho các đề xuất.
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); } ...
Trong tệp kê khai của bạn, nhà cung cấp nội dung được xử lý đặc biệt. Thay vì được gắn thẻ là một hoạt động, nó được mô tả là <provider>
. Trình cung cấp này bao gồm thuộc tính android:authorities
để cho hệ thống biết không gian tên của trình cung cấp nội dung. Ngoài ra, bạn phải đặt thuộc tính android:exported
thành "true"
để tính năng tìm kiếm toàn cầu trên Android có thể sử dụng các kết quả được trả về.
<provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" />
Xử lý cụm từ tìm kiếm được đề xuất
Ứng dụng của bạn phải bao gồm một tệp
res/xml/searchable.xml
để định cấu hình chế độ cài đặt cụm từ tìm kiếm được đề xuất.
Trong tệp res/xml/searchable.xml
, hãy thêm thuộc tính
android:searchSuggestAuthority
để cho hệ thống biết không gian tên của trình cung cấp nội dung. Giá trị này phải khớp với giá trị chuỗi mà bạn chỉ định trong thuộc tính android:authorities
của phần tử <provider>
trong tệp AndroidManifest.xml
.
Ngoài ra, hãy bao gồm một nhãn, là tên của ứng dụng. Chế độ cài đặt tìm kiếm trên hệ thống sử dụng nhãn này khi liệt kê các ứng dụng có thể tìm kiếm.
Tệp searchable.xml
cũng phải bao gồm
android:searchSuggestIntentAction
với giá trị "android.intent.action.VIEW"
để xác định thao tác theo ý định nhằm cung cấp một đề xuất tuỳ chỉnh. Hành động này khác với thao tác theo ý định để cung cấp cụm từ tìm kiếm, như mô tả trong phần sau.
Để biết các cách khác nhằm khai báo thao tác theo ý định cho các đề xuất, hãy xem phần Khai báo thao tác theo ý định.
Cùng với thao tác theo ý định, ứng dụng của bạn phải cung cấp dữ liệu ý định mà bạn chỉ định bằng thuộc tính
android:searchSuggestIntentData
. Đây là phần đầu tiên của URI trỏ đến nội dung, mô tả phần URI chung cho tất cả các hàng trong bảng liên kết cho nội dung đó. Phần URI dành riêng cho mỗi hàng được thiết lập bằng trường SUGGEST_COLUMN_INTENT_DATA_ID
, như mô tả trong phần Xác định cột.
Để biết các cách khác nhằm khai báo dữ liệu ý định cho các đề xuất, hãy xem phần Khai báo dữ liệu ý định.
Thuộc tính android:searchSuggestSelection=" ?"
chỉ định giá trị được chuyển dưới dạng tham số selection
của phương thức query()
. Giá trị dấu chấm hỏi (?
) được thay thế bằng văn bản truy vấn.
Cuối cùng, bạn cũng phải thêm thuộc tính
android:includeInGlobalSearch
với giá trị "true"
. Dưới đây là tệp searchable.xml
mẫu:
<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>
Xử lý cụm từ tìm kiếm
Ngay khi hộp thoại tìm kiếm có từ khớp với giá trị trong một trong các cột của ứng dụng, như mô tả trong phần Xác định cột, hệ thống sẽ kích hoạt ý định ACTION_SEARCH
.
Hoạt động trong ứng dụng xử lý ý định đó sẽ tìm kiếm các cột có từ đã cho trong giá trị trong kho lưu trữ và trả về danh sách các mục nội dung có các cột đó. Trong tệp AndroidManifest.xml
, bạn chỉ định hoạt động xử lý ý định ACTION_SEARCH
như trong ví dụ sau:
... <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" /> ...
Hoạt động cũng phải mô tả cấu hình có thể tìm kiếm bằng cách tham chiếu đến tệp searchable.xml
.
Để sử dụng hộp thoại tìm kiếm chung, tệp kê khai phải mô tả hoạt động nào sẽ nhận được cụm từ tìm kiếm. Tệp kê khai cũng phải mô tả phần tử <provider>
, đúng như mô tả trong tệp searchable.xml
.
Đường liên kết sâu đến ứng dụng của bạn trong màn hình chi tiết
Nếu bạn đã thiết lập cấu hình tìm kiếm như mô tả trong phần Xử lý các cụm từ tìm kiếm được đề xuất và liên kết các trường SUGGEST_COLUMN_TEXT_1
, SUGGEST_COLUMN_PRODUCTION_YEAR
và SUGGEST_COLUMN_DURATION
như mô tả trong phần Xác định cột, thì
đường liên kết sâu đến một thao tác xem dành cho nội dung của bạn sẽ xuất hiện trên màn hình chi tiết sẽ chạy khi người dùng chọn một kết quả tìm kiếm:
Khi người dùng chọn đường liên kết cho ứng dụng của bạn, được xác định bằng nút **Có sẵn** trên màn hình chi tiết, hệ thống sẽ khởi chạy hoạt động xử lý ACTION_VIEW
được đặt là
android:searchSuggestIntentAction
với giá trị "android.intent.action.VIEW"
trong
tệp searchable.xml
.
Bạn cũng có thể thiết lập một đối tượng có ý định tuỳ chỉnh để chạy hoạt động của mình. Điều này được minh hoạ trong
ứng dụng mẫu Leanback. Xin lưu ý rằng ứng dụng mẫu sẽ chạy LeanbackDetailsFragment
riêng để hiển thị thông tin chi tiết về nội dung nghe nhìn đã chọn; trong các ứng dụng của bạn, hãy khởi chạy ngay hoạt động phát nội dung nghe nhìn để lưu người dùng thêm một hoặc hai lần nhấp.
Hành vi tìm kiếm
Tính năng tìm kiếm sẽ xuất hiện trên Android TV từ màn hình chính và từ bên trong ứng dụng. Kết quả tìm kiếm trong 2 trường hợp này là khác nhau.
Tìm kiếm từ màn hình chính
Khi người dùng tìm kiếm từ màn hình chính, kết quả đầu tiên sẽ xuất hiện trong thẻ thực thể. Nếu có ứng dụng có thể phát nội dung đó, thì một đường liên kết đến từng ứng dụng sẽ xuất hiện ở cuối thẻ:
Bạn không thể lập trình để đặt ứng dụng vào thẻ thực thể. Để được đưa vào dưới dạng tuỳ chọn phát, kết quả tìm kiếm của ứng dụng phải khớp với tiêu đề, năm và thời lượng của nội dung được tìm kiếm.
Bạn có thể xem thêm kết quả tìm kiếm bên dưới thẻ này. Để xem các biểu tượng này, người dùng phải nhấn và giữ điều khiển từ xa rồi cuộn xuống. Kết quả cho mỗi ứng dụng sẽ xuất hiện trong một hàng riêng. Bạn không thể kiểm soát thứ tự hàng. Những ứng dụng hỗ trợ hành động trên đồng hồ được liệt kê trước.
Tìm kiếm trong ứng dụng của bạn
Người dùng cũng có thể bắt đầu tìm kiếm từ trong ứng dụng của bạn bằng cách khởi động micrô từ điều khiển từ xa hoặc tay điều khiển trò chơi. Kết quả tìm kiếm được hiển thị trong một hàng ở đầu nội dung của ứng dụng. Ứng dụng của bạn tạo kết quả tìm kiếm bằng cách sử dụng nhà cung cấp dịch vụ tìm kiếm toàn cầu riêng của ứng dụng.
Tìm hiểu thêm
Để tìm hiểu thêm về cách tìm kiếm trong ứng dụng dành cho TV, hãy đọc bài viết Tích hợp tính năng tìm kiếm của Android vào ứng dụng và Thêm chức năng tìm kiếm.
Để biết thêm thông tin về cách tuỳ chỉnh trải nghiệm tìm kiếm trong ứng dụng bằng SearchFragment
, hãy đọc bài viết Tìm kiếm trong ứng dụng TV.