媒体投影

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

Android 14(API 级别 34)引入了应用屏幕共享功能,可让用户共享单个应用窗口,而非整个设备屏幕,而不受窗口模式影响。应用屏幕共享会从共享屏幕中排除状态栏、导航栏、通知和其他系统界面元素,即使使用应用屏幕共享来截取应用的屏幕也是如此。系统只会分享所选应用的内容。

应用屏幕共享可让用户运行多个应用,但仅限单个应用共享内容,从而确保用户隐私、提高用户工作效率以及增强多任务处理能力。

三种展示广告表现形式

媒体投影会捕获设备显示屏或应用窗口的内容,然后将拍摄的图片投影到虚拟屏幕上,该虚拟屏幕会在 Surface 上呈现该图片。

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

应用通过 MediaRecorderSurfaceTextureImageReader 提供 Surface,它使用所拍摄屏幕的内容,并使您能够实时管理在 Surface 上呈现的图片。您可以将图片另存为录制内容,也可以将它们投放到电视或其他设备上。

真实展示广告

通过获取一个令牌来启动媒体投影会话,该令牌会授权您的应用捕获设备显示屏或应用窗口的内容。该令牌由 MediaProjection 类的一个实例表示。

在启动新 activity 时,使用 MediaProjectionManager 系统服务的 getMediaProjection() 方法创建 MediaProjection 实例。使用 createScreenCaptureIntent() 方法中的 intent 启动 activity 以指定屏幕截取操作:

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

startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent());

虚拟屏幕

媒体投影的核心是虚拟屏幕,您可以通过对 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。(如需了解详情,请参阅媒体投影大小部分。)

Surface

调整媒体投影 Surface 的大小,以适当的分辨率生成输出。将 Surface 调大(低分辨率),用于投射到电视或计算机显示器的屏幕;将 Surface 设置为小(高分辨率)以录制设备显示屏。

从 Android 12L(API 级别 32)起,在 Surface 上渲染拍摄的内容时,系统会均匀缩放内容,同时保持宽高比不变,以便内容的两个尺寸(宽度和高度)均等于或小于 Surface 的相应尺寸。然后,捕获的内容会在界面上居中。

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

前台服务权限

如果您的应用以 Android 14 或更高版本为目标平台,应用清单必须包含针对 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>

通过调用 startForeground() 启动媒体投影服务。

如果您没有在调用中指定前台服务类型,则该类型默认为清单中定义的前台服务类型的按位整数。如果清单未指定任何服务类型,系统会抛出 MissingForegroundServiceTypeException

您的应用必须在每次媒体投影会话之前请求用户同意。一个会话是指对 createVirtualDisplay() 的单次调用。MediaProjection 令牌只能使用一次来进行调用。

在 Android 14 或更高版本中,如果您的应用会执行以下任一操作,createVirtualDisplay() 方法会抛出 SecurityException

  • createScreenCaptureIntent() 返回的 Intent 实例多次传递给 getMediaProjection()
  • 在同一个 MediaProjection 实例上多次调用 createVirtualDisplay()

媒体投影大小

无论窗口模式为何,媒体投影都可以捕获整个设备屏幕或应用窗口。

初始大小

使用全屏媒体投影时,您的应用必须确定设备屏幕尺寸。在应用屏幕共享中,除非用户选择拍摄区域,否则您的应用无法确定拍摄的屏幕的大小。因此,任何媒体投影的初始尺寸都是设备屏幕的尺寸。

使用平台 WindowManager getMaximumWindowMetrics() 方法针对设备屏幕返回 WindowMetrics 对象,即使媒体投影托管应用处于多窗口模式,仅占据部分屏幕也是如此。

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

调用 WindowMetrics getBounds() 方法,获取设备显示屏的宽度和高度。

大小变化

当设备旋转或用户选择应用窗口作为应用屏幕共享中的拍摄区域时,媒体投影的大小可能会发生变化。如果捕获的内容的大小与设置媒体投影时获取的最大窗口指标不同,则媒体投影可能为信箱模式。

为了确保媒体投影与所捕获内容的尺寸精确对齐,并且适用于任何捕获的区域和跨设备旋转,请使用 onCapturedContentResize() 回调来调整捕获的大小。(如需了解详情,请参阅后面的自定义部分)。

自定义

您的应用可以使用以下 MediaProjection.Callback API 自定义媒体投影用户体验:

  • onCapturedContentVisibilityChanged():让托管应用(即启动媒体投影的应用)能够显示或隐藏分享内容。

    使用此回调可以根据捕获的区域对用户是否可见自定义应用的界面。例如,如果您的应用对用户可见,并且在应用的界面中显示捕获的内容,并且捕获的应用对用户可见(如通过此回调指示),则用户会看到相同的内容两次。使用该回调更新应用的界面,以隐藏捕获的内容,并为其他内容释放应用中的布局空间。

  • onCapturedContentResize():使托管应用能够根据捕获的显示区域的大小更改虚拟屏幕和媒体投影 Surface 上的媒体投影大小。

    每当捕获的内容(单个应用窗口或整个设备屏幕)大小发生变化(由于设备旋转或捕获的应用进入不同的窗口模式)时触发。使用此 API 可同时调整虚拟屏幕和 Surface 的大小,以确保宽高比与捕获的内容匹配,并且捕获的内容不采用信箱模式。

资源恢复

您的应用应注册 MediaProjection onStop() 回调,以释放应用持有的资源,例如虚拟屏幕和投影界面。

当媒体投影终止或用户不同意继续拍摄会话时,系统会调用该回调函数。

如果您的应用未注册回调且用户不同意媒体投影会话,则对 createVirtualDisplay() 的调用会抛出 IllegalStateException

停止

Android 14 或更高版本会默认启用应用屏幕共享。每个媒体投影会话都为用户提供了共享应用窗口或整个显示屏的选项。

通过调用 createScreenCaptureIntent(MediaProjectionConfig) 方法并使用从 createConfigForDefaultDisplay() 调用返回的 MediaProjectionConfig 实参,应用可以选择停用应用屏幕共享。

使用 createConfigForUserChoice() 调用返回的 MediaProjectionConfig 参数调用 createScreenCaptureIntent(MediaProjectionConfig) 与默认行为相同,即调用 createScreenCaptureIntent()

可调整大小的应用

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

如果您的应用不可调整大小,则必须从窗口上下文中查询显示边界,并使用 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();

其他资源

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