Ниже приведены изменения, внесенные в эти области, относящиеся к конкретному дисплею:
- Изменение размеров мероприятий и экспозиций.
- Размеры и соотношение сторон дисплея
- Правила отображения
- Настройки окна отображения
- Статические идентификаторы отображения
- Использование более чем двух дисплеев
- Фокусировка на каждом дисплее
Изменение размера элементов интерфейса и экранов.
Чтобы указать, что приложение может не поддерживать многооконный режим или изменение размера, для действий используется атрибут resizeableActivity=false . К распространенным проблемам, возникающим в приложениях при изменении размера действий, относятся:
- Конфигурация активности может отличаться от конфигурации приложения или другого невизуального компонента. Распространенная ошибка — считывание метрик отображения из контекста приложения. Возвращаемые значения не будут скорректированы в соответствии с метриками видимой области, в которой отображается активность.
- Активность может не обрабатывать изменение размера и привести к сбою, отображать искаженный пользовательский интерфейс или терять состояние при повторном запуске без сохранения состояния экземпляра.
- Приложение может попытаться использовать абсолютные координаты ввода (вместо координат, относительных к положению окна), что может привести к ошибкам ввода в многооконном режиме.
В Android 7 (и более поздних версиях) для приложения можно установить параметр resizeableActivity=false , чтобы оно всегда работало в полноэкранном режиме. В этом случае платформа предотвращает переход неизменяемых по размеру активностей в режим разделенного экрана. Если пользователь попытается запустить неизменяемую по размеру активность из лаунчера, находясь уже в режиме разделенного экрана, платформа выйдет из режима разделенного экрана и запустит неизменяемую по размеру активность в полноэкранном режиме.
Приложения, в манифесте которых этот атрибут явно установлен в значение false не должны запускаться в многооконном режиме, если не применен режим совместимости:
- Та же конфигурация применяется ко всему процессу, который включает в себя все виды деятельности и компоненты, не связанные с деятельностью.
- Примененная конфигурация соответствует требованиям CDD для дисплеев, совместимых с приложениями.
В Android 10 платформа по-прежнему не позволяет неизменяемым по размеру приложениям переходить в режим разделенного экрана, но их можно временно масштабировать, если для приложения задана фиксированная ориентация или соотношение сторон. В противном случае приложение изменяет свой размер, чтобы заполнить весь экран, как в Android 9 и более ранних версиях.
В реализации по умолчанию применяется следующая политика:
Если активность объявлена несовместимой с многооконным режимом с помощью атрибута android:resizeableActivity и если эта активность соответствует одному из условий, описанных ниже, то при необходимости изменения применяемой конфигурации экрана активность и процесс сохраняются с исходной конфигурацией, и пользователю предоставляется возможность перезапустить процесс приложения для использования обновленной конфигурации экрана.
- Фиксированная ориентация достигается с помощью функции
android:screenOrientation - Приложение задает максимальное или минимальное соотношение сторон по умолчанию на уровне API или явно указывает его.
На этом рисунке отображается неизменяемое по размеру окно с заданным соотношением сторон. При складывании устройства окно масштабируется до размера области отображения, сохраняя при этом соотношение сторон с помощью соответствующих черных полос по краям. Кроме того, пользователю предоставляется возможность перезапуска окна каждый раз, когда изменяется область отображения окна.
При раскладывании устройства конфигурация, размер и соотношение сторон активности не изменяются, но отображается возможность перезапустить активность.
Если resizeableActivity не задан (или задано значение true ), приложение полностью поддерживает изменение размера.
Выполнение
В коде активность, не изменяющая размер и имеющая фиксированную ориентацию или соотношение сторон, называется режимом совместимости размеров (SCM). Условие определяется в ActivityRecord#shouldUseSizeCompatMode() . При запуске активности в режиме SCM конфигурация, связанная с экраном (например, размер или плотность), фиксируется в запрошенной конфигурации переопределения, поэтому активность больше не зависит от текущей конфигурации дисплея.
Если активность SCM не может заполнить весь экран, она выравнивается по верхнему краю и центрируется по горизонтали. Границы активности вычисляются с помощью AppWindowToken#calculateCompatBoundsTransformation() .
Когда действие SCM использует другую конфигурацию экрана, чем его контейнер (например, изменяется размер дисплея или действие перемещается на другой дисплей), ActivityRecord#inSizeCompatMode() возвращает true, и SizeCompatModeActivityController (в системном пользовательском интерфейсе) получает обратный вызов для отображения кнопки перезапуска процесса.
Размеры и соотношение сторон дисплея
Android 10 поддерживает новые соотношения сторон, от высоких соотношений для длинных и узких экранов до соотношения 1:1. Приложения могут определять ApplicationInfo#maxAspectRatio и ApplicationInfo#minAspectRatio экрана, который они могут обрабатывать.

Рисунок 1. Примеры соотношений сторон приложения, поддерживаемых в Android 10.
В устройствах могут быть установлены дополнительные дисплеи с размерами и разрешением меньше, чем требуется Android 9, и ниже (минимум 2,5 дюйма в ширину или высоту, минимум 320 DP для smallestScreenWidth ), но на них можно размещать только те приложения, которые поддерживают эти небольшие дисплеи.
Приложения могут включить эту функцию, указав минимальный поддерживаемый размер, который меньше или равен целевому размеру дисплея. Для этого используйте атрибуты макета активности android:minHeight и android:minWidth в AndroidManifest.
Правила отображения
В Android 10 некоторые политики отображения перенесены из реализации WindowManagerPolicy по умолчанию в PhoneWindowManager в классы, предназначенные для каждого дисплея, например:
- Состояние отображения и вращение
- Некоторые клавиши и отслеживание событий движения
- Системный пользовательский интерфейс и окна оформления
В Android 9 (и более ранних версиях) класс PhoneWindowManager отвечал за политики отображения, состояние и настройки, поворот экрана, отслеживание рамки окна оформления и многое другое. В Android 10 большая часть этих функций перенесена в класс DisplayPolicy , за исключением отслеживания поворота экрана, которое перенесено в DisplayRotation .
Настройки окна отображения
В Android 10 расширены возможности настройки оконного режима для каждого экрана, теперь они включают в себя:
- Режим отображения по умолчанию
- Значения пересканирования
- Поворот пользователя и режим поворота
- Принудительный режим размера, плотности и масштабирования
- Режим удаления контента (при отключенном дисплее)
- Поддержка оформления системы и IME.
Класс DisplayWindowSettings содержит настройки для этих параметров. Они сохраняются на диске в разделе /data в файле display_settings.xml каждый раз, когда изменяется какая-либо настройка. Подробности см. в DisplayWindowSettings.AtomicFileStorage и DisplayWindowSettings#writeSettings() . Производители устройств могут указывать значения по умолчанию в файле display_settings.xml для конфигурации своих устройств. Однако, поскольку файл хранится в /data , может потребоваться дополнительная логика для восстановления файла в случае его удаления при сбросе.
По умолчанию Android 10 использует DisplayInfo#uniqueId в качестве идентификатора дисплея при сохранении настроек. uniqueId должен быть заполнен для всех дисплеев. Кроме того, он стабилен для физических и сетевых дисплеев. Также можно использовать порт физического дисплея в качестве идентификатора, который можно установить в DisplayWindowSettings#mIdentifier . При каждой записи все настройки записываются, поэтому безопасно обновлять ключ, используемый для записи дисплея в хранилище. Подробнее см. раздел «Статические идентификаторы дисплеев» .
Настройки сохраняются в каталоге /data по историческим причинам. Изначально они использовались для сохранения пользовательских настроек, таких как поворот экрана.
Статические идентификаторы отображения
В Android 9 (и более ранних версиях) не было стабильных идентификаторов для дисплеев в рамках фреймворка. При добавлении дисплея в систему для этого дисплея генерировался Display#mDisplayId или DisplayInfo#displayId путем увеличения статического счетчика. Если система добавляла и удаляла один и тот же дисплей, получался другой идентификатор.
Если устройство имело несколько доступных при загрузке дисплеев, им могли быть присвоены разные идентификаторы в зависимости от момента загрузки. Хотя Android 9 (и более ранние версии) включали DisplayInfo#uniqueId , они не содержали достаточно информации для различения дисплеев, поскольку физические дисплеи идентифицировались как local:0 или local:1 , обозначая встроенный и внешний дисплей.
В Android 10 изменена DisplayInfo#uniqueId , добавлен стабильный идентификатор, позволяющий различать локальные, сетевые и виртуальные дисплеи.
| Тип отображения | Формат |
|---|---|
| Местный | local:<stable-id> |
| Сеть | network:<mac-address> |
| Виртуальный | virtual:<package-name-and-name> |
Помимо обновлений uniqueId , DisplayInfo.address содержит DisplayAddress — идентификатор дисплея, который остается стабильным после перезагрузки. В Android 10 DisplayAddress поддерживает физические и сетевые дисплеи. DisplayAddress.Physical содержит стабильный идентификатор дисплея (такой же, как в uniqueId ) и может быть создан с помощью DisplayAddress#fromPhysicalDisplayId() .
В Android 10 также предусмотрен удобный способ получения информации о порте ( Physical#getPort() ). Этот метод можно использовать в рамках фреймворка для статической идентификации дисплеев. Например, он используется в DisplayWindowSettings . DisplayAddress.Network содержит MAC-адрес и может быть создан с помощью DisplayAddress#fromMacAddress() .
Эти дополнения позволяют производителям устройств идентифицировать дисплеи в статических многоэкранных конфигурациях и настраивать различные параметры и функции системы, используя статические идентификаторы дисплеев, такие как порты для физических дисплеев. Эти методы скрыты и предназначены для использования только внутри system_server .
При наличии идентификатора дисплея HWC (который может быть непрозрачным и не всегда стабильным) этот метод возвращает (специфичный для платформы) 8-битный номер порта, идентифицирующий физический разъем для вывода изображения, а также EDID-объект дисплея. SurfaceFlinger извлекает информацию о производителе или модели из EDID для генерации стабильных 64-битных идентификаторов дисплея, предоставляемых фреймворку. Если этот метод не поддерживается или выдает ошибку, SurfaceFlinger переключается в устаревший режим MD, где DisplayInfo#address равен null, а DisplayInfo#uniqueId жестко закодирован, как описано выше.
Чтобы убедиться в поддержке этой функции, выполните следующую команду:
$ dumpsys SurfaceFlinger --display-id # Example output. Display 21691504607621632 (HWC display 0): port=0 pnpId=SHP displayName="LQ123P1JX32" Display 9834494747159041 (HWC display 2): port=1 pnpId=HWP displayName="HP Z24i" Display 1886279400700944 (HWC display 1): port=2 pnpId=AUS displayName="ASUS MB16AP"
Используйте более двух дисплеев.
В Android 9 (и более ранних версиях) SurfaceFlinger и DisplayManagerService предполагали наличие не более двух физических дисплеев с жестко заданными идентификаторами 0 и 1.
Начиная с Android 10, SurfaceFlinger мог использовать API Hardware Composer (HWC) для генерации стабильных идентификаторов дисплеев, что позволяло ему управлять произвольным количеством физических дисплеев. Подробнее см. раздел «Статические идентификаторы дисплеев» .
Платформа может получить токен IBinder для физического дисплея через SurfaceControl#getPhysicalDisplayToken после получения 64-битного идентификатора дисплея из SurfaceControl#getPhysicalDisplayIds или из события горячего подключения DisplayEventReceiver .
В Android 10 (и более ранних версиях) основной внутренний дисплей имеет тип TYPE_INTERNAL , а все дополнительные дисплеи помечаются как TYPE_EXTERNAL независимо от типа подключения. Поэтому дополнительные внутренние дисплеи рассматриваются как внешние. В качестве обходного пути, код, специфичный для устройства, может делать предположения относительно DisplayAddress.Physical#getPort если известен HWC и логика распределения портов предсказуема.
Это ограничение снято в Android 11 (и более поздних версиях).
- В Android 11 первым дисплеем, отображаемым при загрузке, является основной. Тип подключения (внутренний или внешний) не имеет значения. Однако остается верным утверждение, что основной дисплей нельзя отключить, и, следовательно, на практике это должен быть внутренний дисплей. Следует отметить, что некоторые складные телефоны имеют несколько внутренних дисплеев.
- Дополнительные дисплеи корректно классифицируются как
Display.TYPE_INTERNALилиDisplay.TYPE_EXTERNAL(ранее известные какDisplay.TYPE_BUILT_INиDisplay.TYPE_HDMIсоответственно) в зависимости от типа их подключения.
Выполнение
В Android 9 и более ранних версиях дисплеи идентифицируются 32-битными идентификаторами, где 0 — внутренний дисплей, 1 — внешний дисплей, [2, INT32_MAX] — виртуальные дисплеи HWC, а -1 означает недействительный дисплей или виртуальный дисплей, не относящийся к HWC.
Начиная с Android 10, дисплеям присваиваются стабильные и постоянные идентификаторы, что позволяет SurfaceFlinger и DisplayManagerService отслеживать более двух дисплеев и распознавать ранее обнаруженные дисплеи. Если HWC поддерживает IComposerClient.getDisplayIdentificationData и предоставляет данные идентификации дисплея, SurfaceFlinger анализирует структуру EDID и выделяет стабильные 64-битные идентификаторы для физических и виртуальных дисплеев HWC. Идентификаторы выражаются с помощью типа параметра, где значение null представляет собой недействительный дисплей или виртуальный дисплей, не относящийся к HWC. Без поддержки HWC SurfaceFlinger возвращается к устаревшему поведению, используя не более двух физических дисплеев.
Фокусировка на каждом дисплее
Для поддержки нескольких источников ввода, одновременно нацеленных на отдельные дисплеи, Android 10 можно настроить на поддержку нескольких активных окон, максимум по одному на каждый дисплей. Это предназначено только для особых типов устройств, когда несколько пользователей одновременно взаимодействуют с одним и тем же устройством и используют разные методы или устройства ввода, например, Android Automotive.
Настоятельно рекомендуется не включать эту функцию на обычных устройствах, включая устройства с несколькими экранами или устройства, используемые для работы в режиме, аналогичном настольному компьютеру. Это связано, прежде всего, с проблемой безопасности, из-за которой пользователи могут задаваться вопросом, какое окно находится в фокусе ввода.
Представьте пользователя, который вводит конфиденциальную информацию в текстовое поле ввода, например, при входе в банковское приложение или вводе текста, содержащего секретную информацию. Вредоносное приложение может создать виртуальное окно вне экрана, с помощью которого оно сможет выполнить действие, также используя текстовое поле ввода. И легитимные, и вредоносные действия имеют фокус и отображают активный индикатор ввода (мигающий курсор).
Однако, поскольку ввод с клавиатуры (аппаратной или программной) осуществляется только в самое верхнее приложение (то самое последнее запущенное приложение), создав скрытый виртуальный дисплей, вредоносное приложение может перехватывать ввод пользователя, даже при использовании программной клавиатуры на основном экране устройства.
Используйте com.android.internal.R.bool.config_perDisplayFocusEnabled для установки фокуса для каждого дисплея.
Совместимость
Проблема: В Android 9 и более ранних версиях одновременно в фокусе находится не более одного окна в системе.
Решение: В редких случаях, когда в фокусе оказываются два окна одного процесса, система предоставляет фокус только окну, находящемуся выше в Z-порядке. Это ограничение снимается для приложений, ориентированных на Android 10, поскольку ожидается, что они смогут поддерживать одновременную фокусировку на нескольких окнах.
Выполнение
Доступность этой функции контролируется WindowManagerService#mPerDisplayFocusEnabled . В ActivityManager вместо глобального отслеживания в переменной теперь используется ActivityDisplay#getFocusedStack() . ActivityDisplay#getFocusedStack() определяет фокус на основе Z-порядка, а не кэширует значение. Это сделано для того, чтобы отслеживать Z-порядок действий был только один источник — WindowManager.
ActivityStackSupervisor#getTopDisplayFocusedStack() использует аналогичный подход в тех случаях, когда необходимо определить самый верхний сфокусированный стек в системе. Стеки просматриваются сверху вниз в поисках первого подходящего стека.
Теперь InputDispatcher может иметь несколько активных окон (по одному на каждый дисплей). Если событие ввода привязано к конкретному дисплею, оно отправляется в активное окно соответствующего дисплея. В противном случае оно отправляется в активное окно того дисплея, с которым пользователь взаимодействовал в последний раз.
См. InputDispatcher::mFocusedWindowHandlesByDisplay и InputDispatcher::setFocusedDisplay() . Приложения, находящиеся в фокусе, также обновляются отдельно в InputManagerService с помощью NativeInputManager::setFocusedApplication() .
В WindowManager отслеживание сфокусированных окон также осуществляется отдельно. См. DisplayContent#mCurrentFocus и DisplayContent#mFocusedApp и их соответствующие примеры использования. Соответствующие методы отслеживания и обновления фокуса были перенесены из WindowManagerService в DisplayContent .