與電視互動時,使用者通常希望在觀看內容之前,盡量不要輸入相關資料。對許多電視使用者來說,理想的情境為:坐下、開機、觀看。讓使用者輕鬆存取自己喜歡的內容,最簡單的步驟通常是他們偏好的途徑。
注意:只有在 Android 7.1 (API 級別 25) 以下版本中執行的應用程式,才能使用本文所述的 API 提供建議。如要為在 Android 8.0 (API 級別 26) 以上版本中執行的應用程式提供建議,應用程式必須使用推薦管道。
Android 架構會在主畫面提供建議列,協助最低輸入互動。首次使用裝置後,內容推薦會顯示在電視主畫面的第一列。從應用程式的內容目錄中提供推薦內容,有助於吸引使用者再次使用應用程式。
![](https://developer.android.google.cn/static/images/tv/home-recommendations.png?authuser=3&hl=zh-tw)
圖 1 建議列範例。
本指南將說明如何建立推薦內容,並提供 Android 架構,讓使用者可以輕鬆發掘及享受您的應用程式內容。另請參閱 Leanback 範例應用程式的實作範例。
推薦功能的最佳做法
建議可協助使用者快速找到喜愛的內容和應用程式。如要打造最佳的 TV 應用程式使用者體驗,建立高品質的相關推薦內容,就是非常重要的因素。因此,您應審慎考量向使用者提供的推薦內容,並謹慎管理。
「建議」類型
建立推薦項目時,您應將使用者重新連結至未完成的觀看活動,或建議可延伸至相關內容的活動。以下是一些您應考慮的具體建議:
- 接續內容建議,協助使用者繼續觀看下一集。或者,針對已暫停的電影、電視節目或 Podcast 採用繼續建議,讓使用者只要按幾下就能繼續觀看已暫停的內容。
- 「新內容」建議,例如使用者看完其他系列叢書時,新的首播一集節目。此外,如果您的應用程式可讓使用者訂閱、追蹤或追蹤內容,請針對他們追蹤內容清單中的未觀看項目採用新內容推薦功能。
- 根據使用者過往的觀看行為推薦的相關內容。
如要進一步瞭解如何設計推薦資訊卡以提供最佳使用者體驗,請參閱 Android TV 設計規格中的推薦列。
重新整理推薦影片
重新整理建議時,請勿只移除並重新發布建議,因為這會導致建議列末端顯示建議。播放完畢的內容項目 (例如電影) 後,請從推薦中 移除該部影片。
自訂推薦內容
您可以自訂推薦資訊卡來傳達品牌資訊,方法是設定使用者介面元素,例如資訊卡的前景和背景圖片、顏色、應用程式圖示、標題和副標題。詳情請參閱 Android TV 設計規格中的推薦列。
群組建議
您可以視需要根據最佳化建議來源將最佳化建議分組。舉例來說,您的應用程式可能會提供兩種推薦內容群組:針對使用者訂閱的內容進行推薦,以及推薦使用者可能不知道的新熱門內容。
建立或更新推薦資料列時,系統會分別為每個群組排序建議及排序建議。提供群組資訊來取得推薦內容,可確保系統不會將建議排序在不相關的建議下方。
使用 NotificationCompat.Builder.setGroup()
即可設定建議的群組金鑰字串。舉例來說,如要將推薦內容標示為屬於包含新發燒內容的群組,您可以呼叫 setGroup("trending")
。
建立推薦服務
內容推薦是以背景處理功能建立而成。為了讓應用程式能納入推薦內容,請建立服務,定期將應用程式目錄中的清單新增至系統的建議清單。
以下程式碼範例說明如何擴充 IntentService
,為應用程式建立推薦服務:
Kotlin
class UpdateRecommendationsService : IntentService("RecommendationService") { override protected fun onHandleIntent(intent: Intent) { Log.d(TAG, "Updating recommendation cards") val recommendations = VideoProvider.getMovieList() if (recommendations == null) return var count = 0 try { val builder = RecommendationBuilder() .setContext(applicationContext) .setSmallIcon(R.drawable.videos_by_google_icon) for (entry in recommendations.entrySet()) { for (movie in entry.getValue()) { Log.d(TAG, "Recommendation - " + movie.getTitle()) builder.setBackground(movie.getCardImageUrl()) .setId(count + 1) .setPriority(MAX_RECOMMENDATIONS - count) .setTitle(movie.getTitle()) .setDescription(getString(R.string.popular_header)) .setImage(movie.getCardImageUrl()) .setIntent(buildPendingIntent(movie)) .build() if (++count >= MAX_RECOMMENDATIONS) { break } } if (++count >= MAX_RECOMMENDATIONS) { break } } } catch (e: IOException) { Log.e(TAG, "Unable to update recommendation", e) } } private fun buildPendingIntent(movie: Movie): PendingIntent { val detailsIntent = Intent(this, DetailsActivity::class.java) detailsIntent.putExtra("Movie", movie) val stackBuilder = TaskStackBuilder.create(this) stackBuilder.addParentStack(DetailsActivity::class.java) stackBuilder.addNextIntent(detailsIntent) // Ensure a unique PendingIntents, otherwise all // recommendations end up with the same PendingIntent detailsIntent.setAction(movie.getId().toString()) val intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) return intent } companion object { private val TAG = "UpdateRecommendationsService" private val MAX_RECOMMENDATIONS = 3 } }
Java
public class UpdateRecommendationsService extends IntentService { private static final String TAG = "UpdateRecommendationsService"; private static final int MAX_RECOMMENDATIONS = 3; public UpdateRecommendationsService() { super("RecommendationService"); } @Override protected void onHandleIntent(Intent intent) { Log.d(TAG, "Updating recommendation cards"); HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList(); if (recommendations == null) return; int count = 0; try { RecommendationBuilder builder = new RecommendationBuilder() .setContext(getApplicationContext()) .setSmallIcon(R.drawable.videos_by_google_icon); for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) { for (Movie movie : entry.getValue()) { Log.d(TAG, "Recommendation - " + movie.getTitle()); builder.setBackground(movie.getCardImageUrl()) .setId(count + 1) .setPriority(MAX_RECOMMENDATIONS - count) .setTitle(movie.getTitle()) .setDescription(getString(R.string.popular_header)) .setImage(movie.getCardImageUrl()) .setIntent(buildPendingIntent(movie)) .build(); if (++count >= MAX_RECOMMENDATIONS) { break; } } if (++count >= MAX_RECOMMENDATIONS) { break; } } } catch (IOException e) { Log.e(TAG, "Unable to update recommendation", e); } } private PendingIntent buildPendingIntent(Movie movie) { Intent detailsIntent = new Intent(this, DetailsActivity.class); detailsIntent.putExtra("Movie", movie); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(DetailsActivity.class); stackBuilder.addNextIntent(detailsIntent); // Ensure a unique PendingIntents, otherwise all // recommendations end up with the same PendingIntent detailsIntent.setAction(Long.toString(movie.getId())); PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); return intent; } }
為了讓系統辨識並執行這項服務,請使用您的應用程式資訊清單註冊。下列程式碼片段說明如何宣告此類別為服務:
<manifest ... > <application ... > ... <service android:name="com.example.android.tvleanback.UpdateRecommendationsService" android:enabled="true" /> </application> </manifest>
建構作業建議
推薦服務開始執行後,必須建立建議並傳送至 Android 架構。架構會收到使用特定範本且標有特定類別的 Notification
物件建議。
設定值
如要設定推薦資訊卡的 UI 元素值,您必須建立遵循建構工具模式的建構工具類別,如下所示。首先,您需要設定推薦資訊卡元素的值。
Kotlin
class RecommendationBuilder { ... fun setTitle(title: String): RecommendationBuilder { this.title = title return this } fun setDescription(description: String): RecommendationBuilder { this.description = description return this } fun setImage(uri: String): RecommendationBuilder { imageUri = uri return this } fun setBackground(uri: String): RecommendationBuilder { backgroundUri = uri return this } ...
Java
public class RecommendationBuilder { ... public RecommendationBuilder setTitle(String title) { this.title = title; return this; } public RecommendationBuilder setDescription(String description) { this.description = description; return this; } public RecommendationBuilder setImage(String uri) { imageUri = uri; return this; } public RecommendationBuilder setBackground(String uri) { backgroundUri = uri; return this; } ...
建立通知
設定值後,接著即可建構通知,將建構工具類別的值指派給通知,然後呼叫 NotificationCompat.Builder.build()
。
此外,請務必呼叫 setLocalOnly()
,以免其他裝置顯示 NotificationCompat.BigPictureStyle
通知。
以下程式碼範例示範如何建構推薦。
Kotlin
class RecommendationBuilder { ... @Throws(IOException::class) fun build(): Notification { ... val notification = NotificationCompat.BigPictureStyle( NotificationCompat.Builder(context) .setContentTitle(title) .setContentText(description) .setPriority(priority) .setLocalOnly(true) .setOngoing(true) .setColor(context.resources.getColor(R.color.fastlane_background)) .setCategory(Notification.CATEGORY_RECOMMENDATION) .setLargeIcon(image) .setSmallIcon(smallIcon) .setContentIntent(intent) .setExtras(extras)) .build() return notification } }
Java
public class RecommendationBuilder { ... public Notification build() throws IOException { ... Notification notification = new NotificationCompat.BigPictureStyle( new NotificationCompat.Builder(context) .setContentTitle(title) .setContentText(description) .setPriority(priority) .setLocalOnly(true) .setOngoing(true) .setColor(context.getResources().getColor(R.color.fastlane_background)) .setCategory(Notification.CATEGORY_RECOMMENDATION) .setLargeIcon(image) .setSmallIcon(smallIcon) .setContentIntent(intent) .setExtras(extras)) .build(); return notification; } }
執行建議服務
應用程式的推薦服務必須定期執行,才能建立目前的推薦內容。如要執行服務,請建立用於執行計時器的類別,並定期叫用該類別。下列程式碼範例擴充 BroadcastReceiver
類別,以每半小時開始定期執行推薦服務:
Kotlin
class BootupActivity : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { Log.d(TAG, "BootupActivity initiated") if (intent.action.endsWith(Intent.ACTION_BOOT_COMPLETED)) { scheduleRecommendationUpdate(context) } } private fun scheduleRecommendationUpdate(context: Context) { Log.d(TAG, "Scheduling recommendations update") val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val recommendationIntent = Intent(context, UpdateRecommendationsService::class.java) val alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0) alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, INITIAL_DELAY, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent ) } companion object { private val TAG = "BootupActivity" private val INITIAL_DELAY:Long = 5000 } }
Java
public class BootupActivity extends BroadcastReceiver { private static final String TAG = "BootupActivity"; private static final long INITIAL_DELAY = 5000; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "BootupActivity initiated"); if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) { scheduleRecommendationUpdate(context); } } private void scheduleRecommendationUpdate(Context context) { Log.d(TAG, "Scheduling recommendations update"); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class); PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, INITIAL_DELAY, AlarmManager.INTERVAL_HALF_HOUR, alarmIntent); } }
此 BroadcastReceiver
類別的實作必須在啟動該類別的電視裝置啟動後執行。如要完成這項操作,請在應用程式資訊清單中,使用意圖篩選器註冊此類別,以便監聽裝置啟動程序是否完成。下列程式碼範例示範如何將這項設定新增至資訊清單:
<manifest ... > <application ... > <receiver android:name="com.example.android.tvleanback.BootupActivity" android:enabled="true" android:exported="false"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> </intent-filter> </receiver> </application> </manifest>
重要事項:如要接收開機完成通知,應用程式必須要求 RECEIVE_BOOT_COMPLETED
權限。詳情請參閱 ACTION_BOOT_COMPLETED
的說明。
在推薦服務類別的 onHandleIntent()
方法中,按照下列方式將建議發布給管理員:
Kotlin
val notification = notificationBuilder.build() notificationManager.notify(id, notification)
Java
Notification notification = notificationBuilder.build(); notificationManager.notify(id, notification);