大屏设备上的媒体投影

借助 Android 5(API 级别 21)中引入的媒体投影 API,您可以将设备屏幕中的内容截取为可播放、录制或投屏到其他设备(如电视)的媒体流。

媒体投影涉及设备屏幕的三种呈现形式:

投影到虚拟屏幕上的真实设备屏幕。写入应用提供的 `Surface` 的虚拟屏幕内容。
图 1. 投影到虚拟屏幕上的真实设备屏幕。写入应用提供的 Surface 的虚拟屏幕内容。

媒体投影截取设备屏幕中的内容,然后将截取的图像投影到虚拟屏幕上,虚拟屏幕会在 Surface 上呈现该图像。

应用通过 SurfaceViewImageReader 提供 Surface,两者都会使用所截取屏幕的内容。借助 ImageReaderOnImageAvailableListener,您可以实时管理 Surface 上呈现的图像。您可以将图像另存为录像,也可以将其投射到电视或其他设备上。

MediaProjection

通过获取令牌开始媒体投影会话,该令牌会赋予应用截取屏幕内容和/或设备音频的能力。该令牌由 MediaProjection 类的实例表示。您可以在启动新 activity 时创建此类的实例。

旧方法

如需使用旧方法获取媒体投影令牌,请使用从 MediaProjectionManager 系统服务的 createScreenCaptureIntent() 方法返回的 intent 调用 startActivityForResult()

Kotlin

startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(),
                       REQUEST_MEDIA_PROJECTION)

Java

startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(),
                       REQUEST_MEDIA_PROJECTION);

此调用会显示一个确认对话框,告知用户媒体投影会截取显示的所有信息,包括任何敏感信息或个人身份信息。

如果用户提供确认,startActivityForResult() 会将结果代码和数据传递给 onActivityResult() 回调。

然后,您可以将数据和结果代码传递给 MediaProjectionManager 中的 getMediaProjection() 方法,以创建 MediaProjection 实例:

Kotlin

mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData)

Java

mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, resultData);

建议使用 Jetpack Activity 库中的 API 来获取媒体投影令牌:

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<Intent> startMediaProjection = registerForActivityResult(
  new StartActivityForResult(),
  result -> {
    if (result.getResultCode() == Activity.RESULT_OK) {
      mediaProjection[0] = mediaProjectionManager
        .getMediaProjection(result.getResultCode(), result.getData());
    }
  }
);

虚拟屏幕

媒体投影的核心是虚拟屏幕,您可以通过对 MediaProjection 实例调用 createVirtualDisplay() 来创建虚拟屏幕:

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

widthheight 参数指定虚拟屏幕的宽度和高度。如需获取与媒体投影的宽度和高度匹配的值,请使用 Android 11(API 级别 30)中引入的 WindowMetrics API。

WindowMetrics

无论创建媒体投影的应用是在全屏模式下运行还是在多窗口模式下运行,媒体投影都会截取整个屏幕。

如需获取媒体投影的尺寸,请使用 WindowManager#getMaximumWindowMetrics(),此方法会返回完整屏幕的 WindowMetrics 对象,即使媒体投影应用处于多窗口模式,仅占据部分屏幕也是如此。

如需向下兼容到 API 级别 14,请使用 Jetpack WindowManager 库中的 WindowMetricsCalculator#computeMaximumWindowMetrics()

调用 WindowMetrics#getBounds() 以获取媒体投影的虚拟屏幕的正确宽度和高度(请参阅虚拟屏幕)。

请始终确保媒体投影应用可调整大小。可调整大小的应用支持设备配置更改和多窗口模式(请参阅多窗口支持)。

如果您的应用不可调整大小,它必须从窗口上下文中查询屏幕边界,并使用 WindowManager#getMaximumWindowMetrics() 检索应用可用的最大屏幕区域的 WindowMetrics

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

Surface

您应该调整媒体投影 Surface 的大小,以所需的分辨率生成输出。将投射到电视或计算机显示器的屏幕的尺寸调大(低分辨率),将录像的设备屏幕尺寸调小(高分辨率)。

从 Android 12L(API 级别 32)开始,当系统在 Surface 上呈现虚拟屏幕时,它会根据 Surface 的大小缩放虚拟屏幕,其过程类似于使用 ImageView 中的 centerInside 选项。

这种新的缩放方法可最大限度地增加 Surface 图像的大小,同时确保适当的宽高比,从而提升将屏幕投射到电视和其他大屏幕上的显示效果。

建议

为了获得最佳媒体投影效果,请遵循以下建议:

  • 使应用大小可调整。可调整大小的应用支持设备配置更改和多窗口模式(请参阅多窗口支持)。在应用清单中,设置 resizeableActivity="true"。 在 Android 7.0(API 级别 24)及更高版本中,此设置默认为 true。
  • 让您的应用支持横屏和竖屏方向,因为这两种屏幕方向在手机、平板电脑和可折叠设备类型中都很常见。
  • 使用 WindowManager#getMaximumWindowMetrics() 获取媒体投影的边界。如需向下兼容 API 级别 14,请使用 Jetpack WindowManager。(请参阅 WindowMetrics 部分。)
  • 如果您的应用不可调整大小,请从窗口上下文中获取媒体投影边界。(请参阅 WindowMetrics 部分。)

其他资源

如需详细了解媒体投影,请参阅录制视频和音频播放内容