Chiếu nội dung nghe nhìn

Các API android.media.projection được giới thiệu trong Android 5 (cấp độ API 21) cho phép bạn chụp lại nội dung của màn hình thiết bị dưới dạng luồng nội dung nghe nhìn mà bạn có thể phát lại, quay hoặc truyền sang các thiết bị khác, chẳng hạn như TV.

Android 14 (cấp độ API 34) ra mắt tính năng chia sẻ màn hình ứng dụng, cho phép người dùng chia sẻ một cửa sổ ứng dụng thay vì toàn bộ màn hình thiết bị, bất kể chế độ kết xuất cửa sổ hiện tại nào. Tính năng chia sẻ màn hình ứng dụng sẽ loại trừ thanh trạng thái, thanh điều hướng, thông báo và các thành phần giao diện người dùng hệ thống khác khỏi màn hình được chia sẻ, ngay cả khi tính năng chia sẻ màn hình ứng dụng được dùng để ghi lại một ứng dụng ở chế độ toàn màn hình. Chỉ nội dung của ứng dụng đã chọn được chia sẻ.

Tính năng chia sẻ màn hình ứng dụng đảm bảo quyền riêng tư của người dùng, tăng hiệu suất của người dùng và nâng cao khả năng đa nhiệm bằng cách cho phép người dùng chạy nhiều ứng dụng nhưng chỉ chia sẻ nội dung với một ứng dụng.

Ba cách trình bày quảng cáo hiển thị

Tính năng chiếu nội dung nghe nhìn sẽ chụp lại nội dung của màn hình thiết bị hoặc cửa sổ ứng dụng, sau đó chiếu hình ảnh được chụp lên màn hình ảo để kết xuất hình ảnh đó trên Surface.

Màn hình thiết bị thực tế chiếu lên màn hình ảo. Nội dung của màn hình ảo được ghi vào `Bề mặt` (Surface) do ứng dụng cung cấp.
Hình 1. Màn hình thiết bị thực tế hoặc cửa sổ ứng dụng chiếu lên màn hình ảo. Màn hình ảo được ghi vào Surface do ứng dụng cung cấp.

Ứng dụng này cung cấp Surface thông qua MediaRecorder, SurfaceTexture hoặc ImageReader. Các đối tượng này sử dụng nội dung của màn hình được chụp và cho phép bạn quản lý hình ảnh được kết xuất trên Surface theo thời gian thực. Bạn có thể lưu hình ảnh dưới dạng bản ghi hoặc truyền vào TV hoặc thiết bị khác.

Màn hình thực

Bắt đầu một phiên chiếu nội dung nghe nhìn bằng cách lấy mã thông báo giúp ứng dụng của bạn có thể chụp lại nội dung của màn hình thiết bị hoặc cửa sổ ứng dụng. Mã thông báo này được biểu thị bằng một thực thể của lớp MediaProjection.

Sử dụng phương thức getMediaProjection() của dịch vụ hệ thống MediaProjectionManager để tạo một thực thể MediaProjection khi bạn bắt đầu một hoạt động mới. Bắt đầu hoạt động bằng một ý định từ phương thức createScreenCaptureIntent() để chỉ định một thao tác chụp màn hình:

Kotlin

val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java)
var mediaProjection : MediaProjection
val startMediaProjection = registerForActivityResult( StartActivityForResult() ) { result -> if (result.resultCode == RESULT_OK) { mediaProjection = mediaProjectionManager .getMediaProjection(result.resultCode, result.data!!) } }
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())

Java

final MediaProjectionManager mediaProjectionManager =
    getSystemService(MediaProjectionManager.class);
final MediaProjection[] mediaProjection = new MediaProjection[1];
ActivityResultLauncher startMediaProjection = registerForActivityResult( new StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { mediaProjection[0] = mediaProjectionManager .getMediaProjection(result.getResultCode(), result.getData()); } } );
startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());

Màn hình ảo

Tâm điểm của tính năng chiếu nội dung nghe nhìn là màn hình ảo mà bạn tạo bằng cách gọi createVirtualDisplay() trên một thực thể MediaProjection:

Kotlin

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null)

Java

virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null);

Các tham số widthheight chỉ định kích thước của màn hình ảo. Để lấy các giá trị cho chiều rộng và chiều cao, hãy sử dụng các API WindowMetrics được giới thiệu trong Android 11 (cấp độ API 30). (Để biết thông tin chi tiết, hãy xem phần Kích thước chiếu nội dung nghe nhìn.)

Bề mặt

Định kích thước bề mặt chiếu nội dung nghe nhìn để có đầu ra ở độ phân giải phù hợp. Kích thước lớn (độ phân giải thấp) để truyền màn hình sang TV hoặc màn hình máy tính và kích thước nhỏ (độ phân giải cao) để quay màn hình thiết bị.

Kể từ Android 12L (cấp độ API 32), khi kết xuất nội dung đã chụp trên bề mặt, hệ thống sẽ điều chỉnh tỷ lệ nội dung một cách đồng nhất, duy trì tỷ lệ khung hình, sao cho cả hai kích thước của nội dung (chiều rộng và chiều cao) đều bằng hoặc nhỏ hơn kích thước tương ứng của bề mặt. Sau đó, nội dung được chụp sẽ được căn giữa trên bề mặt.

Phương pháp điều chỉnh tỷ lệ của Android 12L cải thiện tính năng truyền màn hình sang TV và các màn hình lớn khác bằng cách tối đa hoá kích thước của hình ảnh bề mặt trong khi vẫn đảm bảo tỷ lệ khung hình phù hợp.

Quyền sử dụng dịch vụ trên nền trước

Nếu ứng dụng của bạn nhắm đến Android 14 trở lên, thì tệp kê khai ứng dụng phải có một nội dung khai báo quyền cho loại dịch vụ trên nền trước mediaProjection:

<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>

Bắt đầu dịch vụ chiếu nội dung nghe nhìn bằng một lệnh gọi đến startForeground().

Nếu bạn không chỉ định loại dịch vụ trên nền trước trong lệnh gọi, thì loại này sẽ mặc định là một số nguyên theo bit của các loại dịch vụ trên nền trước được xác định trong tệp kê khai. Nếu tệp kê khai không chỉ định bất kỳ loại dịch vụ nào, thì hệ thống sẽ gửi ra MissingForegroundServiceTypeException.

Ứng dụng của bạn phải yêu cầu sự đồng ý của người dùng trước mỗi phiên chiếu nội dung nghe nhìn. Phiên là một lệnh gọi duy nhất đến createVirtualDisplay(). Bạn chỉ được dùng mã thông báo MediaProjection một lần để thực hiện cuộc gọi.

Trên Android 14 trở lên, phương thức createVirtualDisplay() sẽ gửi một SecurityException nếu ứng dụng của bạn thực hiện một trong những thao tác sau:

  • Truyền một thực thể Intent được trả về từ createScreenCaptureIntent() đến getMediaProjection() nhiều lần
  • Gọi createVirtualDisplay() nhiều lần trên cùng một thực thể MediaProjection

Kích thước chiếu nội dung nghe nhìn

Chiếu nội dung nghe nhìn có thể chụp toàn bộ màn hình thiết bị hoặc cửa sổ ứng dụng, bất kể chế độ kết xuất cửa sổ hiện tại.

Kích thước ban đầu

Với tính năng chiếu nội dung nghe nhìn ở chế độ toàn màn hình, ứng dụng của bạn phải xác định kích thước màn hình thiết bị. Trong tính năng chia sẻ màn hình trong ứng dụng, ứng dụng của bạn sẽ không thể xác định kích thước của màn hình đã chụp cho đến khi người dùng chọn vùng chụp. Vì vậy, kích thước ban đầu của mọi nội dung chiếu đa phương tiện là kích thước màn hình thiết bị.

Sử dụng phương thức WindowManager getMaximumWindowMetrics() của nền tảng để trả về đối tượng WindowMetrics cho màn hình thiết bị ngay cả khi ứng dụng lưu trữ chiếu nội dung nghe nhìn ở chế độ nhiều cửa sổ, chỉ chiếm một phần màn hình.

Để tương thích xuống đến cấp độ API 14, hãy sử dụng phương thức WindowMetricsCalculator computeMaximumWindowMetrics() trong thư viện Jetpack WindowManager.

Gọi phương thức WindowMetrics getBounds() để lấy chiều rộng và chiều cao của màn hình thiết bị.

Thay đổi kích thước

Kích thước của nội dung truyền trực tiếp có thể thay đổi khi thiết bị được xoay hoặc người dùng chọn một cửa sổ ứng dụng làm vùng chụp khi chia sẻ màn hình ứng dụng. Hình chiếu nội dung nghe nhìn có thể bị hòm thư nếu nội dung được ghi có kích thước khác với chỉ số cửa sổ tối đa thu được khi thiết lập hình chiếu nội dung nghe nhìn.

Để đảm bảo chiếu nội dung nghe nhìn căn chỉnh chính xác với kích thước của nội dung đã chụp cho mọi vùng đã chụp và trên các hướng xoay thiết bị, hãy dùng lệnh gọi lại onCapturedContentResize() để đổi kích thước ảnh chụp. (Để biết thêm thông tin, hãy xem phần Tuỳ chỉnh ở phần tiếp theo).

Tùy chỉnh

Ứng dụng của bạn có thể tuỳ chỉnh trải nghiệm người dùng khi chiếu nội dung nghe nhìn bằng các API MediaProjection.Callback sau đây:

  • onCapturedContentVisibilityChanged(): Cho phép ứng dụng lưu trữ (ứng dụng đã bắt đầu phiên chiếu nội dung nghe nhìn) hiện hoặc ẩn nội dung được chia sẻ.

    Sử dụng lệnh gọi lại này để tuỳ chỉnh giao diện người dùng của ứng dụng dựa trên việc người dùng có nhìn thấy vùng đã chụp hay không. Ví dụ: nếu ứng dụng của bạn hiển thị cho người dùng và đang hiển thị nội dung đã ghi trong giao diện người dùng của ứng dụng, đồng thời ứng dụng đã ghi cũng hiển thị cho người dùng (như được chỉ ra thông qua lệnh gọi lại này), thì người dùng sẽ thấy cùng một nội dung hai lần. Sử dụng lệnh gọi lại để cập nhật giao diện người dùng của ứng dụng nhằm ẩn nội dung đã chụp và giải phóng không gian bố cục trong ứng dụng cho nội dung khác.

  • onCapturedContentResize(): Cho phép ứng dụng lưu trữ thay đổi kích thước của chiếu nội dung nghe nhìn trên màn hình ảo và chiếu nội dung nghe nhìn Surface dựa trên kích thước của vùng hiển thị đã chụp.

    Được kích hoạt bất cứ khi nào nội dung được ghi lại (một cửa sổ ứng dụng hoặc toàn bộ màn hình thiết bị) thay đổi kích thước (do thiết bị xoay hoặc ứng dụng được ghi lại chuyển sang một chế độ cửa sổ khác). Sử dụng API này để đổi kích thước cả màn hình ảo và bề mặt nhằm đảm bảo tỷ lệ khung hình khớp với nội dung đã chụp và ảnh chụp không bị hòm thư.

Khôi phục tài nguyên

Ứng dụng của bạn phải đăng ký lệnh gọi lại MediaProjection onStop() để được thông báo khi phiên chiếu nội dung nghe nhìn bị dừng và trở nên không hợp lệ. Khi phiên kết thúc, ứng dụng của bạn phải giải phóng các tài nguyên mà ứng dụng đang giữ, chẳng hạn như màn hình ảo và bề mặt chiếu. Phiên chiếu nội dung nghe nhìn đã dừng không thể tạo màn hình ảo mới nữa, ngay cả khi ứng dụng của bạn chưa tạo màn hình ảo cho phiên chiếu nội dung nghe nhìn đó.

Hệ thống sẽ gọi lệnh gọi lại khi quá trình chiếu nội dung nghe nhìn kết thúc. Việc chấm dứt này có thể xảy ra vì một số lý do, chẳng hạn như:

  • người dùng dừng phiên bằng giao diện người dùng của ứng dụng hoặc thanh trạng thái chiếu nội dung nghe nhìn của hệ thống
  • màn hình đang bị khoá
  • một phiên chiếu nội dung nghe nhìn khác bắt đầu
  • dừng quá trình của ứng dụng

Nếu ứng dụng của bạn không đăng ký lệnh gọi lại, mọi lệnh gọi đến createVirtualDisplay() sẽ gửi IllegalStateException.

Ngừng

Theo mặc định, Android 14 trở lên cho phép chia sẻ màn hình ứng dụng. Mỗi phiên chiếu nội dung nghe nhìn đều cho phép người dùng chia sẻ một cửa sổ ứng dụng hoặc toàn bộ màn hình.

Ứng dụng của bạn có thể chọn không chia sẻ màn hình ứng dụng bằng cách gọi phương thức createScreenCaptureIntent(MediaProjectionConfig) bằng đối số MediaProjectionConfig được trả về từ lệnh gọi đến createConfigForDefaultDisplay().

Lệnh gọi đến createScreenCaptureIntent(MediaProjectionConfig) với đối số MediaProjectionConfig được trả về từ lệnh gọi đến createConfigForUserChoice() cũng giống như hành vi mặc định, tức là lệnh gọi đến createScreenCaptureIntent().

Ứng dụng có thể thay đổi kích thước

Kích thước ứng dụng chiếu nội dung nghe nhìn phải luôn có thể đổi kích thước (resizeableActivity="true"). Các ứng dụng có thể thay đổi kích thước hỗ trợ thay đổi cấu hình thiết bị và chế độ nhiều cửa sổ (xem phần Hỗ trợ nhiều cửa sổ).

Nếu ứng dụng không thể thay đổi kích thước, thì ứng dụng phải truy vấn các ranh giới hiển thị của cửa sổ và sử dụng getMaximumWindowMetrics() để truy xuất WindowMetrics của khu vực hiển thị tối đa có sẵn cho ứng dụng :

Kotlin

val windowContext = context.createWindowContext(context.display!!,
      WindowManager.LayoutParams.TYPE_APPLICATION, null)
val projectionMetrics = windowContext.getSystemService(WindowManager::class.java)
      .maximumWindowMetrics

Java

Context windowContext = context.createWindowContext(context.getDisplay(),
      WindowManager.LayoutParams.TYPE_APPLICATION, null);
WindowMetrics projectionMetrics = windowContext.getSystemService(WindowManager.class)
      .getMaximumWindowMetrics();

Khối trạng thái trên thanh trạng thái và tính năng tự động dừng

Screen projection exploits expose private user data such as financial information because users don't realize their device screen is being shared.

For apps running on devices with Android 15 QPR1 or higher, a status bar chip that is large and prominent alerts users to any in‑progress screen projection. Users can tap the chip to stop their screen from being shared, cast, or recorded. Also, screen projection automatically stops when the device screen is locked.

Hình 2. Khối trạng thái trên thanh trạng thái cho tính năng chia sẻ màn hình, truyền và ghi hình.

Kiểm thử khả năng cung cấp khối trạng thái chiếu nội dung nghe nhìn bằng cách bắt đầu chia sẻ màn hình, truyền hoặc ghi hình. Khối này sẽ xuất hiện trong thanh trạng thái.

Để đảm bảo ứng dụng của bạn giải phóng tài nguyên và cập nhật giao diện người dùng khi người dùng dừng chiếu màn hình bằng cách tương tác với chip trên thanh trạng thái hoặc bằng cách kích hoạt màn hình khoá, hãy làm như sau:

  • Tạo một thực thể của MediaProjection.Callback.

  • Triển khai phương thức gọi lại onStop(). Phương thức này được gọi khi tính năng chiếu màn hình dừng. Giải phóng mọi tài nguyên mà ứng dụng của bạn đang giữ và cập nhật giao diện người dùng của ứng dụng nếu cần.

Để kiểm thử lệnh gọi lại, hãy nhấn vào chip trên thanh trạng thái hoặc khoá màn hình thiết bị để dừng chiếu màn hình. Xác minh rằng phương thức onStop() được gọi và ứng dụng của bạn phản hồi đúng như dự kiến.

Tài nguyên khác

Để biết thêm thông tin về chiếu nội dung nghe nhìn, hãy xem phần Quay video và phát lại âm thanh.