媒體投影

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

Android 14 (API 級別 34) 導入了應用程式分享螢幕畫面功能,無論視窗模式為何,使用者都能共用單一應用程式視窗,而非整個裝置螢幕。即使應用程式螢幕畫面分享功能用於以全螢幕模式擷取應用程式,應用程式分享螢幕畫面功能也會從共用螢幕上排除狀態列、導覽列、通知和其他系統 UI 元素。系統只會分享所選應用程式的內容。

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

三種顯示方式

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

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

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

Real Display

取得權杖,允許應用程式擷取裝置螢幕或應用程式視窗的內容,以啟動媒體投影工作階段。權杖會以 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<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。(詳情請參閱「媒體投影大小」一節)。

途徑

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

自 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() 回呼,釋出應用程式保留的資源,例如虛擬螢幕和投影途徑。

當媒體投影終止,或使用者未同意繼續擷取工作階段時,系統會呼叫回呼。

如果應用程式未註冊回呼,且使用者不同意媒體投影工作階段,對 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();

其他資源

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