Поддержка многооконного режима

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

Рисунок 1. Отображение двух приложений рядом в режиме разделенного экрана.

Инструкции по использованию режима разделения экрана на телефонах см. в статье «Одновременный просмотр двух приложений на телефоне Pixel» .

Функции многооконного режима, специфичные для разных версий

Пользовательский интерфейс многооконного режима зависит от версии Android и типа устройства:

  • В Android 7.0 (уровень API 24) появился режим разделения экрана на устройствах с небольшим экраном и режим «картинка в картинке» на некоторых устройствах.

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

    • Режим «картинка в картинке» позволяет пользователям продолжать воспроизведение видео, взаимодействуя с другим приложением (см. Поддержка режима «картинка в картинке» ).

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

      Вы можете настроить работу приложения в многооконном режиме, указав минимально допустимые размеры окна Activity. Вы также можете отключить многооконный режим для своего приложения, установив resizeableActivity="false" чтобы система всегда отображала приложение в полноэкранном режиме.

  • Android 8.0 (уровень API 26) расширяет возможности режима «картинка в картинке» на устройства с небольшим экраном.

  • В Android 12 (уровень API 31) многооконный режим становится стандартным поведением.

    • На больших экранах ( средний или расширенный размер окна) платформа поддерживает все приложения в многооконном режиме независимо от их конфигурации. Если resizeableActivity="false" , приложение переходит в режим совместимости, когда это необходимо для соответствия размерам экрана.

    • На небольших экранах (класс размера окна Compact ) система проверяет minWidth и minHeight для Activity, чтобы определить, может ли она работать в многооконном режиме. Если resizeableActivity="false" , запуск приложения в многооконном режиме запрещен независимо от минимальной ширины и высоты.

  • Android 16 (уровень API 36) переопределяет ограничения по ориентации экрана, соотношению сторон и изменению размера.

    • На больших экранах (минимальная ширина >= 600dp) система игнорирует атрибуты манифеста и API среды выполнения, используемые для ограничения ориентации, соотношения сторон и возможности изменения размера приложения, оптимизируя пользовательский интерфейс на всех форм-факторах устройств.

      Чтобы узнать, как исключить игры из изменений Android 16, ознакомьтесь с разделом Исключения для ориентации приложений, соотношения сторон и изменения размера .

Режим разделенного экрана

Пользователи активируют режим разделенного экрана, выполнив следующие действия:

  1. Откройте экран «Недавние»
  2. Проведите пальцем по приложению, чтобы оно появилось на экране
  3. Нажмите значок приложения в строке заголовка приложения.
  4. Выберите опцию меню разделения экрана
  5. Выберите другое приложение на экране «Недавние» или закройте экран «Недавние» и запустите другое приложение.

Пользователи выходят из режима разделенного экрана, перетаскивая разделитель окна к краю экрана — вверх или вниз, влево или вправо.

Запуск рядом

Если вашему приложению необходимо получить доступ к контенту через намерение, вы можете использовать FLAG_ACTIVITY_LAUNCH_ADJACENT , чтобы открыть контент в соседнем окне с разделенным экраном.

FLAG_ACTIVITY_LAUNCH_ADJACENT был представлен в Android 7.0 (уровень API 24), чтобы позволить приложениям, работающим в режиме разделенного экрана, запускать действия в соседнем окне.

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

Чтобы запустить смежную активность, используйте FLAG_ACTIVITY_LAUNCH_ADJACENT вместе с FLAG_ACTIVITY_NEW_TASK , например:

fun openUrlInAdjacentWindow(url: String) {
    Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(url)
       addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT or Intent.FLAG_ACTIVITY_NEW_TASK)
    }.also { intent -> startActivity(intent) }
}

Жизненный цикл активности в многооконном режиме

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

Мульти-резюме

Android 10 (уровень API 29) и более поздние версии поддерживают функцию мультивозобновления — все активности остаются в состоянии RESUMED , когда устройство находится в многооконном режиме. Активность можно приостановить, если поверх неё находится прозрачная активность или она не может быть сфокусирована, например, если активность находится в режиме «картинка в картинке» . Также возможно, что в данный момент времени ни одна активность не находится в фокусе, например, если открыта панель уведомлений. Метод onStop() работает как обычно: он вызывается каждый раз, когда активность убирается с экрана.

Функция многократного возобновления также доступна на некоторых устройствах под управлением Android 9 (уровень API 28). Чтобы включить функцию многократного возобновления на устройствах Android 9, добавьте следующие метаданные в манифест:

<meta-data android:name="android.allow_multiple_resumed_activities" android:value="true" />

Чтобы проверить, поддерживает ли конкретное устройство эти метаданные манифеста, обратитесь к спецификациям устройства.

Андроид 9

В многооконном режиме на Android 9 (уровень API 28) и ниже в данный момент времени активна только та активность, с которой пользователь взаимодействовал последним. Эта активность считается самой верхней и единственной в состоянии RESUMED . Все остальные видимые активности находятся в STARTED , но не RESUMED . Однако система присваивает этим видимым, но не возобновленным активностьм более высокий приоритет, чем невидимым. Если пользователь взаимодействует с одной из видимых активностей, эта активность возобновляется, а предыдущая самая верхняя активность переходит в состояние STARTED .

Если в одном активном процессе приложения выполняется несколько действий, возобновляется действие с наивысшим z-порядком, а остальные приостанавливаются.

Изменения конфигурации

Когда пользователь переводит приложение в многооконный режим, система уведомляет об изменении конфигурации, как указано в разделе «Обработка изменений конфигурации» . То же самое происходит, когда пользователь изменяет размер окна приложения или возвращает его в полноэкранный режим.

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

Если пользователь изменяет размер окна и увеличивает его в любом из измерений, система изменяет размер Activity в соответствии с действием пользователя и при необходимости вносит изменения в конфигурацию. Если приложение отстаёт в отрисовке новых открытых областей, система временно заполняет эти области цветом, заданным атрибутом windowBackground или атрибутом стиля по умолчанию windowBackgroundFallback .

Эксклюзивный доступ к ресурсам

Для поддержки функции многократного возобновления используйте обратный вызов жизненного цикла onTopResumedActivityChanged() .

Обратный вызов вызывается, когда активность получает или теряет верхнюю возобновленную позицию активности, что важно, когда активность использует общий одиночный ресурс, такой как микрофон или камера:

override fun onTopResumedActivityChanged(topResumed: Boolean) {
    if (topResumed) {
        // Top resumed activity.
        // Can be a signal to re-acquire exclusive resources.
    } else {
        // No longer the top resumed activity.
    }
}

Обратите внимание, что приложение может потерять ресурсы по другим причинам, например, из-за удаления общего оборудования.

В любом случае приложение должно корректно обрабатывать события и изменения состояния, которые влияют на доступные ресурсы.

Для приложений, использующих камеру, CameraManager.AvailabilityCallback#onCameraAccessPrioritiesChanged() даёт подсказку о том, что сейчас самое время попытаться получить доступ к камере. Этот метод доступен начиная с Android 10 (API уровня 29).

Помните, что resizeableActivity=false не гарантирует эксклюзивный доступ к камере, поскольку другие приложения, использующие камеру, могут быть открыты на других дисплеях.

Рисунок 2. Камера в многооконном режиме.

Ваше приложение не обязательно должно отключать камеру при потере фокуса. Например, вам может потребоваться продолжить предварительный просмотр камеры, пока пользователь взаимодействует с только что сфокусированным верхним возобновленным приложением. Приложение может продолжать использовать камеру, даже если оно не является верхним возобновленным приложением, но оно должно корректно обрабатывать ситуацию отключения. Когда верхнее возобновленное приложение хочет использовать камеру, оно может открыть её, и ваше приложение потеряет доступ. Ваше приложение может снова открыть камеру, когда снова получит фокус.

После того как приложение получит обратный вызов CameraDevice.StateCallback#onDisconnected() , последующие вызовы на устройстве камеры будут вызывать исключение CameraAccessException .

Метрики окна

В Android 11 (уровень API 30) появились следующие методы WindowManager для задания границ приложений, работающих в многооконном режиме:

Методы библиотеки Jetpack WindowManager computeCurrentWindowMetrics() и computeMaximumWindowMetrics() предлагают аналогичную функциональность соответственно, но с обратной совместимостью с API уровня 14.

Чтобы получить метрики для дисплеев, отличных от текущего, выполните следующие действия (как показано во фрагменте кода):

  • Создать контекст отображения
  • Создать контекст окна для отображения
  • Получить WindowManager контекста окна
  • Получите WindowMetrics максимальной области отображения, доступной приложению.

val windowMetrics = context.createDisplayContext(display)
                           .createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION, null)
                           .getSystemService(WindowManager::class.java)
                           .maximumWindowMetrics

Устаревшие методы

Методы Display getSize() и getMetrics() устарели в API уровня 30 в пользу новых методов WindowManager .

В Android 12 (уровень API 31) отменены методы Display getRealSize() и getRealMetrics() , а их поведение обновлено для большего соответствия поведению getMaximumWindowMetrics() .

Конфигурация многооконного режима

Если ваше приложение предназначено для Android 7.0 (уровень API 24) или выше, вы можете настроить, поддерживают ли ваши активности многооконный режим и как они это делают. Вы можете задать атрибуты в манифесте для управления размером и компоновкой. Настройки атрибутов корневой активности применяются ко всем активностям в её стеке задач. Например, если у корневой активности есть android:resizeableActivity="true" , то все активности в стеке задач можно изменять. На некоторых устройствах большего размера, таких как Chromebook, ваше приложение может работать в окне с изменяемым размером, даже если вы укажете android:resizeableActivity="false" . Если это нарушает работу вашего приложения, вы можете использовать фильтры в Google Play , чтобы ограничить доступность приложения на таких устройствах.

В Android 12 (уровень API 31) по умолчанию используется многооконный режим. На больших экранах ( средний или расширенный размер окна) все приложения работают в многооконном режиме независимо от их конфигурации. На маленьких экранах система проверяет параметры minWidth , minHeight и resizeableActivity активности, чтобы определить, может ли активность работать в многооконном режиме.

resizeableActivity

Установите этот атрибут в элементе <activity> или <application> вашего манифеста, чтобы включить или отключить многооконный режим для API уровня 30 и ниже:

<application
  android:name=".MyActivity"
  android:resizeableActivity=["true" | "false"] />;

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

Если ваше приложение ориентировано на API уровня 24 или выше, но вы не указали значение для этого атрибута, значение атрибута по умолчанию будет равно true.

Если ваше приложение ориентировано на API уровня 31 или выше, этот атрибут работает по-разному на маленьких и больших экранах:

  • Большие экраны ( средний или расширенный класс размера окна): все приложения поддерживают многооконный режим. Этот атрибут указывает, можно ли изменять размер окна. Если resizeableActivity="false" , приложение переходит в режим совместимости, когда это необходимо для соответствия размерам экрана.
  • Маленькие экраны (класс размера окна Compact ): если resizeableActivity="true" и минимальная ширина и минимальная высота Activity соответствуют требованиям к многооконному режиму, Activity поддерживает многооконный режим. Если resizeableActivity="false" , Activity не поддерживает многооконный режим независимо от минимальной ширины и высоты Activity.

Если ваше приложение ориентировано на API уровня 36 или выше, этот атрибут игнорируется на дисплеях с минимальной шириной >= 600 dp. Однако приложение полностью учитывает выбранное пользователем соотношение сторон (см. раздел «Переопределения для каждого приложения» ).

Если вы создаете игру, ознакомьтесь со статьей Ориентация приложения, соотношение сторон и возможность изменения размера, чтобы узнать, как исключить свою игру из изменений Android 16 (API уровня 36).

supportsPictureInPicture

Установите этот атрибут в узле <activity> вашего манифеста, чтобы указать, поддерживает ли активность режим «картинка в картинке».

<activity
  android:name=".MyActivity"
  android:supportsPictureInPicture=["true" | "false"] />

configChanges

Чтобы самостоятельно обрабатывать изменения конфигурации нескольких окон, например, когда пользователь изменяет размер окна, добавьте атрибут android:configChanges в узел <activity> манифеста вашего приложения как минимум со следующими значениями:

<activity
  android:name=".MyActivity"
  android:configChanges="screenSize | smallestScreenSize
      | screenLayout | orientation" />

После добавления android:configChanges ваши Activity и фрагменты получат обратный вызов onConfigurationChanged() вместо того, чтобы быть уничтоженными и заново созданными. После этого вы сможете вручную обновлять представления, перезагружать ресурсы и выполнять другие необходимые операции.

<layout>

В Android 7.0 (уровень API 24) и выше элемент манифеста <layout> поддерживает несколько атрибутов, которые влияют на поведение активности в многооконном режиме:

  • android:defaultHeight , android:defaultWidth : высота и ширина активности по умолчанию при запуске в оконном режиме рабочего стола.

  • android:gravity : начальное положение активности при запуске в оконном режиме рабочего стола. Подходящие значения см. в описании класса Gravity .

  • android:minHeight , android:minWidth : минимальная высота и минимальная ширина для активности как в режиме разделённого экрана, так и в режиме рабочего стола. Если пользователь перемещает разделитель в режиме разделённого экрана, чтобы уменьшить размер активности ниже заданного минимума, система обрезает активность до указанного пользователем размера.

В следующем коде показано, как указать размер и местоположение действия по умолчанию, а также его минимальный размер, когда действие отображается в оконном режиме рабочего стола:

<activity android:name=".MyActivity">
    <layout android:defaultHeight="500dp"
          android:defaultWidth="600dp"
          android:gravity="top|end|..."
          android:minHeight="450dp"
          android:minWidth="300dp" />
</activity>

Многооконный режим во время выполнения

Начиная с Android 7.0, система предлагает функционал для поддержки приложений, которые могут работать в многооконном режиме.

Отключенные функции в многооконном режиме

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

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

Система игнорирует изменения атрибута android:screenOrientation .

Запросы и обратные вызовы в многооконном режиме

Класс Activity предлагает следующие методы для поддержки многооконного режима:

  • isInMultiWindowMode() : указывает, находится ли действие в многооконном режиме.

  • isInPictureInPictureMode() : указывает, находится ли действие в режиме «картинка в картинке».

  • onMultiWindowModeChanged() : система вызывает этот метод каждый раз, когда активность переходит в многооконный режим или выходит из него. Система передаёт методу значение true, если активность переходит в многооконный режим, или false, если активность выходит из многооконного режима.

  • onPictureInPictureModeChanged() : система вызывает этот метод каждый раз, когда действие переходит в режим «картинка в картинке» или выходит из него. Система передаёт методу значение true, если действие переходит в режим «картинка в картинке», или значение false, если действие выходит из этого режима.

Класс Fragment предоставляет версии многих из этих методов, например, Fragment.onMultiWindowModeChanged() .

Режим «картинка в картинке»

Чтобы перевести действие в режим «картинка в картинке», вызовите метод enterPictureInPictureMode() Этот метод не работает, если устройство не поддерживает режим «картинка в картинке». Подробнее см. в разделе Добавление видео с помощью режима «картинка в картинке» (PiP) .

Новые действия в многооконном режиме

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

Если устройство находится в режиме окон рабочего стола и вы запускаете новое действие, вы можете указать размеры и местоположение нового действия на экране, вызвав метод ActivityOptions.setLaunchBounds() . Этот метод не действует, если устройство не находится в многооконном режиме.

На уровне API 30 и ниже при запуске активности в стеке задач эта активность заменяет активность на экране, наследуя все её свойства многооконного режима. Если вы хотите запустить новую активность в отдельном окне в многооконном режиме, необходимо запустить её в новом стеке задач.

Android 12 (уровень API 31) позволяет приложениям разделять окно задач между несколькими активностями. Вы определяете, как приложение отображает активности — на весь экран, рядом или стопкой, — создавая XML-файл конфигурации или выполняя вызовы API Jetpack WindowManager.

Перетаскивание

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

Мультиэкземпляр

У каждой корневой активности есть своя задача, которая отображается в отдельном окне. Чтобы запустить новый экземпляр приложения в отдельном окне, используйте флаг FLAG_ACTIVITY_NEW_TASK . Этот параметр можно комбинировать с атрибутами многооконности, чтобы указать конкретное местоположение нового окна. Например, приложение для покупок может отображать несколько смежных окон для сравнения товаров.

Android 12 (уровень API 31) и выше позволяет запускать два экземпляра активности рядом в одном окне задач при внедрении активности .

Если вы хотите разрешить пользователям запускать другой экземпляр вашего приложения из панели запуска приложений или панели задач, установите android:resizeableActivity="true" в манифесте активности вашей панели запуска и не используйте режим запуска , который предотвращает запуск нескольких экземпляров. Например, активность singleInstancePerTask может быть создана несколько раз в разных задачах, если установлен FLAG_ACTIVITY_MULTIPLE_TASK или FLAG_ACTIVITY_NEW_DOCUMENT .

В Android 15 (уровень API 35) и выше свойство PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI позволяет объявить поддержку многоэкземплярности. Это свойство является явным сигналом для системного пользовательского интерфейса, чтобы предоставить пользователю элементы управления для создания нескольких экземпляров приложения. Свойство не зависит от режима запуска, но должно использоваться только в том случае, если режим запуска активности или приложения совместим с этим свойством, например, если режим запуска не singleInstance .

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

Проверка в многооконном режиме

Независимо от того, ориентировано ли ваше приложение на API уровня 24 или выше, вам следует проверить, как оно ведет себя в многооконном режиме, на случай, если пользователь попытается запустить его в многооконном режиме на устройстве под управлением Android 7.0 или выше.

Тестовые устройства

Устройства под управлением Android 7.0 (уровень API 24) или выше поддерживают многооконный режим.

Уровень API 23 или ниже

Когда пользователи пытаются использовать приложение в многооконном режиме, система принудительно изменяет размер приложения, если приложение не объявляет фиксированную ориентацию.

Если ваше приложение не заявляет фиксированную ориентацию, запустите его на устройстве под управлением Android 7.0 или более поздней версии и попробуйте перевести приложение в режим разделённого экрана. Убедитесь, что пользовательский интерфейс приемлем при принудительном изменении размера приложения.

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

Уровни API 24–30

Если ваше приложение ориентировано на уровни API 24–30 и не отключает поддержку многооконного режима, проверьте следующее поведение как в режиме разделенного экрана, так и в режиме окон рабочего стола:

  • Запустите приложение в полноэкранном режиме, затем переключитесь в многооконный режим, удерживая кнопку « Недавние» . Убедитесь, что приложение переключается корректно.

  • Запустите приложение в многооконном режиме и убедитесь, что оно работает корректно. Вы можете запустить приложение в многооконном режиме, нажав кнопку « Недавние» , затем удерживая заголовок приложения и перетаскивая его в одну из выделенных областей экрана.

  • Измените размер приложения в режиме разделённого экрана, перетаскивая разделитель экрана. Убедитесь, что приложение меняет размер без сбоев и что необходимые элементы интерфейса видны.

  • Если вы указали минимальные размеры для своего приложения, попробуйте изменить размер окна приложения так, чтобы оно было меньше этих размеров. Убедитесь, что размер окна приложения не может быть меньше указанных минимальных размеров.

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

Уровень API 31 или выше

Если ваше приложение ориентировано на API уровня 31 или выше, а минимальная ширина и минимальная высота основного действия меньше или равны соответствующим размерам доступной области отображения, проверьте все поведения, перечисленные для API уровней 24–30 .

Контрольный список тестов

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

  • Вход и выход из многооконного режима.

  • Переключитесь с одного приложения на другое и убедитесь, что приложение работает корректно, пока оно видимо, но неактивно. Например, если ваше приложение воспроизводит видео, убедитесь, что видео продолжает воспроизводиться, пока пользователь взаимодействует с другим приложением.

  • В режиме разделённого экрана попробуйте переместить разделитель экрана, чтобы сделать приложение одновременно больше и меньше. Попробуйте выполнить эти действия как в конфигурации «рядом», так и «одно над другим». Убедитесь, что приложение не зависает, основные функции отображаются, а изменение размера не занимает слишком много времени.

  • Выполните несколько операций по изменению размера быстро друг за другом. Убедитесь, что приложение не падает и не допускает утечек памяти. Профилировщик памяти Android Studio предоставляет информацию об использовании памяти вашим приложением (см. раздел Проверка использования памяти приложением с помощью профилировщика памяти ).

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

Поддержка многооконного режима отключена

Если на уровнях API 24–30 вы отключили поддержку многооконного режима, установив android:resizeableActivity="false" , запустите приложение на устройстве под управлением Android версии от 7.0 до 11 и попробуйте перевести приложение в режимы разделения экрана и рабочего стола. Убедитесь, что при этом приложение остаётся в полноэкранном режиме.

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

Дополнительную информацию о поддержке многооконного режима в Android см. здесь: