媒體投影

Android 5 (API 級別 21) 中導入的 android.media.projection API 可讓您將裝置螢幕的內容擷取為媒體串流,以便播放、錄製或投放到電視等其他裝置。

Android 14 (API 級別 34) 推出應用程式分享螢幕畫面功能,讓使用者無論採用哪種視窗化模式,都能分享單一應用程式視窗,而非整個裝置畫面。使用應用程式分享螢幕畫面時,系統會從分享的畫面中排除狀態列、導覽列、通知和其他系統 UI 元素,即使是使用應用程式分享螢幕畫面功能擷取全螢幕應用程式也一樣。系統只會分享所選應用程式的內容。

應用程式分享螢幕畫面功能可確保使用者隱私、提升使用者工作效率,並讓使用者執行多個應用程式,但限制內容分享僅限單一應用程式,藉此強化多工處理能力。

三種顯示方式

媒體投影功能會擷取裝置螢幕或應用程式視窗的內容,然後將擷取的圖片投影到虛擬螢幕上,以在 Surface 上呈現圖片。

投影到虛擬螢幕的真實裝置螢幕。將虛擬螢幕的內容寫入應用程式提供的「途徑」。
圖 1. 投影到虛擬螢幕的真實裝置螢幕或應用程式視窗。將虛擬螢幕的內容寫入應用程式提供的 Surface

應用程式會透過 MediaRecorderSurfaceTextureImageReader 提供 Surface,這些方式都會取用擷取的螢幕內容,並讓您即時管理顯示在 Surface 上的圖片。您可以將圖片儲存為錄製內容,或是將圖片投放到電視或其他裝置。

實際螢幕

如要啟動媒體投影工作階段,請先取得權杖,該權杖可授予應用程式擷取裝置螢幕或應用程式視窗內容的權限。權杖由 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())

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

虛擬螢幕

媒體投影的核心部分是虛擬螢幕,您可以透過 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

調整媒體投影途徑的大小,以適當的解析度產生輸出內容。建議您在電視或電腦顯示器上投放畫面時,將螢幕調整為大尺寸 (低解析度);在進行裝置螢幕錄製時調整為小尺寸 (高解析度)。

自 Android 12L (API 級別 32) 起,在途徑上算繪擷取的內容時,系統會統一縮放內容並維持顯示比例,讓內容的寬度和高度都等於或小於途徑的對應尺寸。擷取的內容隨後會顯示在途徑中央。

Android 12L 的縮放方法能夠透過盡量放大途徑影像大小,同時確保適當的顯示比例,來改善電視和其他大螢幕的畫面投放功能。

前景服務權限

如果應用程式指定 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 以上版本預設會啟用應用程式分享螢幕畫面功能。在每個媒體投影工作階段中,使用者都可以選擇分享應用程式視窗或整個螢幕。

應用程式可以呼叫 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();

狀態列方塊和自動停止

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.

圖 2. 分享螢幕畫面、投放和錄製的狀態列資訊方塊。

如要測試媒體投影狀態列晶片的可用性,請開始分享螢幕、投放或錄製畫面。狀態列中應會顯示晶片。

為確保應用程式在使用者與狀態列晶片互動或啟動螢幕鎖定功能時,會釋出資源並更新 UI,請採取下列做法:

  • 建立 MediaProjection.Callback 的執行個體。

  • 實作回呼 onStop() 方法。螢幕投影停止時,系統會呼叫這個方法。釋出應用程式持有的所有資源,並視需要更新應用程式 UI。

如要測試回呼功能,請輕觸狀態列晶片或鎖定裝置螢幕,停止螢幕投影。確認系統會呼叫 onStop() 方法,且應用程式會如預期般回應。

其他資源

如要進一步瞭解媒體投影,請參閱「擷取影片和音訊播放」。