使TV應用是可被搜索的

編寫:awong1900 - 原文:http://developer.android.com/training/tv/discovery/searchable.html

Android TV使用Android搜索接口從安裝的應用中檢索內容數據並且釋放搜索結果給用戶。我們的應用內容數據能被包含在這些結果中,去給用戶即時訪問應用程序中的內容。

我們的應用必須提供Android TV數據字段,它是用戶在搜索框中輸入字符生成的建議搜索結果。去做這個,我們的應用必須實現Content Provider,在searchable.xml配置文件描述content provider和其他必要的Android TV信息。我們也需要一個activity在用戶選擇一個建議的搜索結果時處理intent的觸發。所有的這些被描述在Adding Custom Suggestions。本文描述Android TV應用搜索的關鍵點。

這節課展示Android中搜索的知識,展示如何使我們的應用在Android TV裡是可被搜索的。確信我們熟悉Search API guide的解釋。在下面的這節課程之前,查看Adding Search Functionality訓練課程。

這個討論描述的一些代碼,從Android Leanback示例代碼摘出。代碼可以在Github上找到。

識別列

SearchManager描述了數據字段,它被代表為SQLite數據庫的列。不管我們的數據格式,我們必須把我們的數據字段填到那些列,通常用存取我們的內容數據的類。更多信息,查看Building a suggestion table()

SearchManager類為AndroidTV包含了幾個列。下面是重要的一些列:

描述
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 媒體的時間長度

搜索framework需要以下的列:

當這些內容的列的值匹配Google服務的providers提供的的值時,系統提供一個深鏈接到我們的應用,用於詳情查看,以及指向應用的其他Providers的鏈接。更多討論在在詳情頁顯示內容

我們的應用的數據庫類可能定義以下的列:

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去獲得每行的獨一無二的ID。

...
  private static HashMap buildColumnMap() {
    HashMap map = new HashMap();
    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 Search Suggestions

如果URI的第一部分是不同於表格的每一列,我們填充SUGGEST_COLUMN_INTENT_DATA字段的值。當用戶選擇這個內容時,這個intent被啟動依據SUGGEST_COLUMN_INTENT_DATA_ID的混合intent數據或者android:searchSuggestIntentData屬性和SUGGEST_COLUMN_INTENT_DATA字段值之一。

提供搜索建議數據

實現一個Content Provider去返回搜索術語建議到AndroidTV搜索框。系統需要我們的內容容器提供建議,通過調用每次一個字母類型[query()](http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String))方法。在[query()](http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String))的實現中,我們的內容容器搜索我們的建議數據並且返回一個光標指向我們已經指定的建議列。

@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 mVideoDatabase.getWordMatch(query, columns);
  }
...

在我們的manifest文件中,內容容器接受特殊處理。相比被標記為一個activity,它是被描述為<provider>。provider包括android:searchSuggestAuthority屬性去告訴系統我們的內容容器的名字空間。並且,我們必須設置它的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文件去配置搜索建議設置。它包括android:searchSuggestAuthority屬性去告訴系統內容容器的名字空間。這必須匹配在AndroidManifest.xml文件的<provider>元素的android:authorities 屬性的字符串值。

searchable.xml文件必須也包含在"android.intent.action.VIEW"android:searchSuggestIntentAction值去定義提供自定義建議的intent action。這與提供一個搜索術語的intent action不同,下面解釋。查看Declaring the intent action 用另一種方式去定義建議的intent action。

同intent action一起,我們的應用必須提供我們定義的android:searchSuggestIntentData屬性的intent數據。這是指向內容的URI的第一部分。它描述在填充的內容表格中URI所有共同列的部分。URI的獨一無二的部分用 SUGGEST_COLUMN_INTENT_DATA_ID字段建立每一列,以上被描述在識別列。查看Declaring the intent data用另一種方式去定義建議的intent數據。

並且,注意android:searchSuggestSelection="?"屬性為特定的值。這個值作為[query()](http://developer.android.com/reference/android/content/ContentProvider.html#query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String))方法selection參數。方法的問題標記(?)值被代替為請求文本。

最後,我們也必須包含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 intent。我們應用的activity處理intent搜索列的給定的字段資源,並且返回一個那些內容項的列表。在我們的AndroidManifest.xml文件中,我們指定的activity處理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" />
...

activity必須參考searchable.xml文件描述可搜索的設置。用全局搜索框,manifest必須描述activity應該收到的搜索請求。manifest必須描述<provider>元素,詳細被描述在searchable.xml文件。

深鏈接到應用的詳情頁

如果我們有設置處理搜索建議描述的搜索配置和填充 SUGGEST_COLUMN_TEXT_1SUGGEST_COLUMN_CONTENT_TYPESUGGEST_COLUMN_PRODUCTION_YEAR字段到識別列,一個深鏈接去查看詳情頁的內容。當用戶選擇一個搜索結果時,詳情頁將打開。如圖1。

deep-link

圖1 詳情頁顯示一個深鏈接為Google(Leanback)的視頻代碼。Sintel: © copyright Blender Foundation, www.sintel.org.

當用戶選擇我們的應用鏈接,“Available On”按鈕被標識在詳情頁,系統啟動activity處理ACTION_VIEW(在searchable.xml文件設置android:searchSuggestIntentAction值為"android.intent.action.VIEW")。

我們也能設置用戶intent去啟動我們的activity,這個在在AndroidLeanback示例代碼應用中演示。注意示例應用啟動它自己的LeanbackDetailsFragment去顯示被選擇媒體的詳情,但是我們應該啟動activity去播放媒體。立即去保存用戶的另一次或兩次點擊。


下一節: 使TV應用是可被搜索的 >


书籍推荐