Các kênh trên màn hình chính

Màn hình chính của Android TV hay đơn giản là màn hình chính cung cấp một giao diện người dùng hiển thị nội dung đề xuất dưới dạng bảng kênhchương trình. Mỗi hàng là một kênh. Một kênh chứa thẻ cho mọi chương trình có trên kênh đó:

Màn hình chính của TV

Tài liệu này minh hoạ cách thêm kênh và chương trình vào màn hình chính, cập nhật nội dung, xử lý thao tác của người dùng và mang đến trải nghiệm tốt nhất cho người dùng. (Nếu bạn muốn tìm hiểu sâu hơn về API này, hãy thử lớp học lập trình trên màn hình chính và xem phiên trình bày về Android TV tại I/O 2017.)

Lưu ý: Các kênh đề xuất chỉ có trong Android 8.0 (API cấp 26) trở lên. Bạn phải dùng các thuộc tính này để cung cấp đề xuất cho ứng dụng chạy trên Android 8.0 (API cấp 26) trở lên. Để cung cấp các đề xuất cho các ứng dụng chạy trên các phiên bản Android cũ hơn, ứng dụng của bạn phải sử dụng hàng đề xuất.

Giao diện người dùng trên màn hình chính

Ứng dụng có thể tạo kênh mới, thêm, xoá và cập nhật các chương trình trong một kênh, cũng như kiểm soát thứ tự của các chương trình trên một kênh. Ví dụ: ứng dụng có thể tạo kênh "Có gì mới" và hiển thị thẻ cho các chương trình mới có.

Các ứng dụng không thể kiểm soát thứ tự xuất hiện của các kênh trên màn hình chính. Khi ứng dụng của bạn tạo một kênh mới, màn hình chính sẽ thêm kênh đó vào cuối danh sách kênh. Người dùng có thể sắp xếp lại, ẩn và hiển thị các kênh.

Kênh Watch Next

Kênh Watch Next là hàng thứ hai xuất hiện trên màn hình chính, sau hàng ứng dụng. Hệ thống tạo ra và duy trì kênh này. Ứng dụng của bạn có thể thêm các chương trình vào kênh Watch Next. Để biết thêm thông tin, hãy xem phần Thêm chương trình vào kênh Watch Next.

Kênh ứng dụng

Các kênh mà ứng dụng của bạn tạo đều tuân theo vòng đời này:

  1. Người dùng tìm thấy một kênh trong ứng dụng của bạn và yêu cầu thêm kênh đó vào màn hình chính.
  2. Ứng dụng tạo kênh và thêm kênh vào TvProvider (tại thời điểm này, kênh không hiển thị).
  3. Ứng dụng yêu cầu hệ thống hiển thị kênh đó.
  4. Hệ thống sẽ yêu cầu người dùng phê duyệt kênh mới.
  5. Kênh mới sẽ xuất hiện ở hàng cuối cùng của màn hình chính.

Kênh mặc định

Ứng dụng của bạn có thể cung cấp số lượng kênh bất kỳ để người dùng thêm vào màn hình chính. Thường thì người dùng phải chọn và phê duyệt từng kênh thì kênh đó mới xuất hiện trong màn hình chính. Mỗi ứng dụng đều có lựa chọn tạo một kênh mặc định. Kênh mặc định là đặc biệt vì kênh tự động xuất hiện trên màn hình chính; người dùng không cần phải yêu cầu một cách rõ ràng.

Điều kiện tiên quyết

Màn hình chính của Android TV sử dụng các API TvProvider của Android để quản lý các kênh và chương trình mà ứng dụng của bạn tạo ra. Để truy cập vào dữ liệu của nhà cung cấp, hãy thêm quyền sau đây vào tệp kê khai của ứng dụng:

<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />

Thư viện hỗ trợ TvProvider giúp bạn sử dụng trình cung cấp này dễ dàng hơn. Thêm vào các phần phụ thuộc trong tệp build.gradle:

Groovy

implementation 'androidx.tvprovider:tvprovider:1.0.0'

Kotlin

implementation("androidx.tvprovider:tvprovider:1.0.0")

Để làm việc với các kênh và chương trình, hãy nhớ đưa các tính năng nhập thư viện hỗ trợ sau vào chương trình của bạn:

Kotlin

import android.support.media.tv.Channel
import android.support.media.tv.TvContractCompat
import android.support.media.tv.ChannelLogoUtils
import android.support.media.tv.PreviewProgram
import android.support.media.tv.WatchNextProgram

Java

import android.support.media.tv.Channel;
import android.support.media.tv.TvContractCompat;
import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.PreviewProgram;
import android.support.media.tv.WatchNextProgram;

Kênh

Kênh đầu tiên mà ứng dụng của bạn tạo ra sẽ trở thành kênh mặc định của ứng dụng. Kênh mặc định sẽ tự động xuất hiện trong màn hình chính. Tất cả các kênh khác mà bạn tạo phải được người dùng lựa chọn và chấp nhận thì mới có thể xuất hiện trên màn hình chính.

Tạo kênh

Ứng dụng của bạn phải yêu cầu hệ thống chỉ cho thấy các kênh mới thêm khi ứng dụng đang chạy ở nền trước. Điều này ngăn ứng dụng của bạn hiển thị hộp thoại yêu cầu phê duyệt thêm kênh trong khi người dùng đang chạy một ứng dụng khác. Nếu bạn cố gắng thêm một kênh trong khi chạy ở chế độ nền, thì phương thức onActivityResult() của hoạt động sẽ trả về mã trạng thái RESULT_CANCELED.

Để tạo kênh, hãy làm theo các bước sau:

  1. Tạo trình tạo kênh và đặt các thuộc tính của trình tạo kênh đó. Xin lưu ý rằng loại kênh phải là TYPE_PREVIEW. Thêm các thuộc tính khác theo yêu cầu.

    Kotlin

    val builder = Channel.Builder()
    // Every channel you create must have the type TYPE_PREVIEW
    builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
            .setDisplayName("Channel Name")
            .setAppLinkIntentUri(uri)
    

    Java

    Channel.Builder builder = new Channel.Builder();
    // Every channel you create must have the type TYPE_PREVIEW
    builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
            .setDisplayName("Channel Name")
            .setAppLinkIntentUri(uri);
    
  2. Chèn kênh vào nhà cung cấp:

    Kotlin

    var channelUri = context.contentResolver.insert(
            TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues())
    

    Java

    Uri channelUri = context.getContentResolver().insert(
            TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues());
    
  3. Bạn cần lưu mã nhận dạng kênh để sau này có thể thêm chương trình vào kênh. Trích xuất mã nhận dạng kênh từ URI được trả về:

    Kotlin

    var channelId = ContentUris.parseId(channelUri)
    

    Java

    long channelId = ContentUris.parseId(channelUri);
    
  4. Bạn phải thêm biểu trưng cho kênh của mình. Sử dụng Uri hoặc Bitmap. Biểu tượng biểu trưng phải có kích thước 80 dp x 80 dp và phải ở dạng mờ. Thông tin này hiện dưới một mặt nạ tròn:

    Mặt nạ biểu tượng trên màn hình chính của TV

    Kotlin

    // Choose one or the other
    storeChannelLogo(context: Context, channelId: Long, logoUri: Uri) // also works if logoUri is a URL
    storeChannelLogo(context: Context, channelId: Long, logo: Bitmap)
    

    Java

    // Choose one or the other
    storeChannelLogo(Context context, long channelId, Uri logoUri); // also works if logoUri is a URL
    storeChannelLogo(Context context, long channelId, Bitmap logo);
    
  5. Tạo kênh mặc định (không bắt buộc): Khi ứng dụng của bạn tạo kênh đầu tiên, bạn có thể đặt kênh đó làm kênh mặc định để kênh xuất hiện ngay lập tức trên màn hình chính mà không cần người dùng thực hiện thao tác nào. Mọi kênh khác mà bạn tạo sẽ không xuất hiện cho đến khi người dùng chọn các kênh đó một cách rõ ràng.

    Kotlin

    TvContractCompat.requestChannelBrowsable(context, channelId)
    

    Java

    TvContractCompat.requestChannelBrowsable(context, channelId);
    

  6. Đặt kênh mặc định của bạn xuất hiện trước khi mở ứng dụng. Bạn có thể khiến hành vi này xảy ra bằng cách thêm BroadcastReceiver để theo dõi thao tác android.media.tv.action.INITIALIZE_PROGRAMS mà màn hình chính sẽ gửi sau khi cài đặt ứng dụng:
    <receiver
      android:name=".RunOnInstallReceiver"
      android:exported="true">
        <intent-filter>
          <action android:name="android.media.tv.action.INITIALIZE_PROGRAMS" />
          <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </receiver>
    
    Khi cài đặt ứng dụng không qua cửa hàng ứng dụng trong quá trình phát triển, bạn có thể kiểm thử bước này bằng cách kích hoạt ý định thông qua adb, trong đó your.package.name/.YourReceiverNameBroadcastReceiver của ứng dụng:

    adb shell am broadcast -a android.media.tv.action.INITIALIZE_PROGRAMS -n \
        your.package.name/.YourReceiverName
    

    Trong một số ít trường hợp, ứng dụng của bạn có thể nhận được thông báo truyền tin cùng lúc người dùng khởi động ứng dụng. Hãy đảm bảo mã của bạn không cố thêm kênh mặc định nhiều lần.

Cập nhật kênh

Việc cập nhật kênh cũng tương tự như việc tạo kênh.

Sử dụng một Channel.Builder khác để thiết lập các thuộc tính cần thay đổi.

Sử dụng ContentResolver để cập nhật kênh. Sử dụng mã nhận dạng kênh mà bạn đã lưu khi kênh được thêm lần đầu:

Kotlin

context.contentResolver.update(
        TvContractCompat.buildChannelUri(channelId),
        builder.build().toContentValues(),
        null,
        null
)

Java

context.getContentResolver().update(TvContractCompat.buildChannelUri(channelId),
    builder.build().toContentValues(), null, null);

Để cập nhật biểu trưng của kênh, hãy sử dụng storeChannelLogo().

Xoá kênh

Kotlin

context.contentResolver.delete(TvContractCompat.buildChannelUri(channelId), null, null)

Java

context.getContentResolver().delete(TvContractCompat.buildChannelUri(channelId), null, null);

Chương trình

Thêm chương trình vào kênh ứng dụng

Tạo một PreviewProgram.Builder và đặt các thuộc tính của lớp đó:

Kotlin

val builder = PreviewProgram.Builder()
builder.setChannelId(channelId)
        .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
        .setTitle("Title")
        .setDescription("Program description")
        .setPosterArtUri(uri)
        .setIntentUri(uri)
        .setInternalProviderId(appProgramId)

Java

PreviewProgram.Builder builder = new PreviewProgram.Builder();
builder.setChannelId(channelId)
        .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
        .setTitle("Title")
        .setDescription("Program description")
        .setPosterArtUri(uri)
        .setIntentUri(uri)
        .setInternalProviderId(appProgramId);

Thêm các thuộc tính khác tuỳ thuộc vào loại chương trình. (Để xem các thuộc tính có sẵn cho từng loại chương trình, hãy tham khảo các bảng dưới đây.)

Chèn chương trình vào trình cung cấp:

Kotlin

var programUri = context.contentResolver.insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
        builder.build().toContentValues())

Java

Uri programUri = context.getContentResolver().insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
      builder.build().toContentValues());

Truy xuất mã chương trình để tham khảo sau này:

Kotlin

val programId = ContentUris.parseId(programUri)

Java

long programId = ContentUris.parseId(programUri);

Thêm chương trình vào kênh Watch Next

Để chèn các chương trình vào kênh Watch Next, hãy xem phần Thêm chương trình vào kênh Watch Next.

Cập nhật chương trình

Bạn có thể thay đổi thông tin của chương trình. Ví dụ: Có thể bạn muốn cập nhật giá thuê của một bộ phim hoặc cập nhật một thanh tiến trình cho biết thời lượng xem của một chương trình mà người dùng đã xem.

Sử dụng PreviewProgram.Builder để đặt các thuộc tính bạn cần thay đổi, sau đó gọi getContentResolver().update để cập nhật chương trình. Chỉ định mã chương trình mà bạn đã lưu khi chương trình được thêm lần đầu:

Kotlin

context.contentResolver.update(
        TvContractCompat.buildPreviewProgramUri(programId),
                builder.build().toContentValues(), null, null
)

Java

context.getContentResolver().update(TvContractCompat.buildPreviewProgramUri(programId),
    builder.build().toContentValues(), null, null);

Xoá chương trình

Kotlin

context.contentResolver
        .delete(TvContractCompat.buildPreviewProgramUri(programId), null, null)

Java

context.getContentResolver().delete(TvContractCompat.buildPreviewProgramUri(programId), null, null);

Xử lý thao tác của người dùng

Ứng dụng của bạn có thể giúp người dùng khám phá nội dung bằng cách cung cấp giao diện người dùng để hiển thị và thêm kênh. Ứng dụng cũng phải xử lý các hoạt động tương tác với kênh của bạn sau khi kênh xuất hiện trên màn hình chính.

Khám phá và thêm kênh

Ứng dụng của bạn có thể cung cấp một thành phần trên giao diện người dùng cho phép người dùng chọn và thêm các kênh của ứng dụng đó (ví dụ: nút yêu cầu thêm kênh).

Sau khi người dùng yêu cầu một kênh cụ thể, hãy thực thi mã này để xin phép người dùng thêm kênh đó vào giao diện người dùng màn hình chính:

Kotlin

val intent = Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE)
intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId)
try {
  activity.startActivityForResult(intent, 0)
} catch (e: ActivityNotFoundException) {
  // handle error
}

Java

Intent intent = new Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE);
intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
try {
   activity.startActivityForResult(intent, 0);
} catch (ActivityNotFoundException e) {
  // handle error
}

Hệ thống sẽ hiển thị hộp thoại yêu cầu người dùng phê duyệt kênh. Xử lý kết quả của yêu cầu trong phương thức onActivityResult của hoạt động (Activity.RESULT_CANCELED hoặc Activity.RESULT_OK).

Sự kiện trên màn hình chính của Android TV

Khi người dùng tương tác với các chương trình/kênh do ứng dụng xuất bản, màn hình chính sẽ gửi ý định đến ứng dụng:

  • Màn hình chính sẽ gửi Uri lưu trữ trong thuộc tính APP_LINK_INTENT_URI của một kênh tới ứng dụng khi người dùng chọn biểu trưng của kênh đó. Ứng dụng chỉ nên khởi chạy giao diện người dùng chính hoặc chế độ xem liên quan đến kênh đã chọn.
  • Màn hình chính sẽ gửi Uri lưu trữ trong thuộc tính INTENT_URI của một chương trình cho ứng dụng khi người dùng chọn một chương trình. Ứng dụng sẽ phát nội dung được chọn.
  • Người dùng có thể cho biết rằng họ không còn quan tâm đến một chương trình nữa và muốn xoá chương trình đó khỏi giao diện người dùng của màn hình chính. Hệ thống sẽ xoá chương trình khỏi giao diện người dùng rồi gửi ứng dụng sở hữu chương trình này một ý định (android.media.tv.ACTION_preview_PROGRAM_BROWSABLE_ENABLEDD hoặc android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_SUMMARYD) với mã của chương trình. Ứng dụng nên xoá chương trình khỏi nhà cung cấp và KHÔNG nên chèn lại.

Hãy nhớ tạo bộ lọc ý định cho tất cả Uris mà màn hình chính gửi để người dùng tương tác, ví dụ:

<receiver
   android:name=".WatchNextProgramRemoved"
   android:enabled="true"
   android:exported="true">
   <intent-filter>
       <action android:name="android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
   </intent-filter>
</receiver>

Các phương pháp hay nhất

  • Nhiều ứng dụng dành cho TV yêu cầu người dùng phải đăng nhập. Trong trường hợp này, BroadcastReceiver theo dõi android.media.tv.action.INITIALIZE_PROGRAMS sẽ đề xuất nội dung kênh cho người dùng chưa xác thực.Ví dụ: ban đầu, ứng dụng của bạn có thể hiển thị nội dung hay nhất hoặc nội dung đang phổ biến. Sau khi người dùng đăng nhập, ứng dụng này có thể hiển thị nội dung được cá nhân hoá. Đây là cơ hội tuyệt vời để ứng dụng bán thêm cho người dùng trước khi họ đăng nhập.
  • Khi ứng dụng của bạn không chạy ở nền trước và bạn cần cập nhật một kênh hoặc chương trình, hãy sử dụng JobScheduler để lên lịch công việc (xem: JobSchedulerJobService).
  • Hệ thống có thể thu hồi quyền của nhà cung cấp đối với ứng dụng nếu ứng dụng của bạn hoạt động không chính xác (ví dụ: liên tục gửi dữ liệu cho nhà cung cấp). Hãy đảm bảo bạn gói mã truy cập vào trình cung cấp bằng mệnh đề try-catch để xử lý các ngoại lệ về bảo mật.
  • Trước khi cập nhật các chương trình và kênh, hãy truy vấn trình cung cấp để biết dữ liệu bạn cần cập nhật và điều chỉnh dữ liệu. Ví dụ: không cần cập nhật chương trình mà người dùng muốn xoá khỏi giao diện người dùng. Sử dụng công việc trong nền để chèn/cập nhật dữ liệu của bạn vào trình cung cấp sau khi truy vấn dữ liệu hiện có và sau đó yêu cầu phê duyệt cho các kênh của bạn. Bạn có thể chạy công việc này khi ứng dụng khởi động và bất cứ khi nào ứng dụng cần cập nhật dữ liệu.

    Kotlin

    context.contentResolver
      .query(
          TvContractCompat.buildChannelUri(channelId),
              null, null, null, null).use({
                  cursor-> if (cursor != null and cursor.moveToNext()) {
                               val channel = Channel.fromCursor(cursor)
                               if (channel.isBrowsable()) {
                                   //update channel's programs
                               }
                           }
              })
    

    Java

    try (Cursor cursor = context.getContentResolver()
          .query(
              TvContractCompat.buildChannelUri(channelId),
              null,
              null,
              null,
              null)) {
                  if (cursor != null && cursor.moveToNext()) {
                      Channel channel = Channel.fromCursor(cursor);
                      if (channel.isBrowsable()) {
                          //update channel's programs
                      }
                  }
              }
    
  • Sử dụng URI duy nhất cho tất cả hình ảnh (biểu trưng, biểu tượng, hình ảnh nội dung). Nhớ sử dụng một URI khác khi bạn cập nhật hình ảnh. Tất cả hình ảnh đều được lưu vào bộ nhớ đệm. Nếu bạn không thay đổi Uri khi thay đổi hình ảnh, hình ảnh cũ sẽ tiếp tục xuất hiện.

  • Hãy nhớ rằng mệnh đề WHERE không được cho phép và các lệnh gọi đến nhà cung cấp có mệnh đề WHERE sẽ gửi một ngoại lệ bảo mật.

Thuộc tính

Phần này mô tả riêng biệt các thuộc tính của kênh và chương trình.

Thuộc tính của kênh

Bạn phải chỉ định các thuộc tính này cho mọi kênh:

Thuộc tính Ghi chú
LOẠI được đặt thành TYPE_PREVIEW.
DISPLAY_NAME được đặt thành tên kênh.
APP_LINK_INTENT_URI Khi người dùng chọn biểu trưng của kênh, hệ thống sẽ gửi ý định bắt đầu một hoạt động giới thiệu nội dung liên quan đến kênh. Đặt thuộc tính này thành URI dùng trong bộ lọc ý định cho hoạt động đó.

Ngoài ra, một kênh cũng có 6 trường dành riêng để sử dụng ứng dụng nội bộ. Bạn có thể dùng các trường này để lưu trữ khoá hoặc các giá trị khác có thể giúp ứng dụng ánh xạ kênh với cấu trúc dữ liệu nội bộ:

  • NỘI BỘ_provider_ID
  • NỘI_ĐỊA_DỮ LIỆU
  • NỘI bộ_Nhà cung cấp_FLAG1
  • NỘI bộ_Nhà cung cấp_FLAG2
  • NỘI bộ_Nhà cung cấp_FLAG3
  • NỘI bộ_provider_FLAG4

Thuộc tính chương trình

Xem các trang riêng để biết các thuộc tính của từng loại chương trình:

Mã mẫu

Để tìm hiểu thêm về cách tạo các ứng dụng tương tác với màn hình chính cũng như thêm kênh và chương trình vào màn hình chính của Android TV, hãy xem lớp học lập trình trên màn hình chính của chúng tôi.