Медиа-проекция

API android.media.projection представленные в Android 5 (уровень API 21), позволяют захватывать содержимое дисплея устройства в виде медиапотока, который можно воспроизводить, записывать или транслировать на другие устройства, например телевизоры.

В Android 14 (уровень API 34) реализован общий доступ к экрану приложения, который позволяет пользователям совместно использовать одно окно приложения, а не весь экран устройства, независимо от оконного режима. Совместное использование экрана приложения исключает строку состояния, панель навигации, уведомления и другие элементы системного пользовательского интерфейса из общего дисплея, даже если общий доступ к экрану приложения используется для захвата приложения в полноэкранном режиме. Доступен только контент выбранного приложения.

Совместное использование экрана приложений обеспечивает конфиденциальность пользователей, повышает производительность пользователей и улучшает многозадачность, позволяя пользователям запускать несколько приложений, но ограничивая общий доступ к контенту только одним приложением.

Три представления дисплея

Медиа-проекция захватывает содержимое дисплея устройства или окна приложения, а затем проецирует захваченное изображение на виртуальный дисплей, который отображает изображение на Surface .

Реальный дисплей устройства проецируется на виртуальный дисплей. Содержание               виртуальный дисплей, записанный на предоставленную приложением поверхность.
Рис. 1. Реальный экран устройства или окно приложения, проецируемое на виртуальный дисплей. Виртуальный дисплей, записанный на Surface , предоставляемую приложением.

Приложение предоставляет Surface с помощью MediaRecorder , SurfaceTexture или ImageReader , который использует содержимое захваченного дисплея и позволяет управлять изображениями, отображаемыми на Surface в режиме реального времени. Вы можете сохранить изображения в виде записи или транслировать их на телевизор или другое устройство.

Реальный дисплей

Начните сеанс медиапроекции, получив токен, который предоставит вашему приложению возможность захватывать содержимое дисплея устройства или окна приложения. Токен представлен экземпляром класса MediaProjection .

Используйте метод getMediaProjection() системной службы MediaProjectionManager , чтобы создать экземпляр MediaProjection при запуске нового действия. Запустите действие с намерением метода createScreenCaptureIntent() чтобы указать операцию захвата экрана:

Котлин

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

Виртуальный дисплей

Центральным элементом медиапроекции является виртуальный дисплей, который вы создаете, вызывая createVirtualDisplay() в экземпляре MediaProjection :

Котлин

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 определяют размеры виртуального дисплея. Чтобы получить значения ширины и высоты, используйте API-интерфейсы WindowMetrics представленные в Android 11 (уровень API 30). (Подробную информацию см. в разделе «Размер медиапроекции» .)

Поверхность

Измените размер проекционной поверхности мультимедиа, чтобы обеспечить вывод в соответствующем разрешении. Сделайте поверхность большой (низкое разрешение) для трансляции экрана на телевизоры или компьютерные мониторы и маленькой (высокое разрешение) для записи с дисплея устройства.

Начиная с 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 , если ваше приложение выполняет одно из следующих действий:

  • Передает экземпляр Intent , возвращенный из createScreenCaptureIntent() в getMediaProjection() более одного раза.
  • Вызывает createVirtualDisplay() более одного раза в одном и том же экземпляре MediaProjection .

Размер медиа-проекции

Медиа-проекция может захватывать весь дисплей устройства или окно приложения независимо от оконного режима.

Начальный размер

При полноэкранной проекции мультимедиа ваше приложение должно определять размер экрана устройства. При совместном использовании экрана приложения ваше приложение не сможет определить размер захваченного дисплея, пока пользователь не выберет область захвата. Итак, первоначальный размер любой медиапроекции — это размер экрана устройства.

Используйте метод getMaximumWindowMetrics() платформы WindowManager , чтобы вернуть объект WindowMetrics для экрана устройства, даже если ведущее приложение медиапроекции находится в многооконном режиме и занимает только часть дисплея.

Для совместимости до уровня API 14 используйте метод WindowMetricsCalculator computeMaximumWindowMetrics() из библиотеки Jetpack WindowManager .

Вызовите метод WindowMetrics getBounds() чтобы получить ширину и высоту дисплея устройства.

Изменения размера

Размер проекции мультимедиа может измениться, когда устройство поворачивается или пользователь выбирает окно приложения в качестве области захвата при совместном использовании экрана приложения. Медиа-проекция может иметь почтовый ящик, если размер захваченного контента отличается от максимальных показателей окна, полученных при настройке медиа-проекции.

Чтобы гарантировать, что проекция мультимедиа точно соответствует размеру захваченного контента для любой захваченной области и для всех поворотов устройства, используйте обратный вызов onCapturedContentResize() чтобы изменить размер захвата. (Для получения дополнительной информации см. раздел «Настройка» ниже).

Кастомизация

Ваше приложение может настроить пользовательский интерфейс медиапроекции с помощью следующих API MediaProjection.Callback :

  • onCapturedContentVisibilityChanged() : позволяет ведущему приложению (приложению, которое запустило проекцию мультимедиа) показывать или скрывать общий контент.

    Используйте этот обратный вызов, чтобы настроить пользовательский интерфейс вашего приложения в зависимости от того, видна ли захваченная область пользователю. Например, если ваше приложение видимо для пользователя и отображает захваченное содержимое в пользовательском интерфейсе приложения, а захваченное приложение также видно пользователю (как указано в этом обратном вызове), пользователь видит одно и то же содержимое дважды. Используйте обратный вызов, чтобы обновить пользовательский интерфейс вашего приложения, чтобы скрыть захваченный контент и освободить место макета в вашем приложении для другого контента.

  • onCapturedContentResize() : позволяет ведущему приложению изменять размер проекции мультимедиа на виртуальном дисплее и Surface проекции мультимедиа в зависимости от размера захваченной области отображения.

    Срабатывает всякий раз, когда захваченный контент — одно окно приложения или полное отображение устройства — меняет размер (из-за поворота устройства или перехода захваченного приложения в другой оконный режим). Используйте этот API, чтобы изменить размер виртуального дисплея и поверхности, чтобы соотношение сторон соответствовало захваченному содержимому, а захват не был отправлен в почтовый ящик.

Восстановление ресурсов

Ваше приложение должно зарегистрировать обратный вызов MediaProjection onStop() чтобы получать информацию, когда сеанс медиапроекции остановлен и становится недействительным. Когда сеанс остановлен, ваше приложение должно освободить имеющиеся у него ресурсы, такие как виртуальный дисплей и проекционная поверхность. Остановленный сеанс медиапроекции больше не может создавать новый виртуальный дисплей, даже если ваше приложение ранее не создавало виртуальный дисплей для этой медиапроекции.

Система вызывает обратный вызов, когда медиапроекция завершается. Такое прекращение может произойти по нескольким причинам, например:

Если ваше приложение не регистрирует обратный вызов, любой вызов createVirtualDisplay() выдает IllegalStateException .

Уклоняться

Android 14 или более поздней версии по умолчанию включает совместное использование экрана приложения. Каждый сеанс медиапроекции дает пользователям возможность поделиться окном приложения или всем дисплеем.

Ваше приложение может отказаться от совместного использования экрана приложения, вызвав метод createScreenCaptureIntent(MediaProjectionConfig) с аргументом MediaProjectionConfig возвращаемым в результате вызова метода createConfigForDefaultDisplay() .

Вызов createScreenCaptureIntent(MediaProjectionConfig) с аргументом MediaProjectionConfig возвращенным в результате вызова createConfigForUserChoice() аналогичен поведению по умолчанию, то есть вызову createScreenCaptureIntent() .

Приложения с изменяемым размером

Всегда делайте приложения для медиапроекции изменяемого размера ( resizeableActivity="true" ). Приложения с изменяемым размером поддерживают изменение конфигурации устройства и многооконный режим (см. Поддержка многооконного режима ).

Если размер вашего приложения не подлежит изменению, оно должно запросить границы отображения из контекста окна и использовать getMaximumWindowMetrics() для получения WindowMetrics максимальной области отображения, доступной приложению:

Котлин

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 и более поздних версиях проецирование экрана автоматически прекращается, когда экран устройства заблокирован.

Рис. 2. Чип строки состояния для совместного использования экрана, трансляции экрана и записи экрана.

Дополнительные ресурсы

Дополнительные сведения о проецировании мультимедиа см. в разделе Захват видео и воспроизведение звука .