媒體投影

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 引數,選擇停用應用程式畫面分享功能。

createScreenCaptureIntent(MediaProjectionConfig) 的呼叫 (帶有從對 createConfigForUserChoice() 的呼叫傳回的 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) 以上版本會顯示大型醒目狀態列方塊,提醒使用者任何正在進行的螢幕投影作業。使用者可以輕觸方塊,停止分享、投放或錄製螢幕畫面。此外,裝置螢幕鎖定時,螢幕投放功能也會自動停止。

圖 2. 用於分享螢幕畫面、投放內容和錄製內容的狀態列方塊。

如要測試媒體投影狀態列方塊的可用性,請啟動螢幕分享、投放或錄製功能。狀態列中應會顯示方塊。

如要確保應用程式在使用者與狀態列方塊互動或啟用螢幕鎖定畫面時停止投影時,會釋出資源並更新 UI,請執行下列操作:

  • 建立 MediaProjection.Callback 的例項。

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

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

其他資源

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