اندروید تیوی از رابط جستجوی اندروید برای بازیابی دادههای محتوا از برنامههای نصبشده و ارائه نتایج جستجو به کاربر استفاده میکند. دادههای محتوای برنامه شما میتواند در این نتایج گنجانده شود تا کاربر بتواند فوراً به محتوای برنامه شما دسترسی پیدا کند.
برنامه شما باید فیلدهای دادهای را در اختیار Android TV قرار دهد که Android TV بتواند از طریق آنها نتایج جستجوی پیشنهادی را هنگام وارد کردن کاراکترها توسط کاربر در کادر جستجو، تولید کند. برای انجام این کار، برنامه شما باید یک ارائهدهنده محتوا (Content Provider ) پیادهسازی کند که پیشنهادات را به همراه یک فایل پیکربندی searchable.xml که ارائهدهنده محتوا و سایر اطلاعات حیاتی برای Android TV را شرح میدهد، ارائه دهد. همچنین به یک فعالیت (activity) نیاز دارید که intent ای را که هنگام انتخاب یک نتیجه جستجوی پیشنهادی توسط کاربر فعال میشود، مدیریت کند. برای جزئیات بیشتر، به افزودن پیشنهادات جستجوی سفارشی مراجعه کنید. این راهنما نکات اصلی مربوط به برنامههای Android TV را پوشش میدهد.
قبل از خواندن این راهنما، مطمئن شوید که با مفاهیم توضیح داده شده در راهنمای Search API آشنا هستید. همچنین، Add search functionality را مرور کنید.
کد نمونه موجود در این راهنما از برنامه نمونه 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 | مدت زمان رسانه شما بر حسب میلیثانیه (الزامی) |
چارچوب جستجو به ستونهای زیر نیاز دارد:
وقتی مقادیر این ستونها برای محتوای شما با مقادیر همان محتوا از سایر ارائهدهندگانی که توسط سرورهای گوگل پیدا شدهاند، مطابقت داشته باشد، سیستم یک لینک عمیق به برنامه شما در نمای جزئیات محتوا، همراه با لینکهایی به برنامههای سایر ارائهدهندگان، ارائه میدهد. این موضوع در بخش «لینک عمیق به برنامه شما در صفحه جزئیات» بیشتر مورد بحث قرار گرفته است.
کلاس پایگاه داده برنامه شما ممکن است ستونها را به صورت زیر تعریف کند:
کاتلین
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 تنظیم میشود، همانطور که در بخش پیشنهادات جستجوی Handle توضیح داده شده است.
اگر بخش اول URI برای هر سطر در جدول متفاوت است، آن مقدار را با فیلد SUGGEST_COLUMN_INTENT_DATA نگاشت کنید. هنگامی که کاربر این محتوا را انتخاب میکند، intent که اجرا میشود، دادههای intent را از ترکیب SUGGEST_COLUMN_INTENT_DATA_ID و یا ویژگی android:searchSuggestIntentData یا مقدار فیلد SUGGEST_COLUMN_INTENT_DATA فراهم میکند.
ارائه دادههای پیشنهادی جستجو
یک ارائهدهنده محتوا (Content Provider) پیادهسازی کنید تا پیشنهادهای عبارت جستجو را به کادر جستجوی تلویزیون اندروید (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); } ...
در فایل مانیفست شما، با ارائهدهنده محتوا (content provider) برخورد ویژهای میشود. به جای اینکه به عنوان یک فعالیت برچسبگذاری شود، به عنوان یک <provider> توصیف میشود. ارائهدهنده شامل ویژگی android:authorities تا فضای نام ارائهدهنده محتوای شما را به سیستم اعلام کند. همچنین، باید ویژگی android:exported آن را روی "true" تنظیم کنید تا جستجوی سراسری اندروید بتواند از نتایج برگردانده شده از آن استفاده کند.
<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" باشد تا اکشن intent برای ارائه یک پیشنهاد سفارشی تعریف شود. این با اکشن intent برای ارائه یک عبارت جستجو متفاوت است، همانطور که در بخش بعدی توضیح داده شده است. برای روشهای دیگر اعلام اکشن intent برای پیشنهادات، به اعلام اکشن intent مراجعه کنید.
همراه با اکشن intent، برنامه شما باید دادههای intent را ارائه دهد، که شما با ویژگی android:searchSuggestIntentData مشخص میکنید. این اولین بخش از URI است که به محتوا اشاره میکند، که بخشی از URI مشترک برای همه ردیفها در جدول نگاشت برای آن محتوا را توصیف میکند. بخشی از URI که برای هر ردیف منحصر به فرد است، با فیلد SUGGEST_COLUMN_INTENT_DATA_ID ایجاد میشود، همانطور که در بخش شناسایی ستونها توضیح داده شده است. برای روشهای دیگر برای اعلام دادههای intent برای پیشنهادات، به اعلام دادههای intent مراجعه کنید.
ویژگی 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 را همانطور که در بخش «شناسایی ستونها» توضیح داده شده است، نگاشت کرده باشید، یک پیوند عمیق به یک اقدام نظارت برای محتوای شما در صفحه جزئیات که هنگام انتخاب نتیجه جستجو توسط کاربر اجرا میشود، ظاهر میشود:

وقتی کاربر لینک مربوط به برنامه شما را که با دکمه **Available On** در صفحه جزئیات مشخص شده است، انتخاب میکند، سیستم اکتیویتیای را اجرا میکند که ACTION_VIEW تنظیم شده به صورت android:searchSuggestIntentAction با مقدار "android.intent.action.VIEW" در فایل searchable.xml مدیریت میکند.
همچنین میتوانید یک اینتنت سفارشی برای اجرای اکتیویتی خود تنظیم کنید. این مورد در برنامه نمونه Leanback نشان داده شده است. توجه داشته باشید که برنامه نمونه، LeanbackDetailsFragment مخصوص به خود را برای نمایش جزئیات رسانه انتخاب شده اجرا میکند؛ در برنامههای خود، اکتیویتیای را که رسانه را پخش میکند، بلافاصله اجرا کنید تا کاربر نیازی به یک یا دو کلیک دیگر نداشته باشد.
رفتار جستجو
جستجو در اندروید تیوی از صفحه اصلی و از داخل برنامه شما در دسترس است. نتایج جستجو برای این دو مورد متفاوت است.
جستجو از صفحه اصلی
وقتی کاربر از صفحه اصلی جستجو میکند، اولین نتیجه در یک کارت موجودیت ظاهر میشود. اگر برنامههایی وجود داشته باشند که بتوانند محتوا را پخش کنند، پیوندی به هر یک در پایین کارت ظاهر میشود:

شما نمیتوانید از طریق برنامهنویسی، یک برنامه را در کارت موجودیت قرار دهید. برای اینکه به عنوان گزینه پخش گنجانده شود، نتایج جستجوی یک برنامه باید با عنوان، سال و مدت زمان محتوای جستجو شده مطابقت داشته باشد.
ممکن است نتایج جستجوی بیشتری در زیر کارت موجود باشد. برای دیدن آنها، کاربر باید کنترل از راه دور را فشار داده و اسکرول کند. نتایج هر برنامه در یک ردیف جداگانه ظاهر میشود. شما نمیتوانید ترتیب ردیفها را کنترل کنید. برنامههایی که از عملکردهای ساعت پشتیبانی میکنند، در ابتدا فهرست شدهاند.

جستجو از برنامه شما
کاربر همچنین میتواند با فعال کردن میکروفون از طریق ریموت یا دسته بازی، جستجو را از داخل برنامه شما شروع کند. نتایج جستجو در یک ردیف در بالای محتوای برنامه نمایش داده میشوند. برنامه شما نتایج جستجو را با استفاده از ارائه دهنده جستجوی جهانی خود تولید میکند.

بیشتر بدانید
برای کسب اطلاعات بیشتر در مورد جستجوی یک برنامه تلویزیونی، ادغام ویژگیهای جستجوی اندروید در برنامه خود و افزودن قابلیت جستجو را مطالعه کنید.
برای اطلاعات بیشتر در مورد نحوه سفارشیسازی تجربه جستجوی درون برنامهای با SearchFragment ، بخش «جستجو در برنامههای تلویزیونی» را مطالعه کنید.