背景
近期有学员朋友参加framework面试相关面试时候,有的面试官问到了折叠屏相关的一些问题,对于折叠屏这块的知识其实整个网络都属于比较空缺的,可能是因为主流程度不够,折叠屏的出现上市时间有限,折叠屏太贵市场接受度比较差,相关的官方资料比较少等。
那么马哥今天麻烦了一个学员朋友用他的折叠屏手机来进行一些dump从而调研出折叠屏的一些实现原理。
dump情况:
dumpsys activity containers
dumpsys adisplay
输出去如下:



从 dumpsys display 入手调研看懂活跃/非活跃
1 折叠态 (display1.txt)
dumpsys display 关键字段:
Viewports:
Display 0: isActive=true, physicalPort=148, logicalFrame=1080×2420 ← 活跃!
Display 1: isActive=false, physicalPort=147, logicalFrame=2172×2352 ← 不活跃
Display States:
Display 0: ON ← 亮屏
Display 1: OFF ← 灭屏
Display Devices:
port=148 (外屏): state=ON, layerStack=0 ← 活跃!
port=147 (内屏): state=OFF, layerStack=-1 ← 不活跃
2 展开态 (display2.txt)
dumpsys display 关键字段:
Viewports:
Display 0: isActive=true, physicalPort=147, logicalFrame=2172×2352 ← 活跃!
Display 1: isActive=false, physicalPort=148, logicalFrame=1080×2420 ← 不活跃
Display States:
Display 0: ON ← 亮屏
Display 1: OFF ← 灭屏
Display Devices:
port=147 (内屏): state=ON, layerStack=0 ← 活跃!
port=148 (外屏): state=OFF, layerStack=-1 ← 不活跃
3 看懂"活跃"的四个信号
同一个面板是否活跃,在 dump 中可以从四个层面交叉验证:
| 层面 | 活跃时的值 | 不活跃时的值 | 含义 |
|---|---|---|---|
| Viewport.isActive | true | false | 是否当前使用的显示区域 |
| Display State | ON | OFF | 显示电源状态 |
| Device state | ON | OFF | 物理面板供电状态 |
| device.layerStack | 0 | -1 | 是否连接 SurfaceFlinger(-1 = 无内容) |
四个信号同时变化——因为它们来自同一条控制链路。
对比关键字段(折叠→展开的变化):
折叠态: 展开态:
Viewport[0].physicalPort = 148 Viewport[0].physicalPort = 147 ← 换了!
Viewport[0].uniqueId = ...137790 Viewport[0].uniqueId = ...406847 ← 换了!
port=148 device state = ON port=148 device state = OFF ← 外屏灭了
port=148 device layerStack = 0 port=148 device layerStack = -1 ← 断开SF
port=147 device state = OFF port=147 device state = ON ← 内屏亮了
port=147 device layerStack = -1 port=147 device layerStack = 0 ← 接入SF
结论:活跃的面板 → state=ON + layerStack=0;不活跃的面板 → state=OFF + layerStack=-1。layerStack=-1 是关键——这面物理屏上什么都不显示。
从 dumpsys activity containers 看窗口归属
containers dump 展示的是逻辑层面——所有窗口都挂在 LogicDisplay 上,而不是物理面板上。
折叠态 (containers1.txt)
ROOT bounds=[0,0][1080,2420] ← 当前有效尺寸=外屏
Display 0 bounds=[0,0][1080,2420] ← 所有应用在这里
└─ DefaultTaskDisplayArea
├─ Task=8171 小红书 bounds=[0,0][1080,2420]
├─ Task=1 桌面 bounds=[0,0][1080,2420]
├─ Task=8112 微信 bounds=[0,0][1080,2420]
└─ ...
Display 1 bounds=[0,0][2172,2352] ← 尺寸是内屏的,但没有Task
└─ DefaultTaskDisplayArea ← 空的,无应用
展开态 (containers2.txt)
ROOT bounds=[0,0][2172,2352] ← 尺寸=内屏
Display 0 bounds=[0,0][2172,2352] ← 窗口全部迁移到这里
└─ DefaultTaskDisplayArea
├─ Task=8171 小红书 bounds=[0,0][2172,2352] ← 尺寸变了
├─ Task=1 桌面 bounds=[0,0][2172,2352]
├─ Task=8112 微信 bounds=[0,0][2172,2352]
└─ ...
Display 1 bounds=[0,0][1080,2420] ← 尺寸变成外屏,空的
└─ DefaultTaskDisplayArea ← 空
关键理解:Display ID 0 和 1 是逻辑编号,不是物理面板。展开时,物理内屏"换到"Display 0 下面,所有窗口跟着 Display 0 走——所以应用不需要知道自己跑在哪块物理面板上。但 bounds 变了(1080→2172),所以每个 Task 的尺寸都要重算。
活跃/非活跃是如何设置的
核心链路只有三步:
第一步:layout 配置文件说了算
/vendor/etc/displayconfig/display_layout_configuration.xml 定义了每个设备状态下,哪块面板 enabled、哪块 disabled:
<!-- 折叠态 -->
<device-state name="FOLDED">
<layout>
<display address="port:148" logicalDisplayId="0" enabled="true"/> <!-- 外屏活跃 -->
<display address="port:147" logicalDisplayId="1" enabled="false"/> <!-- 内屏休眠 -->
</layout>
</device-state>
<!-- 展开态 -->
<device-state name="OPEN">
<layout>
<display address="port:147" logicalDisplayId="0" enabled="true"/> <!-- 内屏活跃 -->
<display address="port:148" logicalDisplayId="1" enabled="false"/> <!-- 外屏休眠 -->
</layout>
</device-state>
enabled="true/false" 就是活跃与否的源头。
总结
- 硬件层面:两块独立的物理面板(port=147 和 port=148)
- 逻辑层面:Display ID 0/1 是固定编号,窗口和应用挂在逻辑 Display 上不动
- 切换本质:物理面板在两个逻辑 Display 之间"交换",每次只有一个被设为 enabled
- 活跃的标志(dump 里看这四个字段):
Viewport.isActive=true+DisplayState=ON+DeviceState=ON+layerStack=0 - 不活跃的标志:
isActive=false+DisplayState=OFF+DeviceState=OFF+layerStack=-1 - 控制源头:
display_layout_configuration.xml中的enabled属性 →setEnabledLocked()→ 设置 layerStack + 开关设备 - App 感知:通过
Activity.onConfigurationChanged()收到新的屏幕尺寸/density/挖孔信息,触发重绘
更多折叠屏相关深入原理后续马哥将继续分析,有成果了分享到vip群内,学员们敬请期待。

1286

被折叠的 条评论
为什么被折叠?



