Android N 이전 버전의 추천

TV와 상호작용할 때 일반적으로 사용자는 콘텐츠를 보기 전에 최소한의 입력만 하는 것을 선호합니다. 많은 TV 사용자에게 이상적인 시나리오는 앉아서 TV를 켜고 시청하는 것입니다. 일반적으로 사용자가 즐기는 콘텐츠에 도달하는 가장 적은 단계는 사용자가 선호하는 경로입니다.

참고: 여기에 설명된 API를 사용하여 Android 7.1(API 레벨 25) 이하의 Android 버전에서만 실행되는 앱에 추천하세요. Android 8.0(API 레벨 26) 이상에서 실행되는 앱에 추천을 제공하려면 앱에서 추천 채널을 사용해야 합니다.

Android 프레임워크는 홈 화면에 추천 행을 제공하여 최소 입력 상호작용을 지원합니다. 콘텐츠 추천은 기기를 처음 사용한 후 TV 홈 화면의 첫 번째 행으로 표시됩니다. 앱의 콘텐츠 카탈로그에서 추천 항목을 제공하면 사용자를 다시 앱으로 안내할 수 있습니다.

그림 1. 추천 행의 예

이 과정에서는 추천 항목을 만들고 Android 프레임워크에 제공하여 사용자가 앱 콘텐츠를 쉽게 발견하고 즐길 수 있도록 하는 방법을 알려줍니다. 이번 논의에서는 Android TV GitHub 저장소에 있는 Android Leanback 샘플 앱의 일부 코드를 설명합니다.

추천 권장사항

추천을 통해 사용자는 자신이 즐기는 콘텐츠와 앱을 빠르게 찾을 수 있습니다. 사용자와 관련된 고품질의 추천을 만드는 일은 TV 앱에서 훌륭한 사용자 환경을 만드는 중요한 요인입니다. 이러한 이유로 신중하게 고려하여 사용자에게 추천을 제공하고 주의 깊게 관리해야 합니다.

추천 유형

추천을 만들 때 사용자를 완료되지 않은 시청 활동으로 다시 연결하거나 관련 콘텐츠로 확장하는 활동을 제안해야 합니다. 고려해야 할 특정 추천 유형은 다음과 같습니다.

  • 다음 에피소드에 관해 연속 콘텐츠를 추천하여 사용자가 시리즈를 계속 시청할 수 있도록 합니다. 또는 일시중지된 영화나 TV 프로그램, 팟캐스트에 연속 추천을 사용하여 몇 번의 클릭만으로 사용자가 일시중지된 콘텐츠를 다시 시청할 수 있습니다.
  • 사용자가 다른 시리즈 시청을 끝냈다면 첫 방송된 새로운 에피소드와 같은 새로운 콘텐츠를 추천합니다. 또한 앱에서 사용자가 콘텐츠를 구독하거나 팔로우하거나 추적할 수 있는 경우 추적된 콘텐츠 목록에서 시청하지 않은 항목에 새로운 콘텐츠 추천을 사용하세요.
  • 사용자의 과거 시청 행동에 기반한 관련 콘텐츠를 추천합니다.

추천 카드 디자인으로 최상의 사용자 환경을 만드는 방법에 관한 자세한 내용은 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
        }
    }
    

자바

    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를 사용하여 서비스를 등록하세요. 다음 코드 스니펫은 이 클래스를 서비스로 선언하는 방법을 보여줍니다.

    <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
        }

    ...
    

자바

    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
        }
    }
    

자바

    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 클래스를 확장하여 30분마다 추천 서비스의 주기적 실행을 시작합니다.

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
        }
    }
    

자바

    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 클래스의 이러한 구현은 설치된 TV 기기가 시작된 후에 실행되어야 합니다. 이를 위해 기기 부팅 프로세스의 완료를 수신 대기하는 인텐트 필터를 사용하여 앱 manifest에 이 클래스를 등록하세요. 다음 샘플 코드에서는 이 구성을 manifest에 추가하는 방법을 보여줍니다.

    <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)
    

자바

    Notification notification = notificationBuilder.build();
    notificationManager.notify(id, notification);