Ниже приведены изменения, внесенные в эти разделы.
- Изменение размера окон и дисплеев
- Размеры и соотношения сторон медийных объявлений
- Правила в отношении медийной рекламы
- Настройки окна показа
- Идентификаторы статических дисплеев
- Как использовать более двух дисплеев
- Фокус на дисплее
Как изменить размер заданий и экранов
Чтобы указать, что приложение может не поддерживать многооконный режим или изменение размера, в действиях используется атрибут 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().
Если в activity SCM используется конфигурация экрана, отличная от конфигурации контейнера (например, размер экрана изменен или activity перемещено на другой экран), 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. Идентификаторы выражаются с помощью типа варианта, где нулевое значение представляет собой недопустимый дисплей или виртуальный дисплей, не относящийся к 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.