Android多屏幕支持-Android12
Android多屏幕支持-Android12
- 1、概览及相关文章
- 2、屏幕窗口配置
- 2.1 配置xml文件
- 2.2 DisplayInfo#uniqueId 屏幕标识
- 2.3 adb查看信息
- 3、配置文件解析
- 3.1 xml字段读取
- 3.2 简要时序图
- 4、每屏幕焦点
android12-release
1、概览及相关文章
AOSP > 文档 > 心主题 > 多屏幕概览
术语
在这些文章中,主屏幕和辅助屏幕的定义如下:主(默认)屏幕的
屏幕 ID为DEFAULT_DISPLAY
辅助屏幕的屏幕 ID不是DEFAULT_DISPLAY
| 主题区域 | 文章 |
|---|---|
| 开发和测试 | 推荐做法 测试和开发环境 常见问题解答 |
| 相关文章集 | 显示 系统装饰支持 输入法支持 |
| 单篇文章 | 多项恢复 Activity 启动政策 锁定屏幕 输入路由 多区音频 |
2、屏幕窗口配置
2.1 配置xml文件
/data/system/display_settings.xml 配置:
- 模拟屏幕:
uniqueId用于在名称属性中标识屏幕,对于模拟屏幕,此 ID 为overlay:1。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<config identifier="0" />
<displayname="overlay:1"shouldShowSystemDecors="true"shouldShowIme="true" />
</display-settings>
- 内置屏幕:
uniqueId对于内置屏幕,示例值可以是 “local:45354385242535243453”。另一种方式是使用硬件端口信息,并设置 identifier=“1” 以与 DisplayWindowSettingsProvider#IDENTIFIER_PORT 对应,然后更新 name 以使用"port:<port_id>" 格式。
<?xmlversion='1.0' encoding='utf-8' standalone='yes' ?>
<display-settings>
<config identifier="1" />
<displayname="port:12345"shouldShowSystemDecors="true"shouldShowIme="true" />
</display-settings>
2.2 DisplayInfo#uniqueId 屏幕标识
DisplayInfo#uniqueId,以添加稳定的标识符并区分本地、网络和虚拟屏幕
| 屏幕类型 | 格式 |
|---|---|
| 本地 | local:<stable-id> |
| 网络 | network:<mac-address> |
| 虚拟 | virtual:<package-name-and-name> |
2.3 adb查看信息
$ 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"
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp#SurfaceFlinger::dumpDisplayIdentificationData
void SurfaceFlinger::dumpDisplayIdentificationData(std::string& result) const {for (const auto& [token, display] : mDisplays) {const auto displayId = PhysicalDisplayId::tryCast(display->getId());if (!displayId) {continue;}const auto hwcDisplayId = getHwComposer().fromPhysicalDisplayId(*displayId);if (!hwcDisplayId) {continue;}StringAppendF(&result,"Display %s (HWC display %" PRIu64 "): ", to_string(*displayId).c_str(),*hwcDisplayId);uint8_t port;DisplayIdentificationData data;if (!getHwComposer().getDisplayIdentificationData(*hwcDisplayId, &port, &data)) {result.append("no identification data\n");continue;}if (!isEdid(data)) {result.append("unknown identification data\n");continue;}const auto edid = parseEdid(data);if (!edid) {result.append("invalid EDID\n");continue;}StringAppendF(&result, "port=%u pnpId=%s displayName=\"", port, edid->pnpId.data());result.append(edid->displayName.data(), edid->displayName.length());result.append("\"\n");}
}

3、配置文件解析
3.1 xml字段读取
- 文件路径:
DATA_DISPLAY_SETTINGS_FILE_PATH = "system/display_settings.xml"、VENDOR_DISPLAY_SETTINGS_FILE_PATH = "etc/display_settings.xml"、Settings.Global.getString(resolver,DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH)(DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH = "wm_display_settings_path")FileData对象:fileData.mIdentifierType = getIntAttribute(parser, "identifier", IDENTIFIER_UNIQUE_ID)、name = parser.getAttributeValue(null, "name")、shouldShowIme = getBooleanAttribute(parser, "shouldShowIme", null /* defaultValue */)、settingsEntry.mShouldShowSystemDecors = getBooleanAttribute(parser, "shouldShowSystemDecors", null /* defaultValue */)等等private static final class FileData {int mIdentifierType;final Map<String, SettingsEntry> mSettings = new HashMap<>();@Overridepublic String toString() {return "FileData{"+ "mIdentifierType=" + mIdentifierType+ ", mSettings=" + mSettings+ '}';} }
DisplayWindowSettings.java有关显示器的当前持久设置。提供显示设置的策略,并将设置值的持久性和查找委派给提供的{@link SettingsProvider}
@Nullable
private static FileData readSettings(ReadableSettingsStorage storage) {InputStream stream;try {stream = storage.openRead();} catch (IOException e) {Slog.i(TAG, "No existing display settings, starting empty");return null;}FileData fileData = new FileData();boolean success = false;try {TypedXmlPullParser parser = Xml.resolvePullParser(stream);int type;while ((type = parser.next()) != XmlPullParser.START_TAG&& type != XmlPullParser.END_DOCUMENT) {// Do nothing.}if (type != XmlPullParser.START_TAG) {throw new IllegalStateException("no start tag found");}int outerDepth = parser.getDepth();while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {continue;}String tagName = parser.getName();if (tagName.equals("display")) {readDisplay(parser, fileData);} else if (tagName.equals("config")) {readConfig(parser, fileData);} else {Slog.w(TAG, "Unknown element under <display-settings>: "+ parser.getName());XmlUtils.skipCurrentTag(parser);}}success = true;} catch (IllegalStateException e) {Slog.w(TAG, "Failed parsing " + e);} catch (NullPointerException e) {Slog.w(TAG, "Failed parsing " + e);} catch (NumberFormatException e) {Slog.w(TAG, "Failed parsing " + e);} catch (XmlPullParserException e) {Slog.w(TAG, "Failed parsing " + e);} catch (IOException e) {Slog.w(TAG, "Failed parsing " + e);} catch (IndexOutOfBoundsException e) {Slog.w(TAG, "Failed parsing " + e);} finally {try {stream.close();} catch (IOException ignored) {}}if (!success) {fileData.mSettings.clear();}return fileData;
}private static int getIntAttribute(TypedXmlPullParser parser, String name, int defaultValue) {return parser.getAttributeInt(null, name, defaultValue);
}@Nullable
private static Integer getIntegerAttribute(TypedXmlPullParser parser, String name,@Nullable Integer defaultValue) {try {return parser.getAttributeInt(null, name);} catch (Exception ignored) {return defaultValue;}
}@Nullable
private static Boolean getBooleanAttribute(TypedXmlPullParser parser, String name,@Nullable Boolean defaultValue) {try {return parser.getAttributeBoolean(null, name);} catch (Exception ignored) {return defaultValue;}
}private static void readDisplay(TypedXmlPullParser parser, FileData fileData)throws NumberFormatException, XmlPullParserException, IOException {String name = parser.getAttributeValue(null, "name");if (name != null) {SettingsEntry settingsEntry = new SettingsEntry();settingsEntry.mWindowingMode = getIntAttribute(parser, "windowingMode",WindowConfiguration.WINDOWING_MODE_UNDEFINED /* defaultValue */);settingsEntry.mUserRotationMode = getIntegerAttribute(parser, "userRotationMode",null /* defaultValue */);settingsEntry.mUserRotation = getIntegerAttribute(parser, "userRotation",null /* defaultValue */);settingsEntry.mForcedWidth = getIntAttribute(parser, "forcedWidth",0 /* defaultValue */);settingsEntry.mForcedHeight = getIntAttribute(parser, "forcedHeight",0 /* defaultValue */);settingsEntry.mForcedDensity = getIntAttribute(parser, "forcedDensity",0 /* defaultValue */);settingsEntry.mForcedScalingMode = getIntegerAttribute(parser, "forcedScalingMode",null /* defaultValue */);settingsEntry.mRemoveContentMode = getIntAttribute(parser, "removeContentMode",REMOVE_CONTENT_MODE_UNDEFINED /* defaultValue */);settingsEntry.mShouldShowWithInsecureKeyguard = getBooleanAttribute(parser,"shouldShowWithInsecureKeyguard", null /* defaultValue */);settingsEntry.mShouldShowSystemDecors = getBooleanAttribute(parser,"shouldShowSystemDecors", null /* defaultValue */);final Boolean shouldShowIme = getBooleanAttribute(parser, "shouldShowIme",null /* defaultValue */);if (shouldShowIme != null) {settingsEntry.mImePolicy = shouldShowIme ? DISPLAY_IME_POLICY_LOCAL: DISPLAY_IME_POLICY_FALLBACK_DISPLAY;} else {settingsEntry.mImePolicy = getIntegerAttribute(parser, "imePolicy",null /* defaultValue */);}settingsEntry.mFixedToUserRotation = getIntegerAttribute(parser, "fixedToUserRotation",null /* defaultValue */);settingsEntry.mIgnoreOrientationRequest = getBooleanAttribute(parser,"ignoreOrientationRequest", null /* defaultValue */);settingsEntry.mIgnoreDisplayCutout = getBooleanAttribute(parser,"ignoreDisplayCutout", null /* defaultValue */);settingsEntry.mDontMoveToTop = getBooleanAttribute(parser,"dontMoveToTop", null /* defaultValue */);fileData.mSettings.put(name, settingsEntry);}XmlUtils.skipCurrentTag(parser);
}private static void readConfig(TypedXmlPullParser parser, FileData fileData)throws NumberFormatException,XmlPullParserException, IOException {fileData.mIdentifierType = getIntAttribute(parser, "identifier",IDENTIFIER_UNIQUE_ID);XmlUtils.skipCurrentTag(parser);
}
3.2 简要时序图

4、每屏幕焦点
每个屏幕焦点
为了同时支持多个以单个屏幕为目标的输入源,可以将 Android 10 配置为支持多个聚焦窗口,每个屏幕最多支持一个。当多个用户同时与同一设备交互并使用不同的输入方法或设备(例如 Android Automotive)时,此功能仅适用于特殊类型的设备。
强烈建议不要为常规设备启用此功能,包括跨屏设备或用于类似桌面设备体验的设备。这主要是出于安全方面的考虑,因为这样做可能会导致用户不确定哪个窗口具有输入焦点。
想象一下,用户在文本输入字段中输入安全信息,也许是登录某个银行应用或者输入包含敏感信息的文本。恶意应用可以创建一个虚拟的屏幕外屏幕用于执行 activity,也可以使用文本输入字段执行 activity。合法 activity 和恶意 activity 均具有焦点,并且都显示一个有效的输入指示符(闪烁光标)。
不过,键盘(硬件或软件)的输入只能进入最顶层的 activity(最近启动的应用)。通过创建隐藏的虚拟屏幕,即使在主设备屏幕上使用软件键盘,恶意应用也可以获取用户输入。
使用
com.android.internal.R.bool.config_perDisplayFocusEnabled设置每屏幕焦点。
兼容性
**问题:**在 Android 9 及更低版本中,系统中一次最多只有一个窗口具有焦点。**解决方案:**在极少数情况下,来自同一进程的两个窗口都处于聚焦状态,则系统仅向在 Z 轴顺序中较高的窗口提供焦点。对于以 Android 10 为目标平台的应用,目前已取消这一限制,此时预计这些应用可以支持同时聚焦多个窗口。
实现
WindowManagerService#mPerDisplayFocusEnabled用于控制此功能的可用性。在ActivityManager中,系统现在使用的是ActivityDisplay#getFocusedStack(),而不是利用变量进行全局跟踪。ActivityDisplay#getFocusedStack()根据 Z 轴顺序确定焦点,而不是通过缓存值来确定。这样一来,只有一个来源WindowManager需要跟踪 activity 的 Z 轴顺序。如果必须要确定系统中最顶层的聚焦堆栈,
ActivityStackSupervisor#getTopDisplayFocusedStack()会采用类似的方法处理这些情况。系统将从上到下遍历这些堆栈,搜索第一个符合条件的堆栈。
InputDispatcher现在可以有多个聚焦窗口(每个屏幕一个)。如果某个输入事件特定于屏幕,则该事件会被分派到相应屏幕中的聚焦窗口。否则,它会被分派到聚焦屏幕(即用户最近与之交互的屏幕)中的聚焦窗口。请参阅
InputDispatcher::mFocusedWindowHandlesByDisplay 和 InputDispatcher::setFocusedDisplay()。聚焦应用也会通过NativeInputManager::setFocusedApplication()在InputManagerService中分别更新。在
WindowManager中,系统还会单独跟踪聚焦窗口。请参阅DisplayContent#mCurrentFocus和DisplayContent#mFocusedApp以及各自的用途。相关的焦点跟踪和更新方法已从WindowManagerService移至DisplayContent。
相关文章:
Android多屏幕支持-Android12
Android多屏幕支持-Android12 1、概览及相关文章2、屏幕窗口配置2.1 配置xml文件2.2 DisplayInfo#uniqueId 屏幕标识2.3 adb查看信息 3、配置文件解析3.1 xml字段读取3.2 简要时序图 4、每屏幕焦点 android12-release 1、概览及相关文章 AOSP > 文档 > 心主题 > 多屏…...
python环境下载安装教程,python运行环境怎么下载
本篇文章给大家谈谈python安装步骤以及环境变量配置,以及下载python需要设置环境变量吗,希望对各位有所帮助,不要忘了收藏本站喔。 1.https://www.python.org/downloads/windows/ 下载适合自己电脑的python安装包 2.下载后安装即可 3.配置环…...
【0.2】lubancat鲁班猫4远程ubuntu22.04.2 无需任何安装
环境 lubancat4鲁班猫4 (4G0)不带emmc 系统镜像ubuntu-22.04.2-desktop-arm64-lubancat-4.img 网络环境:有线网络与本win10电脑同意环境 操作步骤ubuntu正常开机登陆用户,连接好网络进入设置>网络查看设备当前局域网IP 如192.168.199.159进入设置>共享>远程…...
Flutter 状态管理 Provider
状态管理必要性 Flutter基于声明式构建UI,原生则是命令式,状态管理是用于解决声明式开发带来的问题。 例:命令式的原生,数据更新需要拿到对应控件并更改其显示值;而声明式则需要更改数据值并通过setstate更新状态&am…...
【设计模式】观察者模式
什么是观察者模式? 观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态…...
ORCA优化器浅析——CDXLOperator Base class for operators in a DXL tree
如上图所示,CDXLOperator作为Base class for operators in a DXL tree,其子类CDXLLogical、CDXLScalar、CDXLPhysical作为逻辑节点、物理节点和Scalar节点的DXL表示类,因此其包含了这些类的共同部分特性,比如获取其DXL节点表示的函…...
go入门实践四-go实现一个简单的tcp-socks5代理服务
文章目录 前言socks协议简介go实现一个简单的socks5代理运行与压测抓包验证 前言 SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。协议在应用层和传输层之间。 本文使用先了解socks协议。然后实现一个socks5的tcp代理服务端。最后&#…...
div 中元素居中的N种常用方法
本文主要记录几种常用的div盒子水平垂直都居中的方法。本文主要参考了该篇博文并实践加以记录说明以加深理解记忆 css之div盒子居中常用方法大全 本文例子使用的 html body结构下的div 盒子模型如下: <body><div class"container"><div c…...
Java获取指定文件夹下目录下所有视频并复制到另一个地方
import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption;public class VideoCopier {public static void main(String[] args) {// 指定源文件夹路径和目标文件夹路径String sourceFolderPath "path/to…...
windows server 2016 搭建使用 svn 服务器教程
参考教程: https://zhuanlan.zhihu.com/p/428552058 https://blog.csdn.net/weixin_33897722/article/details/85602029 配置环境 windows server 2016 远程服务器公网 ip 安装 SVN 服务端 下载 svn 服务端安装包:https://www.visualsvn.com/download…...
【Python】如何判断时间序列数据是否为平稳时间序列或非平稳时间序列?
判断时间序列数据是否为平稳时间序列或非平稳时间序列,通常可以通过以下方法: (1)观察时间序列数据的均值和方差是否随时间变化而发生明显的改变。若均值和方差变化明显,则该时间序列数据可能为非平稳时间序列&#x…...
Labview控制APx(Audio Precision)进行测试测量(六)
用 LabVIEW 驱动 VIs生成任意波形 在 APx500 应用程序中,默认波形类型为正弦。这是指 APx 内置的正弦发生器,根据信号路径设置,许多测量还允许其他内置波形,如方波,分裂正弦波或分裂相位,以及使用导入的。w…...
【Linux】网络协议总结
目录 网络协议总结 应用层 传输层 网络层 数据链路层 网络协议总结 应用层 应用层的作用:负责应用程序间沟通,完成一系列业务处理所需服务。能够根据自己的需求,设计对应的应用层协议。了解HTTP协议。理解DNS的原理和工作流程。 传…...
如何轻松注册企业邮箱?快速掌握超简单的注册技巧!
随着互联网的发展,越来越多的企业开始使用电子邮件作为通信工具。企业邮箱不仅可以提高企业的工作效率,还可以使企业通信更加便捷、保密性更高。那么,企业邮箱怎么注册申请呢?下面我们来详细介绍一下。 第一步:选择邮箱…...
【行为型设计模式】C#设计模式之观察者模式
题目:假设你正在开发一个简单的新闻发布系统,该系统允许用户订阅不同的新闻频道,并在有新闻发布时向订阅者发送通知。使用观察者模式设计和实现该系统。观察者模式的相关概念和定义: 观察者模式是一种行为设计模式,它定…...
《Java面向对象程序设计》学习笔记——第 8 章 设计模式
专栏:《Java面向对象程序设计》学习笔记 第 8 章 设计模式 一个好的设计系统往往是易维护、易扩展、易复用的。 8.1 设计模式简介 8.1.1 什么是设计模式 一个设计模式 (pattern) 是针对某一类问题的最佳解决方案,而且己经被成功应用于许多系统的设…...
Java学习笔记28——字节流1
IO流概述和分类 IO流IO流的分类字节流字节流写数据FileOutputStream字节流写数据的三种方式字节流写数据的两个问题字节流写数据加异常处理 IO流 IO:输入输出 流:一种抽象的概念,是对数据传输的总称,流的本质就是数据传输 IO流的…...
C++连接串口方式(MFC版本)(简单版本)
ComSerialPort.h /*_________________________串口________________________________*/class Com_SerialPort { public:Com_SerialPort();Com_SerialPort(int port, int baudRate, int byteSize, int parity, int stopBits);~Com_SerialPort(); public:bool Connect(bool bMut…...
ospf重发布
华子目录 一、实验拓扑二、实验要求三、实验思路1、配置接口IP地址以及环回地址(以此类推)2、配置动态路由协议3、重发布4、更改接口类型5、配置路由策略 一、实验拓扑 二、实验要求 1、使用双点双向重发布2、所有路由器进行最佳选路3、存在备份路径&am…...
基于weka手工实现K-means
一、K-means聚类算法 K均值聚类(K-means clustering)是一种常见的无监督学习算法,用于将数据集中的样本划分为K个不同的类别或簇。它通过最小化样本点与所属簇中心点之间的距离来确定最佳的簇划分。 K均值聚类的基本思想如下: …...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
