הגדרת אפליקציות טלוויזיה שזמינות לחיפוש

ב-Android TV נעשה שימוש בממשק החיפוש של Android כדי לאחזר נתוני תוכן מאפליקציות מותקנות ולהציג תוצאות חיפוש למשתמש. של האפליקציה שלך לכלול נתוני תוכן בתוצאות האלו כדי לתת למשתמש גישה מיידית לתוכן באפליקציה שלך.

האפליקציה צריכה לספק ל-Android TV את שדות הנתונים שמהם מערכת Android TV יכולה ליצור הצעות לחיפוש תוצאות כשהמשתמש מזין תווים בתיבת הדו-שיח של החיפוש. כדי לעשות את זה, האפליקציה צריכה להטמיע ספק תוכן שמציג מודעות להעלות את ההצעות, וגם קובץ תצורה של searchable.xml שמתאר את התוכן ספק מידע חיוני אחר עבור Android TV. נדרשת גם פעילות שמטפלת Intent שמופעל כשהמשתמש בוחר בתוצאת חיפוש מוצעת. עבור פרטים נוספים, ראו הוספה הצעות לחיפוש בהתאמה אישית. המדריך הזה עוסק בנקודות העיקריות שספציפיות לאפליקציות ל-Android TV.

לפני קריאת המדריך הזה, ודאו שאתם מכירים את המושגים שמוסברים מדריך Search 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, המערכת מספקת קישור עומק לאפליקציה בפרטים לצפייה בתוכן, יחד עם קישורים לאפליקציות של ספקים אחרים. הנושא הזה דן בנושא בקטע קישור עומק לאפליקציה במסך הפרטים.

סוג מסד הנתונים של האפליקציה עשוי להגדיר את העמודות כך:

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;
...

כשיוצרים את המפה מהעמודות SearchManager אל שדות הנתונים, חייב לציין גם את _ID כדי לתת לכל שורה מזהה ייחודי.

Kotlin

companion object {
    ....
    private fun buildColumnMap(): MapS<tring, 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;
  }
...

בדוגמה הקודמת, שימו לב למיפוי אל 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 שמצביע אל השורות שהקצית להצעות.

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);
}
...

בקובץ המניפסט, ספק התוכן מקבל טיפול מיוחד. במקום מתויגת כפעילות, היא מתוארת <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" כדי להגדיר את פעולת 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>

לטפל במונחי חיפוש

ברגע שבתיבת הדו-שיח של החיפוש יש מילה שתואמת לערך באחת מהעמודות של האפליקציה, כמו שמתואר בקטע זיהוי עמודות, המערכת מפעילה את Intent מסוג ACTION_SEARCH. הפעילות באפליקציה שמטפלת בגורמים האלה Intent מחפש במאגר עמודות שהמילה הנתנה מופיעה בהן בערכים שלהם, ומחזיר רשימה של פריטי תוכן בעמודות האלה. בקובץ AndroidManifest.xml, מציינים את פעילות שמטפלת ב-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" />
...

הפעילות חייבת לתאר גם את ההגדרה שניתנת לחיפוש, כולל אזכור של 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, אפשר לקרוא את המאמר לחיפוש באפליקציות לטלוויזיה.