借助 Android 5(API 级别 21)中引入的 android.media.projection
API,您可以将设备屏幕中的内容截取为可播放、录制或投屏到其他设备(如电视)的媒体流。
Android 14(API 级别 34)引入了应用屏幕共享功能,让用户能够分享单个应用窗口(而非整个设备屏幕),无论窗口模式如何。应用屏幕共享功能会从共享显示屏中排除状态栏、导航栏、通知和其他系统界面元素,即使应用屏幕共享功能用于全屏截取应用也是如此。系统只会分享所选应用的内容。
应用屏幕共享功能可让用户运行多个应用,但仅限于与单个应用共享内容,从而确保用户隐私、提高用户工作效率并增强多任务处理能力。
三种显示表示法
媒体投影会截取设备显示屏或应用窗口中的内容,然后将截取的图像投影到虚拟屏幕上,虚拟屏幕会在 Surface
上呈现该图像。
应用通过 MediaRecorder
、SurfaceTexture
或 ImageReader
提供 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);
width
和 height
参数指定虚拟屏幕的尺寸。如需获取宽度和高度的值,请使用 Android 11(API 级别 30)中引入的 WindowMetrics
API。(如需了解详情,请参阅媒体投影大小部分。)
Surface
调整媒体投影 Surface 的大小,以适当的分辨率生成输出。将 Surface 的尺寸调大(低分辨率),以便将屏幕投放到电视或计算机显示器;将 Surface 的尺寸调小(高分辨率),以便录制设备显示屏。
从 Android 12L(API 级别 32)开始,当系统在 Surface 上呈现捕获的内容时,会均匀缩放内容,保持宽高比,以便内容的两个尺寸(宽度和高度)等于或小于 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()
回调,以便在媒体投影会话停止并失效时收到通知。当会话停止时,您的应用应释放其持有的资源,例如虚拟显示屏和投影 surface。已停止的媒体投影会话无法再创建新的虚拟显示屏,即使您的应用之前未为该媒体投影创建虚拟显示屏也是如此。
系统会在媒体投影终止时调用回调。导致这种终止的原因有很多,例如:
- 用户使用应用的界面或系统的媒体投放状态栏条状标签停止会话
- 屏幕正在锁定
- 开始另一个媒体投影会话
- 应用进程被终止
如果您的应用未注册回调,对 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();
状态栏条状标签和自动停止
屏幕投影漏洞会泄露用户的私密数据(例如财务信息),因为用户不知道自己的设备屏幕正在共享。
Android 15(API 级别 35)QPR1 引入了一个大而醒目的新状态栏条状标签,该标签应会提醒用户任何正在进行的屏幕投影。用户可以点按该条状标签,停止共享、投放或录制其屏幕。
在 Android 15 QPR1 及更高版本中,当设备屏幕锁定时,屏幕投影会自动停止。
其他资源
如需详细了解媒体投影,请参阅录制视频和音频播放内容。