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

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

Lưu ý: Chỉ sử dụng các API được mô tả ở đây để đưa ra đề xuất trong các ứng dụng chạy trong các phiên bản Android tối đa và bao gồm Android 7.1 (API cấp 25). Để cung cấp đề xuất cho các ứ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 sẽ xuất hiện ở hàng đầu tiên trên màn hình chính của TV sau lần sử dụng thiết bị đầu tiên. Những nội dung đề xuất trong 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 trong ứng dụng. 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 các đề xuất

Hệ thống đề 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 để tạo ra trải nghiệm người dùng tuyệt vời bằng ứng dụng TV. Vì lý do này, bạn nên xem xét kỹ lưỡng những đề xuất mà bạn đưa ra cho người dùng và quản lý chúng thật chặt chẽ.

Các loại đề xuất

Khi tạo các đề 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 đề xuất đó sang nội dung có liên quan. Dưới đây là một số loại đề xuất cụ thể bạn nên cân nhắc:

  • Các đề xuất Nội dung tiếp tục cho tập tiếp theo để người dùng tiếp tục xem phim bộ. Bạn cũng có thể dùng các đề xuất về việc tiếp tục xem đối với phim, chương trình truyền hình hoặc podcast đang tạm dừng để người dùng có thể quay lại xem nội dung bị tạm dừng chỉ sau vài cú nhấp chuột.
  • Các đề xuất Nội dung mới, chẳng hạn như cho một tập mới chạy lần đầu, nếu người dùng xem xong một loạt phim 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 sử dụng đề xuất nội dung mới cho các mục chưa xem trong danh sách nội dung được theo dõi.
  • Đề xuất Nội dung có liên quan dựa trên hành vi 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 người dùng tốt nhất, hãy xem Hàng đề xuất trong Thông số thiết kế của Android TV.

Làm mới đề xuất

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

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

Bạn có thể tuỳ chỉnh các thẻ đề xuất để truyền tải thông tin thương hiệu bằng cách đặt các phần tử 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ố thiết kế của Android TV.

Đề xuất theo 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ể đưa ra 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 thứ tự các đề xuất cho từng nhóm 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, bạn có thể đảm bảo rằng các đề xuất không bị xếp dưới thứ tự của các đề xuất không liên quan.

Sử dụng NotificationCompat.Builder.setGroup() để đặt chuỗi khoá nhóm cho 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

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

Mã ví dụ sau đây minh hoạ cách mở rộng IntentService để tạo một 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;
    }
}

Để dịch vụ này được hệ thống nhận dạng và chạy, hãy đăng ký dịch vụ bằng tệp kê khai ứng dụng của bạn. Đoạn mã sau đây 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>

Đề xuất về bản dựng

Khi bắt đầu chạy, dịch vụ đề xuất của bạn phải tạo các đề xuất và chuyển các đề xuất đó đến khung Android. Khung này sẽ nhận được các đề xuất dưới dạng đố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 các giá trị

Để đặt giá trị thành phần trên giao diện người dùng cho thẻ đề xuất, bạn cần 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ị của các phần tử trên 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ị, bạn sẽ tạo thông báo, gán các 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 tạo 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 của ứng dụng phải chạy định kỳ để tạo ra 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 đó định kỳ. Ví dụ về mã sau đây 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);
    }
}

Phương thức triển khai lớp BroadcastReceiver này phải chạy sau khi khởi động thiết bị TV mà lớp này được cài đặt. Để thực hiện việc này, hãy đăng ký lớp này trong tệp kê khai ứng dụng của bạn bằng một bộ lọc ý định để theo dõi quá trình 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: Việc nhận được thông báo khởi động xong sẽ yêu cầu ứng dụng 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);