aa
This commit is contained in:
40
.obsidian/workspace.json
vendored
40
.obsidian/workspace.json
vendored
@@ -27,30 +27,16 @@
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "docs/Obsidian笔记体系/Daily/2026-01-13.md",
|
||||
"file": "docs/学习笔记/Android整机性能分析与问题定位实战指南.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "2026-01-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "59894255df52f532",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "docs/学习笔记/两份提示词对比.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "两份提示词对比"
|
||||
"title": "Android整机性能分析与问题定位实战指南"
|
||||
}
|
||||
}
|
||||
],
|
||||
"currentTab": 2
|
||||
"currentTab": 1
|
||||
}
|
||||
],
|
||||
"direction": "vertical"
|
||||
@@ -212,19 +198,24 @@
|
||||
"bases:Create new base": false
|
||||
}
|
||||
},
|
||||
"active": "59894255df52f532",
|
||||
"active": "25c9f7051aac05b3",
|
||||
"lastOpenFiles": [
|
||||
"docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md",
|
||||
"docs/学习笔记/Android整机性能分析与问题定位实战指南.md",
|
||||
"docs/学习笔记/Android WindowManagerService (WMS) 架构深度解析(第一次提示词).md",
|
||||
"docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md",
|
||||
"docs/学习笔记/两份提示词对比.md",
|
||||
"docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md",
|
||||
"docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md",
|
||||
"docs/学习笔记/前端学习.md",
|
||||
"docs/学习笔记/腾讯云相关账户.md",
|
||||
"docs/学习笔记/效率提升.md",
|
||||
"docs/学习笔记/Android性能分析标准化操作手册.md",
|
||||
"docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-13.md",
|
||||
"docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md",
|
||||
"docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md",
|
||||
"docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md",
|
||||
"docs/Obsidian笔记体系/Projects/aitsc",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-14.md",
|
||||
"docs/Obsidian笔记体系/Daily/2024-06-02.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-13.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-15.md",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-20.md",
|
||||
"docs/学习笔记/高级性能问题分析.md",
|
||||
@@ -236,11 +227,6 @@
|
||||
"docs/Obsidian笔记体系/Areas/09-调试与工具链/Systrace_Perfetto全解读.md",
|
||||
"docs/学习笔记/php/未命名.md",
|
||||
"docs/git/git设置用户名和邮箱.md",
|
||||
"docs/学习笔记/产品经理/产品经理--写文档.md",
|
||||
"docs/学习笔记/产品经理/沟通与表达详解.md",
|
||||
"docs/学习笔记/产品经理/数据分析详解.md",
|
||||
"docs/学习笔记/产品经理/产品运营详解.md",
|
||||
"docs/学习笔记/产品经理/产品设计详解.md",
|
||||
"docs/学习笔记/产品经理",
|
||||
"Pasted image 20260129111501.png",
|
||||
"Pasted image 20260129111451.png",
|
||||
|
||||
591
docs/学习笔记/Android WindowManagerService (WMS) 架构深度解析(第一次提示词).md
Normal file
591
docs/学习笔记/Android WindowManagerService (WMS) 架构深度解析(第一次提示词).md
Normal file
@@ -0,0 +1,591 @@
|
||||
# Android WindowManagerService (WMS) 架构深度解析
|
||||
|
||||
**版本**: Android 13 (API 33)
|
||||
**面向对象**: Framework层高级工程师
|
||||
**作者**: Android系统团队
|
||||
|
||||
---
|
||||
|
||||
## 1. WMS概述
|
||||
|
||||
### 1.1 架构地位与服务初始化
|
||||
|
||||
WindowManagerService作为Android图形系统的中枢,运行于`system_server`进程,管理所有窗口的生命周期、布局、层级与输入事件分发。它与ActivityManagerService(AMS)、SurfaceFlinger构成Android Framework的三驾马车。
|
||||
|
||||
**服务启动时序 (SystemServer.java)**:
|
||||
```java
|
||||
// frameworks/base/services/java/com/android/server/SystemServer.java
|
||||
private void startOtherServices() {
|
||||
// 1. 初始化WMS
|
||||
wm = WindowManagerService.main(context, inputManager, ...);
|
||||
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
|
||||
|
||||
// 2. WMS就绪后通知AMS
|
||||
wm.displayReady();
|
||||
wm.systemReady();
|
||||
}
|
||||
```
|
||||
|
||||
**核心初始化链路**:
|
||||
- `main()` → 创建`WindowManagerService`实例
|
||||
- 初始化`WindowManagerPolicy` (策略接口,实际为`PhoneWindowManager`)
|
||||
- 创建`DisplayContent`管理多显示器
|
||||
- 关联`InputManagerService`建立输入通道
|
||||
|
||||
### 1.2 核心架构类图
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class WindowManagerService {
|
||||
- WindowManagerPolicy mPolicy
|
||||
- WindowHashMap mWindowMap
|
||||
- RootWindowContainer mRoot
|
||||
- H mH
|
||||
+ addWindow()
|
||||
+ removeWindow()
|
||||
+ relayoutWindow()
|
||||
- performLayoutAndPlaceSurfacesLocked()
|
||||
}
|
||||
|
||||
class WindowState {
|
||||
- IWindow mClient
|
||||
- WindowToken mToken
|
||||
- WindowManager.LayoutParams mAttrs
|
||||
- Session mSession
|
||||
- SurfaceControl mSurfaceControl
|
||||
+ openInputChannel()
|
||||
+ attach()
|
||||
}
|
||||
|
||||
class WindowToken {
|
||||
- IBinder token
|
||||
- int windowType
|
||||
- DisplayContent mDisplayContent
|
||||
+ addWindow()
|
||||
}
|
||||
|
||||
class DisplayContent {
|
||||
- Display mDisplay
|
||||
- TaskDisplayArea mTaskDisplayArea
|
||||
- DisplayPolicy mDisplayPolicy
|
||||
- InputMonitor mInputMonitor
|
||||
+ getWindowToken()
|
||||
+ computeImeTarget()
|
||||
}
|
||||
|
||||
class InputMonitor {
|
||||
- InputWindowHandle mInputWindowHandles
|
||||
+ updateInputWindowsLw()
|
||||
+ setInputFocusLw()
|
||||
}
|
||||
|
||||
WindowManagerService --> DisplayContent : 管理
|
||||
WindowManagerService --> WindowState : 管理
|
||||
DisplayContent --> WindowToken : 包含
|
||||
WindowToken --> WindowState : 包含
|
||||
WindowManagerService --> InputMonitor : 持有
|
||||
InputMonitor --> WindowState : 监控
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 窗口管理核心
|
||||
|
||||
### 2.1 核心数据结构
|
||||
|
||||
| 类名 | 作用 | 关键成员 |
|
||||
|------|------|----------|
|
||||
| **WindowState** | 真实窗口状态实体 | mAttrs(窗口参数), mFrame(显示区域), mSurfaceControl(Surface句柄) |
|
||||
| **WindowToken** | 窗口令牌,同一组窗口的集合 | token(Binder令牌), mChildren(子窗口列表) |
|
||||
| **DisplayContent** | 逻辑屏幕管理 | mDisplay(物理显示), mTaskDisplayArea(任务区) |
|
||||
| **ActivityRecord** | Activity对应的窗口Token | 继承自WindowToken,绑定AMS生命周期 |
|
||||
| **Session** | 应用进程与WMS的会话 | 每个应用进程对应一个Session |
|
||||
|
||||
### 2.2 窗口添加流程 (`addWindow`)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as 应用进程
|
||||
participant WMS as WindowManagerService
|
||||
participant DC as DisplayContent
|
||||
participant SF as SurfaceFlinger
|
||||
|
||||
App->>WMS: Session.addToDisplayAsUser()
|
||||
activate WMS
|
||||
|
||||
WMS->>WMS: 权限校验(mPolicy.checkAddPermission)
|
||||
WMS->>DC: displayContent.getWindowToken()
|
||||
|
||||
alt Token不存在
|
||||
WMS->>WMS: 创建WindowToken
|
||||
end
|
||||
|
||||
WMS->>WMS: 创建WindowState
|
||||
WMS->>WMS: displayPolicy.adjustWindowParamsLw()
|
||||
WMS->>WMS: validateAddingWindowLw()
|
||||
|
||||
WMS->>WindowState: openInputChannel()
|
||||
Note right of WindowState: 创建InputChannel对,<br/>一端给WMS,一端给应用
|
||||
|
||||
WMS->>WMS: win.attach() → 加入容器
|
||||
WMS->>mWindowMap: put(client.asBinder(), win)
|
||||
WMS->>WindowToken: addWindow(win)
|
||||
|
||||
WMS->>WMS: updateFocusedWindowLocked()
|
||||
WMS->>DC: getInputMonitor().setInputFocusLw()
|
||||
WMS->>WindowState: getParent().assignChildLayers()
|
||||
|
||||
WMS-->>SF: SurfaceControl.openTransaction()
|
||||
WMS-->>SF: 创建Surface
|
||||
WMS-->>SF: SurfaceControl.closeTransaction()
|
||||
|
||||
deactivate WMS
|
||||
```
|
||||
|
||||
**源码核心片段 (Android 13)** :
|
||||
```java
|
||||
// frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
|
||||
public int addWindow(Session session, IWindow client, LayoutParams attrs, ...) {
|
||||
synchronized (mGlobalLock) {
|
||||
// 1. 重复添加检查
|
||||
if (mWindowMap.containsKey(client.asBinder())) {
|
||||
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
|
||||
}
|
||||
|
||||
// 2. Token获取/创建
|
||||
WindowToken token = displayContent.getWindowToken(attrs.token);
|
||||
if (token == null) {
|
||||
token = new WindowToken.Builder(this, binder, type)
|
||||
.setDisplayContent(displayContent)
|
||||
.build();
|
||||
}
|
||||
|
||||
// 3. 创建WindowState
|
||||
final WindowState win = new WindowState(this, session, client, token,
|
||||
parentWindow, appOp[0], attrs, viewVisibility, ...);
|
||||
|
||||
// 4. 打开输入通道
|
||||
if (outInputChannel != null) {
|
||||
win.openInputChannel(outInputChannel);
|
||||
}
|
||||
|
||||
// 5. 加入管理容器
|
||||
win.attach();
|
||||
mWindowMap.put(client.asBinder(), win);
|
||||
win.mToken.addWindow(win);
|
||||
|
||||
// 6. 触发布局与焦点更新
|
||||
performLayoutAndPlaceSurfacesLocked();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 窗口删除流程
|
||||
|
||||
**主要步骤**:
|
||||
1. **线程检查**: 确保调用线程与创建线程一致
|
||||
2. **移除引用**: 从`ViewRootImpl`列表、布局参数列表、View列表中删除
|
||||
3. **动画处理**: 如果窗口正在运行动画,延迟删除
|
||||
4. **资源释放**: 释放Surface、关闭InputChannel
|
||||
|
||||
```java
|
||||
// WindowManagerService.removeWindow() 核心逻辑
|
||||
void removeWindow(Session session, IWindow client) {
|
||||
synchronized(mGlobalLock) {
|
||||
WindowState win = mWindowMap.remove(client.asBinder());
|
||||
if (win == null) return;
|
||||
|
||||
// 关闭输入通道
|
||||
win.removeInputChannel();
|
||||
|
||||
// 从Token中移除
|
||||
win.mToken.removeWindow(win);
|
||||
|
||||
// 释放Surface
|
||||
win.mSurfaceControl.reparent(null);
|
||||
win.mSurfaceControl = null;
|
||||
|
||||
// 重新计算焦点
|
||||
updateFocusedWindowLocked();
|
||||
performLayoutAndPlaceSurfacesLocked();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 窗口层级管理 (Z-order)
|
||||
|
||||
Android窗口分为三大类,层级规则如下:
|
||||
|
||||
| 窗口类型 | 范围 | 层级特点 |
|
||||
|---------|------|----------|
|
||||
| **应用窗口** | 1 ~ 99 | 随Activity生命周期,由Task组织 |
|
||||
| **子窗口** | 1000 ~ 1999 | 必须依附于父窗口,Z-order在父窗口之上 |
|
||||
| **系统窗口** | 2000 ~ 2999 | 最高层级,如状态栏、输入法、Toast |
|
||||
|
||||
**层级分配算法**:
|
||||
- `assignLayersLocked()` 在每次布局时调用
|
||||
- 基于`BaseLayer + (type * 10000) + subLayerOffset`计算
|
||||
- 系统窗口 > 输入法窗口 > 应用窗口 > 壁纸窗口
|
||||
|
||||
```java
|
||||
// frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
|
||||
void assignChildLayers() {
|
||||
for (int i = 0; i < mChildren.size(); i++) {
|
||||
WindowContainer child = mChildren.get(i);
|
||||
child.assignLayer(getPrefix(), ++layer);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 布局与合成机制
|
||||
|
||||
### 3.1 布局流程核心方法
|
||||
|
||||
`performLayoutAndPlaceSurfacesLocked()` (简称**LAPS**) 是WMS的心脏,处理所有窗口位置、大小、可见性的计算。
|
||||
|
||||
**调用时机**:
|
||||
- 窗口添加/删除/更新
|
||||
- 配置变化(横竖屏切换、多窗口调整)
|
||||
- 动画更新
|
||||
- VSync信号到达
|
||||
|
||||
**执行流程**:
|
||||
```java
|
||||
// 简化版LAPS流程
|
||||
void performLayoutAndPlaceSurfacesLocked() {
|
||||
// 1. 布局窗口位置
|
||||
performLayoutLocked();
|
||||
|
||||
// 2. 分配Surface层值
|
||||
assignLayersLocked();
|
||||
|
||||
// 3. 准备Surface (与SurfaceFlinger交互)
|
||||
for (WindowState win : mWindows) {
|
||||
if (win.hasSurface() && win.shouldPrepareSurface()) {
|
||||
win.prepareSurfaceLocked();
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 提交事务给SurfaceFlinger
|
||||
mSurfaceControlFactory.getTransaction().apply();
|
||||
|
||||
// 5. 更新输入焦点窗口信息
|
||||
mInputMonitor.updateInputWindowsLw();
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 relayoutWindow 核心流程
|
||||
|
||||
应用通过`ViewRootImpl.relayoutWindow()`请求WMS重新计算窗口布局并分配Surface。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant AppThread as UI线程
|
||||
participant VRI as ViewRootImpl
|
||||
participant WMS as WindowManagerService
|
||||
participant SF as SurfaceFlinger
|
||||
|
||||
AppThread->>VRI: performTraversals()
|
||||
VRI->>WMS: relayoutWindow()
|
||||
activate WMS
|
||||
|
||||
WMS->>WMS: 获取mGlobalLock
|
||||
WMS->>WindowState: 计算新Frame
|
||||
WMS->>WindowState: 更新可见区域
|
||||
|
||||
alt Surface需要重建
|
||||
WMS->>WindowState: createSurfaceControl()
|
||||
WMS-->>SF: createSurface()
|
||||
else Surface无变化
|
||||
WMS->>WindowState: setPosition/setSize
|
||||
end
|
||||
|
||||
WMS->>WMS: performLayoutAndPlaceSurfaces()
|
||||
WMS-->>VRI: 返回RelayoutResult
|
||||
deactivate WMS
|
||||
|
||||
VRI->>AppThread: 通知布局完成
|
||||
VRI->>SF: 队列绘制命令
|
||||
```
|
||||
|
||||
**性能关键点**: `relayoutWindow` 持有`mGlobalLock`,长时间操作会阻塞其他窗口更新,导致丢帧。
|
||||
|
||||
### 3.3 与SurfaceFlinger的交互
|
||||
|
||||
WMS通过`SurfaceControl`与SurfaceFlinger通信:
|
||||
|
||||
```java
|
||||
// WindowState.createSurfaceControl() 核心逻辑
|
||||
void createSurfaceControl() {
|
||||
SurfaceControl.Builder b = new SurfaceControl.Builder()
|
||||
.setName(mAttrs.getTitle().toString())
|
||||
.setSize(mFrame.width(), mFrame.height())
|
||||
.setFormat(mAttrs.format)
|
||||
.setFlags(flags)
|
||||
.setParent(mToken.getSurfaceControl());
|
||||
|
||||
mSurfaceControl = b.build();
|
||||
|
||||
// 设置初始属性
|
||||
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
|
||||
t.setPosition(mSurfaceControl, mFrame.left, mFrame.top);
|
||||
t.setLayer(mSurfaceControl, mLayer);
|
||||
t.setAlpha(mSurfaceControl, mAttrs.alpha);
|
||||
t.apply();
|
||||
}
|
||||
```
|
||||
|
||||
**关键事务类型**:
|
||||
- `setPosition` / `setSize`: 更新位置尺寸
|
||||
- `setLayer`: 设置Z-order
|
||||
- `setAlpha` / `setMatrix`: 动画相关
|
||||
- `reparent`: 改变Surface父子关系(用于窗口切换动画)
|
||||
|
||||
---
|
||||
|
||||
## 4. 输入事件分发机制
|
||||
|
||||
### 4.1 完整分发链路
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[InputReader读取事件] --> B[InputDispatcher派发]
|
||||
B --> C{InputMonitor筛选窗口}
|
||||
|
||||
C -->|焦点窗口| D[获取窗口InputChannel]
|
||||
C -->|触摸坐标命中测试| E[获取命中窗口]
|
||||
|
||||
D --> F[通过socket发送]
|
||||
E --> F
|
||||
|
||||
F --> G[应用进程ViewRootImpl]
|
||||
G --> H[InputEventReceiver接收]
|
||||
H --> I[ViewRootImpl处理]
|
||||
I --> J[View树分发]
|
||||
```
|
||||
|
||||
### 4.2 InputMonitor角色
|
||||
|
||||
`InputMonitor`是WMS中的核心组件,负责维护输入窗口信息并传递给InputDispatcher:
|
||||
|
||||
```java
|
||||
// frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
|
||||
void updateInputWindowsLw(boolean force) {
|
||||
// 遍历所有窗口,收集输入窗口信息
|
||||
mDisplayContent.forAllWindows((w) -> {
|
||||
InputWindowHandle handle = w.getInputWindowHandle();
|
||||
handle.setTouchableRegion(w.getTouchableRegion());
|
||||
handle.setFocus(w == mDisplayContent.mCurrentFocus);
|
||||
handle.setLayer(w.getLayer());
|
||||
|
||||
// 添加到列表
|
||||
mInputWindowHandles.add(handle);
|
||||
}, true);
|
||||
|
||||
// 提交给InputDispatcher
|
||||
mInputManager.setInputWindows(mInputWindowHandles);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 InputChannel建立
|
||||
|
||||
每个窗口在添加时通过`openInputChannel`创建一对Socket连接:
|
||||
|
||||
```java
|
||||
// WindowState.openInputChannel()
|
||||
void openInputChannel(InputChannel outInputChannel) {
|
||||
String name = getName();
|
||||
InputChannel[] channels = InputChannel.openInputChannelPair(name);
|
||||
|
||||
// 服务端注册到InputDispatcher
|
||||
mInputWindowHandle = new InputWindowHandle(this);
|
||||
mService.mInputManager.registerInputChannel(channels[0], mInputWindowHandle);
|
||||
|
||||
// 客户端通道通过Binder返回
|
||||
channels[1].transferTo(outInputChannel);
|
||||
|
||||
// 保存服务端通道
|
||||
mClientChannel = channels[0];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 跨服务协作机制
|
||||
|
||||
### 5.1 WMS与AMS协作
|
||||
|
||||
AMS与WMS通过`ActivityRecord`(继承自`WindowToken`)建立绑定关系:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant AMS
|
||||
participant WMS
|
||||
participant Activity as ActivityRecord
|
||||
|
||||
AMS->>WMS: setAppWindowToken(token, activity)
|
||||
Note right of WMS: 绑定Activity与WindowToken
|
||||
|
||||
AMS->>AMS: startActivityLocked()
|
||||
AMS->>WMS: setAppStartingWindow()
|
||||
WMS-->>Activity: 创建启动窗口
|
||||
|
||||
AMS->>WMS: setAppVisibility(visible)
|
||||
WMS->>Activity: 显示/隐藏Activity窗口
|
||||
|
||||
AMS->>WMS: moveTaskToFront()
|
||||
WMS->>Activity: 调整Task层值
|
||||
|
||||
AMS->>WMS: setFocusedApp(token)
|
||||
WMS->>Activity: 设置焦点窗口
|
||||
```
|
||||
|
||||
**关键交互场景**:
|
||||
1. **Activity启动**: AMS通知WMS创建`AppWindowToken`
|
||||
2. **窗口可见性**: AMS控制生命周期,WMS控制显示/隐藏
|
||||
3. **Configuration变化**: 横竖屏切换需两服务协同
|
||||
4. **Task管理**: AMS维护Task栈,WMS维护显示层级
|
||||
|
||||
### 5.2 WMS与InputManagerService
|
||||
|
||||
```java
|
||||
// WMS持有IMS引用,IMS回调WMS
|
||||
class WindowManagerService {
|
||||
InputManagerService mInputManager;
|
||||
|
||||
// IMS回调WMS处理注入事件
|
||||
boolean injectInputEvent(InputEvent event, int mode) {
|
||||
return mInputManager.injectInputEvent(event, mode);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 容器遍历机制 (Android 13新特性)
|
||||
|
||||
WMS基于`WindowContainer`树形结构管理,提供统一的遍历API:
|
||||
|
||||
```java
|
||||
// 遍历所有叶子Task执行操作
|
||||
void forAllLeafTasks(Consumer<Task> callback) {
|
||||
mRoot.forAllLeafTasks(callback, true);
|
||||
}
|
||||
|
||||
// 遍历所有Activity
|
||||
void forAllActivities(Consumer<ActivityRecord> callback) {
|
||||
mRoot.forAllActivities(callback, true);
|
||||
}
|
||||
|
||||
// 实际应用:pause所有后台Task
|
||||
void pauseBackTasks() {
|
||||
mRoot.forAllLeafTasks((task) -> {
|
||||
if (task.isVisible()) task.startPausing();
|
||||
}, false);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能优化与问题排查
|
||||
|
||||
### 6.1 锁竞争优化
|
||||
|
||||
WMS重度依赖`mGlobalLock`,优化策略:
|
||||
|
||||
| 问题 | 优化方案 |
|
||||
|------|----------|
|
||||
| 长时间持有锁 | 拆分锁粒度,`DisplayContent`独立锁 |
|
||||
| 布局计算耗时 | 异步布局预览 |
|
||||
| Surface创建阻塞 | 预创建Surface池,复用缓冲区 |
|
||||
| Binder调用同步 | 增加异步接口,如`relayoutAsync` |
|
||||
|
||||
**典型案例**: ANR日志分析
|
||||
```
|
||||
"android.fg" Blocked -> "JobScheduler" Blocked -> "WMS.relayoutWindow" Blocked
|
||||
```
|
||||
→ 表明`relayoutWindow`持有锁时间过长,导致前台线程阻塞
|
||||
|
||||
### 6.2 常见异常及解决方案
|
||||
|
||||
| 异常 | 触发条件 | 排查方向 |
|
||||
|------|----------|----------|
|
||||
| `BadTokenException` | 窗口Token无效 | 检查Activity是否已销毁,WindowToken是否正确传递 |
|
||||
| 窗口闪烁 | Surface频繁重建 | 检查`relayoutWindow`调用频率,View层级是否稳定 |
|
||||
| 触摸事件错乱 | InputChannel未正确关闭 | 使用`dumpsys input`查看窗口注册状态 |
|
||||
| 多窗口卡顿 | Z-order计算复杂 | 简化窗口层级,减少重叠窗口数量 |
|
||||
|
||||
### 6.3 调试命令
|
||||
|
||||
```bash
|
||||
# 查看所有窗口状态
|
||||
adb shell dumpsys window windows
|
||||
|
||||
# 查看输入系统状态
|
||||
adb shell dumpsys input
|
||||
|
||||
# 查看显示信息
|
||||
adb shell dumpsys display
|
||||
|
||||
# 查看WMS内部锁状态
|
||||
adb shell dumpsys window traces
|
||||
|
||||
# 开启WMS详细日志
|
||||
adb shell setprop log.tag.WindowManager VERBOSE
|
||||
adb logcat -s WindowManager
|
||||
```
|
||||
|
||||
**关键输出解读**:
|
||||
```
|
||||
Window #7 Window{...}:
|
||||
mDisplayId=0
|
||||
mSession=Session{...}
|
||||
mClient=android.view.IWindow$Stub$Proxy
|
||||
mSurfaceControl=Surface(name=...)
|
||||
mFrame=[0,0][1080,1920]
|
||||
mAttrs=WM.LayoutParams{... flags=0x... type=TYPE_BASE_APPLICATION}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 附录
|
||||
|
||||
### 7.1 核心源码路径
|
||||
|
||||
| 组件 | 源码路径 |
|
||||
|------|----------|
|
||||
| WMS主类 | `frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java` |
|
||||
| WindowState | `frameworks/base/services/core/java/com/android/server/wm/WindowState.java` |
|
||||
| DisplayContent | `frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java` |
|
||||
| InputMonitor | `frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java` |
|
||||
| RootWindowContainer | `frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java` |
|
||||
| ActivityRecord | `frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java` |
|
||||
|
||||
### 7.2 版本演进特性
|
||||
|
||||
| Android版本 | WMS关键变化 |
|
||||
|-------------|-------------|
|
||||
| **Android 10** | 引入`TaskFragment`,支持更细粒度窗口组织 |
|
||||
| **Android 11** | 窗口同步机制优化,减少Surface重建 |
|
||||
| **Android 12** | 新增`WindowInsets`动画,改进多窗口布局 |
|
||||
| **Android 13** | 容器遍历API标准化,锁粒度细化 |
|
||||
|
||||
### 7.3 推荐文献
|
||||
|
||||
1. 《深入理解Android内核设计思想》- 窗口系统章节
|
||||
2. AOSP官方文档: [source.android.com/docs/core/graphics](https://source.android.com/docs/core/graphics)
|
||||
3. 老罗的Android之旅: WMS系列博客
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
WindowManagerService作为Android窗口系统的中枢,承担着窗口生命周期管理、布局计算、输入事件路由三大核心职责。理解WMS需要把握以下几点:
|
||||
|
||||
1. **架构分层**: 客户端(WindowManager) ↔ 服务端(WMS) ↔ 底层(SurfaceFlinger)的三层模型
|
||||
2. **核心对象**: WindowState(窗口实体)、WindowToken(逻辑分组)、DisplayContent(显示区域)
|
||||
3. **关键流程**: addWindow/removeWindow/relayoutWindow 构成窗口生命周期
|
||||
4. **锁机制**: `mGlobalLock`是性能关键点,异步化是优化方向
|
||||
5. **协同机制**: AMS控制生命周期,WMS控制显示,SurfaceFlinger负责合成
|
||||
|
||||
掌握WMS原理不仅是解决窗口相关问题的基石,更是深入理解Android图形系统、输入系统的必备知识。
|
||||
235
docs/学习笔记/Android整机性能分析与问题定位实战指南.md
Normal file
235
docs/学习笔记/Android整机性能分析与问题定位实战指南.md
Normal file
@@ -0,0 +1,235 @@
|
||||
好的,遵照您的指示。作为一名高级Android开发工程师,我将基于您提供的详细规范,为您撰写一份关于“Android整机性能分析与问题定位”的实战指南。这份指南将严格遵循您设定的技术背景、流程规范、质量标准和交付要求。
|
||||
|
||||
---
|
||||
|
||||
# 版本历史
|
||||
| 版本 | 日期 | 作者 | 变更描述 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 1.0 | 2024-05-24 | AI Assistant | 初始版本,根据高级开发工程师规范制定 |
|
||||
|
||||
## 一、引言
|
||||
本指南旨在为高级Android开发工程师提供一个系统化、标准化的整机性能分析框架。当面对海量性能日志和Trace数据时,工程师可依据此文档进行高效、精准的问题定位,并产出高质量的根因分析与优化建议。
|
||||
|
||||
## 二、分析准备:数据预处理与验证
|
||||
|
||||
在开始深入分析前,必须确保输入数据的完整性和一致性。这是所有后续分析的基石。
|
||||
|
||||
### 2.1 数据完整性检查清单
|
||||
收到测试团队的bugreport或日志包后,请使用自动化脚本或手动验证以下核心文件是否存在且内容有效:
|
||||
|
||||
```bash
|
||||
# 示例脚本片段: check_integrity.sh
|
||||
#!/bin/bash
|
||||
BUGREPORT_DIR=$1
|
||||
echo "正在验证数据完整性于: $BUGREPORT_DIR"
|
||||
|
||||
# 必须文件列表
|
||||
REQUIRED_FILES=(
|
||||
"bugreport-*.txt" # 主bugreport报告
|
||||
"logcat.txt" # 完整logcat
|
||||
"kernel.log" | "dmesg.txt" # 内核日志
|
||||
"traces.txt" # ANR traces
|
||||
"perfetto-trace.perfetto-trace" | "systrace.html" # Perfetto或Systrace
|
||||
"dumpstate_board.txt" # 板级信息
|
||||
)
|
||||
|
||||
for pattern in "${REQUIRED_FILES[@]}"; do
|
||||
if ! ls $BUGREPORT_DIR/$pattern 1> /dev/null 2>&1; then
|
||||
echo "警告: 未找到匹配模式 '$pattern' 的文件。"
|
||||
else
|
||||
echo "✓ 找到: $(ls $BUGREPORT_DIR/$pattern | head -n 1)"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
### 2.2 时间同步与归一化
|
||||
不同日志源(如`logcat`和`kernel`)可能使用不同的时间基准。为统一分析,需将所有日志时间戳转换为相对时间(例如,相对于开机时间或第一个关键事件的时间)。
|
||||
|
||||
- **kernel (dmesg)**:通常为`[ 秒.微秒]`格式,表示系统启动后的时间。
|
||||
- **logcat**:可显示绝对时间或相对时间。在bugreport中通常包含绝对时间。
|
||||
|
||||
**最佳实践**:找到第一个ANR或关键事件在`logcat`中的绝对时间,然后在`dmesg`中找到对应时间点的内核日志,以建立时间关联。
|
||||
|
||||
## 三、系统性分析流程(黄金四步法)
|
||||
|
||||
按照“由粗到细、由表及里”的原则,分步执行分析。
|
||||
|
||||
### 3.1 第一步:初步症状定位与关键事件捕获 (5-10分钟)
|
||||
目标是快速识别问题的表象、发生时间和影响的进程。
|
||||
|
||||
1. **快速扫描ANR与崩溃**:
|
||||
```bash
|
||||
# 扫描logcat.txt
|
||||
grep -n "ANR in" logcat.txt --color=always
|
||||
grep -n "FATAL EXCEPTION" logcat.txt --color=always
|
||||
grep -n "am_anr" logcat.txt # ActivityManagerService的ANR事件日志
|
||||
```
|
||||
|
||||
2. **定位UI卡顿**:
|
||||
```bash
|
||||
# 查找Choreographer丢帧记录,通常以"Choreographer"或"Skipped X frames"开头
|
||||
grep -n "Choreographer.*Skipped" logcat.txt --color=always
|
||||
```
|
||||
|
||||
3. **检查系统关键错误**:
|
||||
```bash
|
||||
# 内核错误、低内存、服务死亡等
|
||||
grep -n "cancelled due to low memory" logcat.txt
|
||||
grep -ni "service.*died" logcat.txt
|
||||
grep -n "Out of memory" kernel.log
|
||||
```
|
||||
|
||||
4. **记录关键信息**:
|
||||
- **问题进程名与PID**:`com.example.app` (PID: 1234)
|
||||
- **发生时间戳**:`2024-05-24 10:30:15.123`
|
||||
- **问题类型**:`ANR (Input dispatching timed out)` 或 `UI Jank`
|
||||
|
||||
### 3.2 第二步:系统资源瓶颈分析 (15-30分钟)
|
||||
判断问题是否由系统全局资源(CPU、内存、IO)紧张引起。
|
||||
|
||||
#### 3.2.1 CPU调度分析
|
||||
- **工具**:Perfetto UI (ui.perfetto.dev) 或 `systrace`。
|
||||
- **操作**:加载trace文件。
|
||||
- **关注点**:
|
||||
1. **整体负载**:查看CPU Frequency和Core C-State图,是否存在长时间满频运行或核心被限频?
|
||||
2. **关键进程调度**:找到问题进程(PID)的“Slices”轨道。分析其在ANR/卡顿期间的调度情况:
|
||||
- **Running**:绿色,表示正在执行。如果占比高,说明它在忙于自己的计算。
|
||||
- **Runnable**:蓝色或深色,表示它想运行但CPU被其他任务抢占。这指向**CPU资源竞争**。
|
||||
- **Sleep/Uninterruptible Sleep**:灰色,表示它在等待(锁、IO、Binder)。这指向**锁竞争或IO阻塞**。
|
||||
3. **寻找“大胃王”**:按CPU占用排序,找出在问题时间段内抢占CPU的高负载进程(如媒体编解码、后台压缩任务)。
|
||||
|
||||
#### 3.2.2 内存压力检测
|
||||
- **工具**:`bugreport.txt` 中的 `meminfo` 部分,`dumpsys meminfo`,以及 `kernel.log`。
|
||||
- **操作**:在`bugreport`中搜索`Total RAM`和`Free Memory`。
|
||||
- **关注点**:
|
||||
1. **可用内存低**:`MemFree` + `Cached` 是否远低于总内存的20%?
|
||||
2. **Low Memory Killer(LMK) 日志**:在 `kernel.log` 或 `logcat` 中搜索 `lowmemorykiller` 或 `lmk`。频繁的LMK杀进程是内存严重不足的铁证。
|
||||
```
|
||||
[ pid= 937, uid= 123] (com.android.systemui) 杀进程以回收内存
|
||||
```
|
||||
3. **Zone Normal 水位**:在`dmesg`中查看`zoneinfo`,判断`normal` zone是否频繁进入`min`水位以下,触发直接或kswapd内存回收。内存回收(尤其是直接回收)是巨大的性能开销。
|
||||
|
||||
#### 3.2.3 I/O阻塞分析
|
||||
- **工具**:Perfetto (添加`f2fs`/`ext4` 和 `block` 数据源)。
|
||||
- **操作**:在Perfetto UI中放大问题时间窗口。
|
||||
- **关注点**:
|
||||
1. **进程状态为Uninterruptible Sleep (D状态)**:在进程的“State”轨道中,如果大量出现灰色(且工具提示为 `D`),表示进程正在执行不可中断的I/O等待。
|
||||
2. **Binder事务延迟**:查看Binder相关trace事件(`binder_transaction`, `binder_reply`)。在问题进程的Binder线程中,是否存在长时间未完成的Binder调用?调用方和被调用方分别是谁?
|
||||
3. **I/O操作**:打开`ftrace`事件中的`f2fs/iostat`或`block_rq_issue`,查看是否有大量或超大的块读写请求发生。
|
||||
|
||||
### 3.3 第三步:进程内细粒度分析 (30-60分钟)
|
||||
在排除或确认系统瓶颈后,深入分析问题进程自身的执行流。
|
||||
|
||||
#### 3.3.1 ANR根因分析
|
||||
- **工具**:`traces.txt` 文件,Perfetto。
|
||||
- **操作**:
|
||||
1. 在`traces.txt`中找到发生ANR的进程及其主线程的堆栈。
|
||||
2. **关键时间线分析 (P0要求)**:结合Perfetto,精确到微秒级,重建事件序列:
|
||||
- **T0**: Input事件发送到system_server。
|
||||
- **T1**: system_server将该事件分发给目标应用进程(通过Binder)。
|
||||
- **T2**: 目标应用主线程收到Binder调用,开始处理。
|
||||
- **T3 (ANR触发点)**: 5秒后,system_server未收到处理完成的信号,输出ANR。
|
||||
3. **调用栈深度分析 (P0要求)**:`traces.txt`中主线程的栈顶往往就是“罪魁祸首”。
|
||||
- **常见情况**:
|
||||
- **锁等待**:`waiting to lock <0x...>`,表明主线程在等待一个被其他线程持有的锁。需找到持锁线程的堆栈。
|
||||
- **Binder调用**:正在执行一个Binder同步调用,等待其他进程(通常是system_server)返回。需分析被调用端的耗时。
|
||||
- **自身耗时**:在应用代码或某系统库函数中循环,如复杂的布局、Json解析、数据库查询。
|
||||
|
||||
#### 3.3.2 UI Jank/渲染卡顿分析
|
||||
- **工具**:Perfetto (启用`vsync`, `gfx`, `input`)。
|
||||
- **操作**:
|
||||
1. 找到`SurfaceFlinger`和应用的`RenderThread`轨道。
|
||||
2. 分析一帧的完整流水线:
|
||||
- **App主线程 (UI Thread)**:处理Input,执行`onMeasure`, `onLayout`, `onDraw`。耗时过长会导致帧错过vsync。
|
||||
- **RenderThread**:将DisplayList绘制指令提交给GPU。耗时可能由复杂绘制指令引起。
|
||||
- **hwui**:等待GPU完成。可能由GPU负载过高或驱动问题引起。
|
||||
- **SurfaceFlinger**:合成各应用图层。如果此阶段耗时,可能是合成层数过多(Overdraw)或硬件合成器(HWC)能力不足。
|
||||
3. **定位最慢阶段**:哪一阶段的彩色条形图最长?哪个阶段导致了帧截止时间(红色垂直线)的错过?
|
||||
|
||||
### 3.4 第四步:关联分析与根因确认 (15分钟)
|
||||
将以上各步骤的发现关联起来,形成完整证据链。
|
||||
|
||||
- **案例**:初步定位有ANR,第二步发现内存紧张,LMK频繁杀进程。第三步发现被杀进程恰好是ANR进程依赖的一个关键服务(如ContentProvider所在进程)。那么,**根本原因**就不是主线程阻塞,而是**系统内存不足导致依赖服务被回收**,从而造成调用方ANR。
|
||||
- **验证**:查看ANR发生前,`logcat`中是否有`lmk`杀掉关键服务的日志?查看`traces.txt`中主线程是否在`binderTransaction`等待一个不存在的进程返回?
|
||||
|
||||
## 四、问题分类与优先级判定
|
||||
|
||||
根据分析结果,对问题进行定级。
|
||||
|
||||
- **P0级 (阻塞性)**:
|
||||
- **标准**:ANR持续时间 > 5秒,或连续丢帧 > 16帧。
|
||||
- **示例**:任何用户可见的ANR;启动应用时黑屏/白屏超过5秒;滑动列表持续卡顿。
|
||||
- **P1级 (严重)**:
|
||||
- **标准**:UI响应延迟 > 100ms,或内存泄漏 > 50MB,或发生系统服务重启。
|
||||
- **示例**:点击按钮后200ms才有反应;后台长期占用大量内存导致其他应用被LMK;system_server或surfaceflinger重启。
|
||||
- **P2级 (一般)**:
|
||||
- **标准**:单次操作偶尔延迟,或资源占用在极端场景下才出现问题。
|
||||
- **示例**:首次打开相册慢;在某些特定网络条件下,列表图片加载慢。
|
||||
|
||||
## 五、交付成果与质量标准
|
||||
|
||||
### 5.1 核心交付物:性能分析报告 (示例模板)
|
||||
|
||||
```markdown
|
||||
## 问题概述
|
||||
- **现象**:在视频播放界面快速滑动评论区时,界面严重卡顿,帧率降至个位数。
|
||||
- **影响范围**:视频类应用 `com.media.video` (PID: 9876) ,Android 12 设备。
|
||||
- **P等级**:P1 (严重 UI Jank)
|
||||
|
||||
## 根因分析
|
||||
### 关键时间线
|
||||
在Perfetto trace的时间窗口 `12:05:30.000` - `12:05:32.000` 内:
|
||||
|
||||
1. **UI Thread (TID 9876)**:在 `RecyclerView` 的 `onBindViewHolder` 中调用了 `MediaMetadataUtils.getAlbumArt()`,耗时约 **45ms**。
|
||||
2. **RenderThread (TID 9882)**:主线程耗时导致错过了 `Choreographer` 的 `doFrame` 回调,无法开始下一帧的绘制。
|
||||
3. **后续帧**:UI Thread 在多个连续的 `doFrame` 中均耗时 > 30ms,导致连续丢帧超过30帧。
|
||||
*(此处应插入Perfetto标记帧超时的截图)*
|
||||
|
||||
### 调用栈深度分析
|
||||
通过 `simpleperf` 对主线程采样,发现 `getAlbumArt()` 中的耗时操作最终调用到了 `BitmapFactory.decodeStream()` 从本地文件系统加载一张较大分辨率的图片。
|
||||
*(此处应插入调用栈火焰图或堆栈列表)*
|
||||
- **层1 (应用层)**:`com.media.video.adapter.CommentAdapter.onBindViewHolder`
|
||||
- **层2 (应用层)**:`com.media.video.utils.MediaMetadataUtils.getAlbumArt`
|
||||
- **层3 (Framework)**:`android.graphics.BitmapFactory.decodeStream`
|
||||
- **层4 (Native)**:`libskia.so` 中的 `SkCodec::getPixels`,显示正在解码一张 JPEG 图片。
|
||||
- **根因**:`RecyclerView` 滑动时,主线程同步解码磁盘上的高清图片,阻塞了UI绘制。
|
||||
|
||||
## 优化建议
|
||||
### 代码修改建议
|
||||
1. **异步加载**:使用 `Glide` 或 `Coil` 等图片加载库,在后台线程异步加载评论区的用户头像和缩略图。
|
||||
```kotlin
|
||||
// 原代码 (可能导致卡顿)
|
||||
// val bitmap = MediaMetadataUtils.getAlbumArt(context, mediaId)
|
||||
// holder.albumArt.setImageBitmap(bitmap)
|
||||
|
||||
// 优化后代码 (使用Coil)
|
||||
holder.albumArt.load(MediaMetadataUtils.getAlbumArtUri(mediaId)) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.placeholder)
|
||||
}
|
||||
```
|
||||
2. **图片压缩**:如果必须直接解码,确保使用 `BitmapFactory.Options` 进行采样,降低加载到内存中的图片尺寸,减少解码时间。
|
||||
|
||||
### 配置调整方案
|
||||
- 无。此问题主要源于应用代码,无需系统配置调整。
|
||||
|
||||
## 支持证据
|
||||
1. `perfetto_trace_jank.html` (已标记问题区间)
|
||||
2. `trace_ui_thread_jank.txt` (主线程堆栈片段)
|
||||
3. `cpu_profile.data` (Simpleperf性能采样数据)
|
||||
```
|
||||
|
||||
### 5.2 可复现分析脚本
|
||||
创建自动化脚本来提取关键指标,例如:
|
||||
- `extract_anr_traces.py`:自动解析`traces.txt`和`logcat`,生成ANR摘要报告。
|
||||
- `perfetto_metric.sh`:调用Perfetto命令行工具,计算指定时间段内的平均帧率、Jank次数。
|
||||
|
||||
### 5.3 质量检查清单
|
||||
在提交报告前,使用以下清单进行自查:
|
||||
|
||||
- [x] **P0/P1问题全覆盖**:所有ANR、严重卡顿均有关联分析。
|
||||
- [x] **根因精确**:是否定位到具体函数/代码行/系统配置?(例如:`com.example.app$MyClass.onClick (line: 250)`, 而不是“主线程阻塞”)
|
||||
- [x] **证据链完整**:是否从现象、日志、trace到代码修改建议形成了逻辑闭环?
|
||||
- [x] **数据量化**:优化前后的性能对比是否有数据支撑?(例如:帧率从15fps提升到55fps,内存占用降低80MB)
|
||||
- [x] **报告规范**:报告格式是否符合Markdown模板要求?关键部分是否包含截图?
|
||||
- [x] **可复现性**:分析脚本是否能在Linux/macOS环境顺利运行?依赖是否清晰?
|
||||
Reference in New Issue
Block a user