Android 5 (API 수준 21)에 도입된 android.media.projection
API를 사용하면 기기 디스플레이의 콘텐츠를 미디어 스트림으로 캡처하여 재생하거나 녹화하거나 TV와 같은 다른 기기로 전송할 수 있습니다.
Android 14 (API 수준 34)에서는 사용자가 창 모드와 관계없이 전체 기기 화면 대신 단일 앱 창을 공유할 수 있는 앱 화면 공유를 도입했습니다. 앱 화면 공유는 앱 화면 공유를 사용하여 앱을 전체 화면으로 캡처하는 경우에도 상태 표시줄, 탐색 메뉴, 알림, 기타 시스템 UI 요소를 공유 디스플레이에서 제외합니다. 선택한 앱의 콘텐츠만 공유됩니다.
앱 화면 공유는 사용자가 여러 앱을 실행하면서 콘텐츠 공유를 단일 앱으로 제한할 수 있도록 하여 사용자 개인 정보를 보호하고, 사용자 생산성을 높이며, 멀티태스킹을 개선합니다.
디스플레이 표현 3가지
미디어 프로젝션은 기기 디스플레이 또는 앱 창의 콘텐츠를 캡처한 다음 캡처된 이미지를 Surface
에서 이미지를 렌더링하는 가상 디스플레이에 프로젝션합니다.
애플리케이션은 MediaRecorder
, SurfaceTexture
또는 ImageReader
를 통해 Surface
를 제공합니다. 이 객체는 캡처된 디스플레이의 콘텐츠를 사용하고 Surface
에서 렌더링된 이미지를 실시간으로 관리할 수 있도록 합니다. 이미지를 녹화 파일로 저장하거나 TV 또는 다른 기기로 전송할 수 있습니다.
실제 디스플레이
앱에 기기 디스플레이 또는 앱 창의 콘텐츠를 캡처하는 기능을 부여하는 토큰을 획득하여 미디어 프로젝션 세션을 시작합니다. 토큰은 MediaProjection
클래스의 인스턴스로 표시됩니다.
새 활동을 시작할 때 MediaProjectionManager
시스템 서비스의 getMediaProjection()
메서드를 사용하여 MediaProjection
인스턴스를 만듭니다. createScreenCaptureIntent()
메서드의 인텐트로 활동을 시작하여 화면 캡처 작업을 지정합니다.
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())
자바
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)
자바
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
적절한 해상도로 출력을 생성하려면 미디어 프로젝션 표면의 크기를 조절합니다. TV나 컴퓨터 모니터로 화면을 전송할 때는 노출 영역을 크게 (저해상도) 설정하고 기기 디스플레이 녹화에서는 노출 영역을 작게 (고해상도) 설정하세요.
Android 12L (API 수준 32)부터는 캡처된 콘텐츠를 노출 영역에서 렌더링할 때 시스템이 가로세로 비율을 유지하면서 콘텐츠를 균일하게 크기 조정하여 콘텐츠의 두 크기 (너비 및 높이)가 노출 영역의 상응하는 크기와 같거나 작도록 합니다. 그러면 캡처된 콘텐츠가 노출 영역의 중앙에 배치됩니다.
Android 12L 조정 방법을 통해 적절한 가로세로 비율을 보장하면서 노출 영역 이미지 크기를 최대화하여 TV와 기타 대형 디스플레이로의 화면 전송을 개선합니다.
포그라운드 서비스 권한
앱이 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()
: 호스트 앱 (미디어 프로젝션을 시작한 앱)이 공유 콘텐츠를 표시하거나 숨길 수 있도록 합니다.이 콜백을 사용하여 캡처된 영역이 사용자에게 표시되는지 여부에 따라 앱의 UI를 맞춤설정합니다. 예를 들어 앱이 사용자에게 표시되고 앱의 UI 내에 캡처된 콘텐츠가 표시되고 캡처된 앱도 사용자에게 표시되는 경우 (이 콜백을 통해 표시됨) 사용자에게 동일한 콘텐츠가 두 번 표시됩니다. 콜백을 사용하여 앱의 UI를 업데이트하여 캡처된 콘텐츠를 숨기고 앱에서 다른 콘텐츠를 위한 레이아웃 공간을 확보합니다.
onCapturedContentResize()
: 호스트 앱이 캡처된 디스플레이 영역의 크기를 기반으로 가상 디스플레이 및 미디어 프로젝션Surface
의 미디어 프로젝션 크기를 변경할 수 있도록 합니다.캡처된 콘텐츠(단일 앱 창 또는 전체 기기 디스플레이)의 크기가 변경될 때마다(기기 회전 또는 캡처된 앱이 다른 창 모드로 전환되어서) 트리거됩니다. 이 API를 사용하여 가상 디스플레이와 노출 영역의 크기를 모두 조절하여 가로세로 비율이 캡처된 콘텐츠와 일치하고 캡처가 레터박스 처리되지 않도록 합니다.
리소스 복구
앱은 미디어 프로젝션 세션이 중지되고 무효화될 때 알림을 받기 위해 MediaProjection
onStop()
콜백을 등록해야 합니다. 세션이 중지되면 앱은 가상 디스플레이 및 프로젝션 노출 영역과 같이 보유한 리소스를 해제해야 합니다. 중지된 미디어 프로젝션 세션은 앱이 이전에 해당 미디어 프로젝션의 가상 디스플레이를 만들지 않았더라도 더 이상 새 가상 디스플레이를 만들 수 없습니다.
미디어 프로젝션이 종료되면 시스템에서 콜백을 호출합니다. 계정 폐쇄는 다음과 같은 여러 가지 이유로 발생할 수 있습니다.
- 사용자가 앱의 UI 또는 시스템의 미디어 프로젝션 상태 표시줄 칩을 사용하여 세션을 중지합니다.
- 화면이 잠깁니다.
- 다른 미디어 프로젝션 세션이 시작됩니다.
- 앱 프로세스가 종료됩니다.
앱이 콜백을 등록하지 않으면 createVirtualDisplay()
호출 시 IllegalStateException
이 발생합니다.
선택 해제
Android 14 이상에서는 기본적으로 앱 화면 공유가 사용 설정됩니다. 각 미디어 프로젝션 세션에서 사용자는 앱 창 또는 전체 디스플레이를 공유할 수 있습니다.
앱은 createConfigForDefaultDisplay()
호출에서 반환된 MediaProjectionConfig
인수를 사용하여 createScreenCaptureIntent(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
자바
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 이상에서는 기기 화면이 잠기면 화면 프로젝션이 자동으로 중지됩니다.
추가 리소스
미디어 프로젝션에 관한 자세한 내용은 동영상 및 오디오 재생 캡처를 참고하세요.