Đề xuất cho Android N trở xuống

Khi tương tác với TV, người dùng thường thích đưa ra ít dữ liệu đầu vào nhất trước khi xem nội dung. Một tình huống lý tưởng cho nhiều người dùng TV là: ngồi xuống, bật và xem. Nhìn chung, bước ít nhất để đưa người dùng đến nội dung mà họ yêu thích chính là cách họ thích.

Lưu ý: Chỉ sử dụng các API mô tả ở đây để đưa ra đề xuất trong các ứng dụng chạy trong các phiên bản Android cho đến và bao gồm Android 7.1 (API cấp 25). Để cung cấp nội dung đề xuất cho ứng dụng chạy trên Android 8.0 (API cấp 26) trở lên, ứng dụng của bạn phải sử dụng kênh đề xuất.

Khung Android hỗ trợ hoạt động tương tác đầu vào tối thiểu bằng cách cung cấp một hàng đề xuất trên màn hình chính. Nội dung đề xuất xuất hiện ở hàng đầu tiên trên màn hình chính của TV sau lần đầu tiên sử dụng thiết bị này. Việc đưa ra các đề xuất từ danh mục nội dung của ứng dụng có thể giúp đưa người dùng quay lại ứng dụng của bạn.

Hình 1. Ví dụ về hàng đề xuất.

Hướng dẫn này chỉ cho bạn cách tạo và cung cấp các đề xuất cho khung Android để người dùng có thể dễ dàng khám phá và thưởng thức nội dung ứng dụng của bạn. Ngoài ra, hãy xem cách triển khai mẫu trong ứng dụng mẫu Leanback.

Các phương pháp hay nhất cho nội dung đề xuất

Nội dung đề xuất giúp người dùng nhanh chóng tìm thấy nội dung và ứng dụng mà họ yêu thích. Việc tạo các đề xuất có chất lượng cao và phù hợp với người dùng là một yếu tố quan trọng trong việc tạo ra trải nghiệm người dùng tuyệt vời cho ứng dụng TV. Vì lý do này, bạn nên xem xét cẩn thận những đề xuất mà bạn đưa ra cho người dùng và quản lý các đề xuất đó một cách chặt chẽ.

Các loại đề xuất

Khi tạo đề xuất, bạn nên liên kết người dùng quay lại các hoạt động xem chưa hoàn chỉnh hoặc đề xuất các hoạt động mở rộng hoạt động đó đến nội dung liên quan. Dưới đây là một số loại đề xuất cụ thể mà bạn nên cân nhắc:

  • Các đề xuất về nội dung tiếp tục cho tập tiếp theo để người dùng tiếp tục xem chương trình dài tập. Bạn cũng có thể sử dụng các đề xuất tiếp tục xem phim, chương trình truyền hình hoặc podcast bị tạm dừng để người dùng có thể quay lại xem nội dung bị tạm dừng chỉ bằng vài cú nhấp chuột.
  • Các đề xuất nội dung mới, chẳng hạn như cho tập mới chiếu trong tập đầu tiên, nếu người dùng xem xong một chương trình dài tập khác. Ngoài ra, nếu ứng dụng của bạn cho phép người dùng đăng ký, theo dõi hoặc theo dõi nội dung, hãy áp dụng các đề xuất mới về nội dung cho các mục chưa xem trong danh sách nội dung được theo dõi.
  • Nội dung đề xuất về Nội dung có liên quan dựa trên thói quen xem trước đây của người dùng.

Để biết thêm thông tin về cách thiết kế thẻ đề xuất nhằm mang lại trải nghiệm tốt nhất cho người dùng, hãy xem Hàng Đề xuất trong Thông số kỹ thuật thiết kế của Android TV.

Làm mới đề xuất

Khi làm mới các đề xuất, đừng chỉ xoá rồi đăng lại, bởi làm như vậy sẽ khiến các đề xuất xuất hiện ở cuối hàng đề xuất. Sau khi một mục nội dung, chẳng hạn như phim, đã được phát, hãy xoá mục đó khỏi đề xuất.

Tuỳ chỉnh nội dung đề xuất

Bạn có thể tuỳ chỉnh thẻ đề xuất để truyền tải thông tin thương hiệu bằng cách đặt các thành phần trên giao diện người dùng (chẳng hạn như hình ảnh nền trước và nền sau, màu sắc, biểu tượng ứng dụng, tiêu đề và tiêu đề phụ của thẻ). Để tìm hiểu thêm, hãy xem Hàng Đề xuất trong Thông số kỹ thuật thiết kế của Android TV.

Đề xuất cho nhóm

Bạn có thể tuỳ ý nhóm các đề xuất dựa trên nguồn đề xuất. Ví dụ: ứng dụng của bạn có thể cung cấp 2 nhóm đề xuất: đề xuất về nội dung mà người dùng đã đăng ký và đề xuất về nội dung thịnh hành mới mà có thể người dùng chưa biết.

Hệ thống sẽ xếp hạng và sắp xếp các đề xuất cho từng nhóm một cách riêng biệt khi tạo hoặc cập nhật hàng đề xuất. Bằng cách cung cấp thông tin nhóm cho các đề xuất của bạn, bạn có thể đảm bảo rằng các đề xuất của bạn không được sắp xếp theo thứ tự đề xuất thấp hơn các đề xuất không liên quan.

Sử dụng NotificationCompat.Builder.setGroup() để đặt chuỗi khoá nhóm của một đề xuất. Ví dụ: để đánh dấu một đề xuất là thuộc về nhóm chứa nội dung thịnh hành mới, bạn có thể gọi setGroup("trending").

Tạo dịch vụ đề xuất

Đề xuất về nội dung được tạo bằng tính năng xử lý nền. Để ứng dụng của bạn đóng góp vào các đề xuất, hãy tạo một dịch vụ giúp định kỳ thêm các trang thông tin từ danh mục ứng dụng vào danh sách đề xuất của hệ thống.

Ví dụ về mã sau đây minh hoạ cách mở rộng IntentService để tạo dịch vụ đề xuất cho ứng dụng của bạn:

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

Để hệ thống nhận dạng và chạy dịch vụ này, hãy đăng ký dịch vụ bằng cách sử dụng tệp kê khai ứng dụng của bạn. Đoạn mã sau minh hoạ cách khai báo lớp này là một dịch vụ:

<manifest ... >
  <application ... >
    ...

    <service
            android:name="com.example.android.tvleanback.UpdateRecommendationsService"
            android:enabled="true" />
  </application>
</manifest>

Xây dựng các đề xuất

Khi dịch vụ đề xuất bắt đầu chạy, dịch vụ đó phải tạo các đề xuất và chuyển các đề xuất đó đến khung Android. Khung này nhận được các đề xuất dưới dạng các đối tượng Notification sử dụng một mẫu cụ thể và được đánh dấu bằng một danh mục cụ thể.

Đặt giá trị

Để đặt giá trị thành phần trên giao diện người dùng cho thẻ đề xuất, bạn hãy tạo một lớp trình tạo tuân theo mẫu trình tạo được mô tả như sau. Trước tiên, bạn đặt giá trị cho các phần tử của thẻ đề xuất.

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

Tạo thông báo

Sau khi đặt giá trị, hãy tạo thông báo, gán giá trị từ lớp trình tạo cho thông báo và gọi NotificationCompat.Builder.build().

Ngoài ra, hãy nhớ gọi setLocalOnly() để thông báo NotificationCompat.BigPictureStyle không xuất hiện trên các thiết bị khác.

Ví dụ về mã sau đây minh hoạ cách xây dựng một đề xuất.

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

Chạy dịch vụ đề xuất

Dịch vụ đề xuất dành cho ứng dụng của bạn phải chạy định kỳ để tạo các đề xuất hiện có. Để chạy dịch vụ, hãy tạo một lớp chạy bộ tính giờ và gọi lớp đó theo định kỳ. Ví dụ về mã sau đây sẽ mở rộng lớp BroadcastReceiver để bắt đầu thực thi định kỳ một dịch vụ đề xuất nửa giờ một lần:

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

Quá trình triển khai lớp BroadcastReceiver này phải chạy sau khi khởi động thiết bị TV chứa lớp này. Để thực hiện việc này, hãy đăng ký lớp này trong tệp kê khai ứng dụng bằng bộ lọc ý định để theo dõi việc hoàn tất quá trình khởi động thiết bị. Mã mẫu sau đây minh hoạ cách thêm cấu hình này vào tệp kê khai:

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

Lưu ý quan trọng: Để nhận được thông báo khởi động xong, ứng dụng của bạn phải yêu cầu quyền RECEIVE_BOOT_COMPLETED. Để biết thêm thông tin, hãy xem ACTION_BOOT_COMPLETED.

Trong phương thức onHandleIntent() của lớp dịch vụ đề xuất, hãy đăng đề xuất cho người quản lý như sau:

Kotlin

val notification = notificationBuilder.build()
notificationManager.notify(id, notification)

Java

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