背景
有学员反馈说新版本的pixel负一屏的方案已经不是采用以前的Overlay那种独立Window方式,而是使用的独立的Activity方式,针对学员提出的这个疑问,马哥这边也下载了一个pixel的模拟器进行调研pixel的新版本负一屏方案原理。
直观认识:
主桌面

负一屏

拖动中间时候

调研原理部分
层级结构树部分
使用dumpsys activity containers和sf等查看负一屏情况
DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ ├─ Task=1 type=HOME mode=fullscreen override-mode=fullscreen requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ │ └─ Task=3 type=HOME mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ │ ├─ ActivityRecord{6892885 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t3} type=HOME mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ │ │ └─ 6a55d8e com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=HOME mode=fullscreen override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ │ └─ TaskFragment{b923527 mode=multi-window} type=HOME mode=MULTI-WINDOW override-mode=MULTI-WINDOW requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ │ └─ ActivityRecord{184635947 u0 com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.launcher.minusone.activity.MinusOneActivity t3} type=HOME mode=MULTI-WINDOW override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
│ │ └─ e843f4 com.google.android.googlequicksearchbox/com.google.android.apps.search.googleapp.launcher.minusone.activity.MinusOneActivity type=HOME mode=MULTI-WINDOW override-mode=undefined requested-bounds=[0,0][0,0] bounds=[0,0][2076,2152]
上面的相比正常的多了一个 TaskFragment{b923527 mode=multi-window}的容器,这个是专门装载负一屏的task。
关键属性:
- TaskFragment 以
WINDOWING_MODE_MULTI_WINDOW创建,与 fullscreen 的 Launcher Activity 共存于同一 Task - TaskFragment 的
organizerUid=10195(com.google.android.apps.nexuslauncher),由 Launcher 控制 - MinusOneActivity 进程为
com.google.android.googlequicksearchbox:googleapp(uid=10161),与 Launcher 不同进程
activity的生命周期部分
桌面滑动到负一屏

桌面滑动到负一屏时候,桌面首先就已经Pasue了,然后负一屏进行resume,彻底滑到负一屏后,桌面进行stop。
负一屏滑动回桌面
可以看到桌面先调研onStart,松手后滑到桌面才是resume,然后负一屏的activity就进行stop。
三种状态与生命周期
1 CLOSED 状态
- TaskFragment 存在但
setHidden=true - MinusOneActivity 未启动(或已 stopped)
- Launcher 独占显示
2 OPEN 状态
- TaskFragment
setHidden=false, MinusOneActivity RESUMED - Launcher Window 仍 VISIBLE 但在 WMS 中
mObscured=true(被 TF 遮挡) - Launcher Activity 状态为 PAUSED
- MinusOneActivity 为
mFocusedWindow
3 MID-DRAG 状态(实验验证)
以下数据来自拖动到中间位置时的实际 dumpsys 实验:
WMS 层面 (dumpsys window)
| 属性 | Launcher (Window #9) | MinusOne (Window #8) |
|---|---|---|
mViewVisibility | 0x0 (VISIBLE) | 0x0 (VISIBLE) |
mHasSurface | true | true |
mObscured | true | false |
mWindowingMode | fullscreen | multi-window |
| Surface | VRI-NexusLauncherActivity#721 | VRI-MinusOneActivity#758 |
关键发现:拖动期间 MinusOne 的 Window mViewVisibility=0x0 (VISIBLE) 且 mHasSurface=true。这与 OPEN 状态一致,说明 MinusOne 的 Window 在整个拖动过程中保持可见。
Activity 层面 (dumpsys activity)
Task{68ab20d #3 type=home}
├─ topResumedActivity = MinusOneActivity ← ★ 负一屏是 resumed
├─ TaskFragment{b923527 mode=multi-window}
│ └─ MinusOneActivity: state=RESUMED
│ mWindowingMode=multi-window
└─ NexusLauncherActivity: state=PAUSED
mWindowingMode=fullscreen
关键发现:拖动过程中 MinusOneActivity 已经处于 RESUMED 状态,Launcher 处于 PAUSED。说明一旦开始拖动(ACTION_DOWN),系统就已经完成了 Activity 生命周期的切换。用户拖动的是"已经 RESUME 的 MinusOneActivity 的 Surface 的位置",而不是在拖动过程中做 Activity 切换。
总结部分
Pixel Launcher 的负一屏不是在 Launcher 进程内嵌入一个 View 或 Fragment,而是利用 Android 12+ 的 TaskFragment API,在 Launcher 的 Task 内创建一个独立的 TaskFragment,将 Google App (Velvet) 的 MinusOneActivity 以 multi-window 模式嵌入其中。
两个 Activity 拥有各自独立的 Window 和 Surface,在 SurfaceFlinger 层面通过 z-order 叠加显示,通过两种不同的偏移机制实现同步滑动:
| 偏移目标 | 机制 | 层级 |
|---|---|---|
| Launcher (NexusLauncherActivity) | View.setTranslationX() | View 层 |
| 负一屏 (MinusOneActivity) | SurfaceControl.Transaction.setMatrix() | Surface 层 |
上面就是马哥对于新版本负一屏的初步调研方案,后续有时间会进行模仿pixel实现新版本负一屏的demo,欢迎相关vip学员找马哥交流实现方案等。

2274

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



