文档更新
This commit is contained in:
16
.obsidian/workspace.json
vendored
16
.obsidian/workspace.json
vendored
@@ -41,12 +41,12 @@
|
||||
"state": {
|
||||
"type": "markdown",
|
||||
"state": {
|
||||
"file": "docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md",
|
||||
"file": "docs/学习笔记/两份提示词对比.md",
|
||||
"mode": "source",
|
||||
"source": false
|
||||
},
|
||||
"icon": "lucide-file",
|
||||
"title": "ai提示词常用命令"
|
||||
"title": "两份提示词对比"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -214,8 +214,13 @@
|
||||
},
|
||||
"active": "59894255df52f532",
|
||||
"lastOpenFiles": [
|
||||
"docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md",
|
||||
"docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md",
|
||||
"docs/学习笔记/两份提示词对比.md",
|
||||
"docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md",
|
||||
"docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md",
|
||||
"docs/学习笔记/Android性能分析标准化操作手册.md",
|
||||
"docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md",
|
||||
"docs/Obsidian笔记体系/Projects/知你-调测/知你--调测.md",
|
||||
"docs/Obsidian笔记体系/Projects/aitsc",
|
||||
"docs/Obsidian笔记体系/Daily/2026-01-14.md",
|
||||
"docs/Obsidian笔记体系/Daily/2024-06-02.md",
|
||||
@@ -236,11 +241,6 @@
|
||||
"docs/学习笔记/产品经理/数据分析详解.md",
|
||||
"docs/学习笔记/产品经理/产品运营详解.md",
|
||||
"docs/学习笔记/产品经理/产品设计详解.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图形系统、输入系统的必备知识。
|
||||
319
docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md
Normal file
319
docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md
Normal file
@@ -0,0 +1,319 @@
|
||||
好的,遵照您的指示,我将基于您提供的详细提示词和搜索材料,为您创作一份《Android WindowManagerService核心原理深度解析》培训课件。
|
||||
|
||||
---
|
||||
|
||||
# Android WindowManagerService核心原理深度解析
|
||||
|
||||
## 目录
|
||||
1. **引言**:为什么需要深入理解WMS?
|
||||
2. **WMS概览与架构定位**
|
||||
- 系统服务中的WMS
|
||||
- 核心概念定义 (`Window`, `Token`, `State`)
|
||||
- WMS核心职责
|
||||
3. **窗口的生命周期管理**
|
||||
- 从`ViewRootImpl`到`WindowState`的旅程
|
||||
- 关键流程序列图:添加窗口
|
||||
- 核心数据结构:`WindowState` 解析
|
||||
4. **布局、动画与Surface管理**
|
||||
- 布局循环的核心:`performSurfacePlacement`
|
||||
- 窗口动画的管理者:`WindowAnimator`
|
||||
- Surface的申请与合成协调
|
||||
5. **输入事件的中转与分发**
|
||||
- WMS作为IMS的中转站
|
||||
- 关键流程序列图:触摸事件分发
|
||||
- 命中测试(Hit Test)与`InputChannel`
|
||||
6. **与关键系统服务的协同**
|
||||
- WMS & AMS:Activity状态与窗口的同步
|
||||
- WMS & SurfaceFlinger:通过`Transaction`提交变更
|
||||
- UML简化类图:核心协作关系
|
||||
7. **高级主题与案例分析**(可选)
|
||||
- 从`dumpsys window`看窗口状态
|
||||
- 常见问题分析:窗口泄漏、事件丢失
|
||||
8. **总结与Q&A**
|
||||
|
||||
---
|
||||
|
||||
## 1. 引言:为什么需要深入理解WMS?
|
||||
|
||||
各位同事,在日常开发中,我们面对的挑战往往不止于业务逻辑的实现。当遇到**多窗口适配的诡异布局**、**悬浮窗权限的反复横跳**、**应用启动或转场时的莫名卡顿**,甚至是**触摸事件偶尔“灵异”丢失**的问题时,我们是否感到束手无策,只能依靠试错或搜索碎片化的解决方案?
|
||||
|
||||
这些复杂UI场景和性能瓶颈的根源,往往深藏在Android Framework的核心——**WindowManagerService(WMS)** 之中。WMS是Android图形系统的“总调度中心”和“交通警察”,它管理者所有窗口的创建、布局、显示顺序以及它们与输入系统的交互。
|
||||
|
||||
本次培训旨在穿透API层面,深入Framework核心,系统性地揭示从**应用视图请求**到**最终像素显示**的完整链路。我们将聚焦WMS在其中扮演的核心角色,帮助大家建立起完整的窗口系统知识体系,从而在面对疑难杂症时,能够**定位根因、精准施策**。
|
||||
|
||||
## 2. WMS概览与架构定位
|
||||
|
||||
### 2.1 系统服务中的WMS
|
||||
|
||||
WMS是System Server中最早启动的核心服务之一。它在SystemServer的`startOtherServices`方法中被创建和启动。
|
||||
|
||||
```java
|
||||
// frameworks/base/services/java/com/android/server/SystemServer.java
|
||||
private void startOtherServices() {
|
||||
// ...
|
||||
traceBeginAndSlog("StartWindowManagerService");
|
||||
// 注意:WMS的main方法运行在"android.display"线程,优先级高于"system_server"线程
|
||||
wm = WindowManagerService.main(context, inputManager, ...);
|
||||
traceEnd();
|
||||
// ...
|
||||
// 将WMS和IMS注册到ServiceManager
|
||||
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
|
||||
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
*代码示例:WMS在SystemServer中的启动 *
|
||||
|
||||
WMS的启动涉及多线程同步(`system_server`, `android.display`, `android.ui`),体现了其初始化对UI相关线程的优先级要求。
|
||||
|
||||
### 2.2 核心概念定义
|
||||
|
||||
在深入代码前,我们必须统一对以下几个核心抽象的理解:
|
||||
|
||||
- **`Window`(窗口)**:一个抽象的概念,不是实体,可以理解为**绘制表面(Surface)** 的载体或容器。每一个窗口都对应着一个用于绘制的`Surface`。在应用层,`PhoneWindow`是`Window`的唯一实现类。
|
||||
- **`WindowManager`**:应用层与WMS通信的接口。它是一个Binder客户端,应用通过它发送添加、删除窗口的请求。其实现类`WindowManagerImpl`将工作委托给单例的`WindowManagerGlobal`。
|
||||
- **`View`**:窗口的实体表现形式。窗口的内容正是通过`View`树来填充和呈现的。可以说,**Window是View的载体,View是Window的内容**。
|
||||
- **`WindowToken`**:一个**令牌**。用于建立Binder通信的映射关系,并**将一组相关的窗口(例如同一个Activity的所有窗口,包括主窗口和子窗口)组织在一起**。对于Activity,它是`AppWindowToken`。
|
||||
- **`WindowState`**:WMS内部用于**描述一个窗口所有状态**的真实对象。它保存了窗口的尺寸、位置、Z-order、是否可见、对应的`Session`、`InputChannel`等所有信息。它是WMS管理窗口的“活档案”。
|
||||
- **`DisplayContent`**:用于**描述一块逻辑屏幕**。它管理着显示在这个屏幕上的所有窗口的根层级。WMS通过`DisplayContent`来管理多屏(如双屏、折叠屏)的窗口。
|
||||
|
||||
### 2.3 WMS核心职责
|
||||
|
||||
1. **窗口管理**:负责窗口的添加、删除、Z-order排序、焦点管理。
|
||||
2. **Surface管理**:作为“仲裁者”,为窗口分配`Surface`,并协调应用与`SurfaceFlinger`的关系。
|
||||
3. **窗口动画**:管理所有窗口切换、移动时的动画效果。
|
||||
4. **输入事件中转**:配合`InputManagerService`,将触摸或按键事件准确地分发给合适的窗口。
|
||||
|
||||
## 3. 窗口的生命周期管理
|
||||
|
||||
### 3.1 从`ViewRootImpl`到`WindowState`的旅程
|
||||
|
||||
以一个典型的Activity窗口创建为例,看看它是如何被WMS纳入管理的。
|
||||
|
||||
**核心流程序列图:添加窗口**
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as 应用进程(UI Thread)
|
||||
participant ViewRoot as ViewRootImpl
|
||||
participant WMS as WindowManagerService
|
||||
participant SF as SurfaceFlinger
|
||||
|
||||
App->>ViewRoot: 1. requestLayout()
|
||||
ViewRoot->>ViewRoot: 2. scheduleTraversals()
|
||||
ViewRoot->>WMS: 3. relayoutWindow() (Binder调用)
|
||||
Note over WMS: 4. 创建/获取Surface
|
||||
WMS->>SF: 5. createSurface() (通过SurfaceSession)
|
||||
SF-->>WMS: 返回SurfaceControl
|
||||
WMS-->>ViewRoot: 返回relayoutResult & Surface (Binder返回)
|
||||
Note over ViewRoot: 6. 将Surface保存到mSurface
|
||||
ViewRoot->>ViewRoot: 7. performMeasure/Layout/Draw
|
||||
ViewRoot->>SF: 8. 通过Canvas/Skia渲染到Surface的BufferQueue
|
||||
```
|
||||
|
||||
**关键步骤详解:**
|
||||
|
||||
1. **发起请求**:当View树需要布局时,`ViewRootImpl`的`requestLayout`被调用,最终通过Binder调用进入WMS的`relayoutWindow`方法。
|
||||
2. **WMS处理 (核心逻辑)**:
|
||||
- **权限与合法性检查**:WMS首先检查调用者的权限和窗口参数(`LayoutParams`)是否合法。
|
||||
- **创建/获取WindowState**:如果是首次添加,WMS会创建一个`WindowState`对象,并将其添加到全局的`mWindowMap`中,同时根据`WindowToken`建立层级关系。
|
||||
- **Surface分配**:如果窗口需要新的绘图表面,WMS会通过`Session`(内部封装了与SurfaceFlinger的`SurfaceSession`连接)请求`SurfaceFlinger`创建一个`Layer`(在WMS侧对应为`SurfaceControl`)。
|
||||
3. **返回与绘制**:WMS将分配好的`Surface`(实际上是`SurfaceControl`的句柄)通过Binder返回给应用层的`ViewRootImpl`。应用层拿到`Surface`后,便可以开始渲染内容。
|
||||
|
||||
### 3.2 核心数据结构:`WindowState` 解析
|
||||
|
||||
`WindowState`是WMS中最重要的数据结构,我们可以通过简化源码来理解其核心成员:
|
||||
|
||||
```java
|
||||
// frameworks/base/services/core/java/com/android/server/wm/WindowState.java
|
||||
class WindowState extends WindowContainer<WindowState> {
|
||||
// 指向持有该窗口的客户端Session (每个应用进程一个)
|
||||
final Session mSession;
|
||||
// 应用层 ViewRootImpl 的 W Binder对象,用于向应用进程通信
|
||||
final IWindow mClient;
|
||||
// 与该窗口关联的窗口令牌 (如 AppWindowToken)
|
||||
final WindowToken mToken;
|
||||
// 窗口的布局参数 (x, y, width, height, type, flags...)
|
||||
final WindowManager.LayoutParams mAttrs;
|
||||
// 窗口所属的逻辑屏幕
|
||||
final DisplayContent mDisplayContent;
|
||||
// 窗口在WMS侧控制的Surface句柄,用于设置位置、大小、层级等
|
||||
SurfaceControl mSurfaceControl;
|
||||
// 接收输入事件的通道
|
||||
InputChannel mInputChannel;
|
||||
// 窗口的帧大小和位置信息 (包含四个矩形:包含框架、内容框架、可见框架等)
|
||||
final Rect mFrame = new Rect();
|
||||
final Rect mContentFrame = new Rect();
|
||||
// 窗口当前的Z-order 值
|
||||
int mLayer;
|
||||
// ... 以及其他大量状态和方法
|
||||
}
|
||||
```
|
||||
*代码示例:WindowState核心成员变量 *
|
||||
|
||||
`WindowState`不仅保存了窗口的属性,其本身也参与到`WindowContainer`的层级管理中,构成了WMS内部对窗口树的描述。
|
||||
|
||||
## 4. 布局、动画与Surface管理
|
||||
|
||||
### 4.1 布局循环的核心:`performSurfacePlacement`
|
||||
|
||||
WMS的核心工作引擎是`performSurfacePlacement`(旧称`performLayoutAndPlaceSurfacesLocked`)。这个方法会在窗口状态发生变化(如添加窗口、更改大小、动画更新)时被触发。
|
||||
|
||||
**它的主要任务**是遍历所有`DisplayContent`及其下的`WindowState`,计算每个窗口最终的**位置、大小和Z-order**。然后,它会将这些变更通过`SurfaceControl.Transaction`提交给SurfaceFlinger。
|
||||
|
||||
### 4.2 窗口动画的管理者:`WindowAnimator`
|
||||
|
||||
WMS内部有一个`WindowAnimator`对象,专门负责管理所有窗口动画。
|
||||
- 当一个窗口需要执行动画(如启动、退出、旋转)时,WMS会创建相应的动画对象(如`MoveAnimation`, `AlphaAnimation`)。
|
||||
- `WindowAnimator`会定期(在VSync信号驱动下)更新动画状态,并再次触发`performSurfacePlacement`,将动画过程中的新位置、透明度等属性通过`Transaction`设置给SurfaceFlinger,从而实现流畅的动画效果。
|
||||
|
||||
### 4.3 Surface的申请与合成协调
|
||||
|
||||
WMS并不负责绘制,它只负责**“布局”和“定序”**。
|
||||
- **应用侧Surface**:由`ViewRootImpl`持有,用于应用线程绘制UI。
|
||||
- **WMS侧SurfaceControl**:由`WindowState`持有,用于WMS控制该窗口在屏幕上的最终呈现属性。
|
||||
- **与SurfaceFlinger的交互**:WMS通过一系列的`Transaction`,将窗口的**位置、大小、旋转、透明度、Z-order**等信息同步给SurfaceFlinger。SurfaceFlinger则根据这些信息,将所有窗口的Surface(图形缓冲区)进行混合(Compose),最终输出到屏幕硬件。
|
||||
|
||||
## 5. 输入事件的中转与分发
|
||||
|
||||
### 5.1 WMS作为IMS的中转站
|
||||
|
||||
`InputManagerService`(IMS)负责从内核读取原始输入事件,但它不知道这些事件该发给谁。这时,WMS作为窗口的管理者,就成为了理想的“导航员”。
|
||||
|
||||
### 5.2 关键流程序列图:触摸事件分发
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant IMS as InputManagerService
|
||||
participant WMS as WindowManagerService
|
||||
participant Dispatcher as InputDispatcher (Native)
|
||||
participant AppView as 目标窗口 (View)
|
||||
|
||||
IMS->>Dispatcher: 1. 读取并派发事件
|
||||
Dispatcher->>WMS: 2. 查询目标窗口 (通过InputMonitor)
|
||||
WMS-->>Dispatcher: 3. 返回焦点窗口及其InputChannel
|
||||
Dispatcher->>Dispatcher: 4. 命中测试,确定目标
|
||||
Dispatcher->>AppView: 5. 通过InputChannel发送事件 (Socket)
|
||||
AppView->>AppView: 6. ViewRootImpl$WindowInputEventReceiver 处理
|
||||
AppView->>AppView: 7. 事件分发 (dispatchTouchEvent)
|
||||
```
|
||||
|
||||
### 5.3 命中测试与`InputChannel`
|
||||
|
||||
1. **建立通道**:在窗口添加时(`WMS.addWindow`),如果窗口需要接收输入,WMS会为它创建一对`InputChannel`(基于Socket)。服务端的一端注册到IMS的`InputDispatcher`,客户端的一端通过Binder返回给`ViewRootImpl`,并封装成`WindowInputEventReceiver`。
|
||||
```java
|
||||
// WMS.addWindow 中创建InputChannel的片段
|
||||
if (outInputChannel != null && (attrs.inputFeatures & ...) == 0) {
|
||||
String name = win.makeInputChannelName();
|
||||
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); // 创建Socket对
|
||||
win.setInputChannel(inputChannels[0]);
|
||||
inputChannels[1].transferTo(outInputChannel); // 将客户端Channel返回
|
||||
mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle); // 注册服务端Channel
|
||||
}
|
||||
```
|
||||
*代码示例:InputChannel的创建与注册 *
|
||||
|
||||
2. **命中测试**:当IMS的`InputDispatcher`有事件到来时,它会调用WMS(通过`InputMonitor`)获取当前所有窗口的信息(Z-order、位置、焦点状态)。`InputDispatcher`执行**命中测试**,找到最合适的窗口,然后通过之前建立的Socket连接(`InputChannel`)直接将事件发送给目标应用进程。
|
||||
|
||||
3. **应用内处理**:应用进程的`WindowInputEventReceiver`收到事件后,将其传递给`ViewRootImpl`,进而开始我们在应用层熟悉的`View`树事件分发流程。
|
||||
|
||||
## 6. 与关键系统服务的协同
|
||||
|
||||
### 6.1 WMS & AMS:状态同步
|
||||
|
||||
AMS和WMS是一对紧密协作的“好基友”。以Activity启动为例:
|
||||
1. **AMS准备**:AMS在启动一个Activity时,首先会调用WMS的`addAppToken`,告诉WMS:“我要启动一个Activity了,这是它的令牌(`AppWindowToken`)”。WMS将其加入到`mAppTokens`列表中。
|
||||
2. **WMS同步**:当Activity在应用进程创建完毕,并通过`ViewRootImpl`请求添加窗口时,WMS会通过`AppWindowToken`找到这个窗口属于哪个Activity,并将其与已有的`Token`关联起来。
|
||||
3. **状态回调**:当窗口显示或隐藏时,WMS也会回调AMS的方法,通知它窗口的`finishedStarting`、`finishedPausing`等状态,以便AMS继续执行生命周期的下一步。
|
||||
|
||||
### 6.2 UML简化类图:核心协作关系
|
||||
|
||||
下图展示了WMS、AMS及其核心内部类与周边服务的关系,有助于建立宏观印象。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class WindowManagerService {
|
||||
- InputManagerService mInputManager
|
||||
- ActivityManagerService mActivityManager
|
||||
- WindowAnimator mAnimator
|
||||
- HashMap~IBinder, WindowState~ mWindowMap
|
||||
- ArrayList~AppWindowToken~ mAppTokens
|
||||
+ addWindow()
|
||||
+ relayoutWindow()
|
||||
+ performSurfacePlacement()
|
||||
}
|
||||
|
||||
class ActivityManagerService {
|
||||
+ startActivity()
|
||||
+ resumeTopActivity()
|
||||
}
|
||||
|
||||
class InputManagerService {
|
||||
- InputDispatcher mDispatcher
|
||||
+ registerInputChannel()
|
||||
}
|
||||
|
||||
class SurfaceFlinger {
|
||||
+ createSurface()
|
||||
+ setTransaction()
|
||||
}
|
||||
|
||||
class WindowState {
|
||||
- IWindow mClient
|
||||
- WindowToken mToken
|
||||
- SurfaceControl mSurfaceControl
|
||||
- InputChannel mInputChannel
|
||||
}
|
||||
|
||||
class AppWindowToken {
|
||||
- IApplicationToken mAppToken
|
||||
}
|
||||
|
||||
class DisplayContent {
|
||||
- int mDisplayId
|
||||
- WindowList mWindows
|
||||
}
|
||||
|
||||
WindowManagerService --> ActivityManagerService : 持有引用
|
||||
WindowManagerService --> InputManagerService : 持有引用
|
||||
WindowManagerService --> SurfaceFlinger : Binder通信
|
||||
WindowManagerService *-- WindowAnimator
|
||||
WindowManagerService *-- WindowState : 管理 (mWindowMap)
|
||||
WindowManagerService *-- AppWindowToken : 管理 (mAppTokens)
|
||||
WindowManagerService *-- DisplayContent : 管理
|
||||
WindowState --> WindowToken
|
||||
AppWindowToken --|> WindowToken
|
||||
DisplayContent --> WindowState : 包含
|
||||
```
|
||||
|
||||
## 7. 高级主题与案例分析(可选)
|
||||
|
||||
### 7.1 从`dumpsys window`看窗口状态
|
||||
|
||||
`adb shell dumpsys window`是我们调试窗口问题的利器。通过它,我们可以看到:
|
||||
- **`Display`信息**:当前有几个逻辑屏幕,分辨率如何。
|
||||
- **`Window Hierarchy`**:当前所有窗口的堆叠顺序,最上层的是焦点窗口。
|
||||
- **`WindowState`详情**:每个窗口的包名、token、`mSurfaceControl`是否有效、`mFrame`坐标、`mInputChannel`状态等。
|
||||
- **`Focus`**:当前焦点窗口是哪个。
|
||||
|
||||
**案例分析:** 当遇到窗口无法显示时,可以通过`dumpsys window`查看对应`WindowState`是否存在。如果存在,检查`mSurfaceControl`是否有效;如果不存在,说明窗口添加失败,可能是权限或token问题。
|
||||
|
||||
### 7.2 常见问题分析
|
||||
|
||||
- **问题1:悬浮窗无法显示或显示位置不对**
|
||||
- **可能原因**:未正确申请`SYSTEM_ALERT_WINDOW`权限;LayoutParams中的`type`设置错误;在Android 10+上,未对`Display`进行适配。
|
||||
- **WMS视角**:检查`dumpsys window`中该悬浮窗的`WindowState`是否存在,以及其`mFrame`和`mAttrs.gravity`是否符合预期。
|
||||
|
||||
- **问题2:触摸事件“被窃取”或无法传递到目标View**
|
||||
- **可能原因**:有其它窗口(如系统弹窗、输入法窗口)覆盖在目标窗口之上,但被设置为可触摸(`FLAG_NOT_TOUCH_MODAL`处理不当);焦点窗口不是你期望的那个。
|
||||
- **WMS视角**:通过`dumpsys window`查看`Window Hierarchy`,确认Z-order的顺序。点击事件会首先发给Z-order最大的窗口。检查焦点窗口`Focus`是否正确。
|
||||
|
||||
## 8. 总结与Q&A
|
||||
|
||||
### 总结
|
||||
WMS作为Android GUI系统的“大脑”,其核心价值在于:
|
||||
1. **集中管理**:通过`WindowState`、`WindowToken`和`DisplayContent`,统一维护所有窗口的状态和层级。
|
||||
2. **协调调度**:作为中心枢纽,协调应用进程的绘制请求(`relayoutWindow`)、输入系统的派发请求(命中测试)以及SurfaceFlinger的合成请求(`Transaction`)。
|
||||
3. **状态同步**:与AMS深度绑定,确保Activity的生命周期与窗口的显示状态同步。
|
||||
|
||||
深入理解WMS,不仅能帮助我们诊断疑难杂症,更能让我们对整个Android系统的设计哲学有更深刻的体会。
|
||||
254
docs/学习笔记/Android性能分析标准化操作手册.md
Normal file
254
docs/学习笔记/Android性能分析标准化操作手册.md
Normal file
@@ -0,0 +1,254 @@
|
||||
好的,遵照您的指示,我将以一名资深的Android移动应用开发工程师的身份,为您制定一份系统、专业且实操性强的**Android性能分析标准化操作手册**。本方案将严格遵循您提出的技术背景、规范、标准、约束及交付要求。
|
||||
|
||||
---
|
||||
|
||||
|
||||
**版本:** 1.0
|
||||
**适用对象:** Android应用开发工程师、测试工程师
|
||||
**目标:** 建立一套标准化、可复现的性能分析流程,系统性地识别和定位Android应用中的性能瓶颈,为优化工作提供精确的数据支撑。
|
||||
|
||||
---
|
||||
|
||||
## 1. 环境配置指南
|
||||
|
||||
在进行任何性能分析之前,必须确保环境配置正确,以保证数据的准确性和工具的有效性。
|
||||
|
||||
### 1.1 开发者选项与调试模式
|
||||
- **开启开发者选项:** 设置 -> 关于手机 -> 连续点击“版本号”7次。
|
||||
- **开启USB调试:** 开发者选项 -> USB调试。
|
||||
- **开启“不锁定屏幕”:** 开发者选项 -> 不锁定屏幕(便于长时间测试)。
|
||||
- **选择“ART”/“Ahead-of-Time”编译(可选):** 开发者选项 -> 选择运行环境。对于性能分析,建议使用ART,并在测试前通过 `adb shell cmd package compile -f -m speed <package_name>` 对目标应用进行全量AOT编译,以获得更接近用户真实安装后的性能表现。
|
||||
- **启用“点按显示点按操作反馈”和“指针位置”:** 开发者选项,用于记录用户交互轨迹,辅助分析卡顿与操作的关联。
|
||||
|
||||
### 1.2 Android Studio Profiler 配置
|
||||
- **版本要求:** Android Studio 4.0+。
|
||||
- **连接设备:** 通过USB连接开启调试模式的设备,或使用Wi-Fi调试 (Android 11+)。
|
||||
- **选择进程:** 在Profiler窗口中找到并选择你的应用进程。
|
||||
- **高级分析:** 对于CPU、内存、网络等分析,务必开启“Advanced Profiling”以获取更详细的方法调用和分配信息。这会引入额外开销,适合在内部测试时使用。
|
||||
|
||||
### 1.3 系统权限与命令行工具
|
||||
- **确保adb可用:** 将 `adb` 所在目录(`$ANDROID_HOME/platform-tools`)添加到系统环境变量PATH中。
|
||||
- **Root权限(可选但非必须):** 大多数性能分析不需要Root。但如果需要分析系统服务、内核事件或使用`trace`命令,Root权限会更方便。
|
||||
- **Perfetto配置:** Android 9+ 推荐使用Perfetto。通过 `adb shell perfetto` 命令进行配置,或通过开发者选项中的“系统跟踪”启用。
|
||||
|
||||
---
|
||||
|
||||
## 2. 分场景性能分析流程
|
||||
|
||||
### 2.1 卡顿分析(Jank Detection)
|
||||
|
||||
**目标:** 定位导致UI线程阻塞、掉帧的具体代码和原因。
|
||||
|
||||
#### 标准流程:Systrace / Perfetto + Method Tracing
|
||||
|
||||
1. **场景重现与抓取Trace:**
|
||||
- **Systrace (系统跟踪):**
|
||||
- **UI操作:** 在命令行运行 `python systrace.py -t 10 -o mytrace.html sched freq idle am wm gfx view webview`。`-t 10` 表示抓取10秒,时间窗口应覆盖待测操作。
|
||||
- **Perfetto (推荐):** 配置更灵活。可以通过开发者选项中的“系统跟踪”预置标签,或者在代码中使用 `Trace.beginSection()`/`endSection()` 自定义跟踪点。
|
||||
```bash
|
||||
# 启动Perfetto记录,抓取10秒,将结果保存到/trace_output.perfetto-trace
|
||||
adb shell perfetto -o /data/misc/perfetto-traces/trace_output.perfetto-trace -t 10s sched freq idle am wm gfx view
|
||||
# 从设备拉取文件
|
||||
adb pull /data/misc/perfetto-traces/trace_output.perfetto-trace
|
||||
```
|
||||
- **Method Tracing (方法跟踪):**
|
||||
- 在Android Studio Profiler中,选择 **CPU**,然后选择 **Trace Java Methods** (Sampled Java Methods) 或 **Instrumented Java Methods**。前者开销低,适合长时监控;后者开销大,信息全,适合短时精确定位。
|
||||
- 点击录制,复现操作,结束后分析Call Chart。
|
||||
|
||||
2. **数据分析与根因定位:**
|
||||
- **打开Trace文件:** Systrace (.html) 用Chrome打开 `chrome://tracing/`;Perfetto (.perfetto-trace) 用 `ui.perfetto.ash` 打开。
|
||||
- **识别掉帧:** 寻找UI Thread上的红色或黄色警报。正常帧绘制应在16.6ms内完成。
|
||||
- **分析耗时操作:** 放大时间轴,查看主线程在掉帧期间正在执行什么方法。
|
||||
- **常见问题:** 复杂的布局测量(`onMeasure`)、过度绘制导致的重复渲染、昂贵的`Bitmap`操作(`decodeResource`)、I/O操作(文件读写、网络请求)、锁竞争(`binder transaction`)、大量对象的创建与GC。
|
||||
- **结合Method Tracing:** 如果在Systrace中发现可疑的方法调用,但看不清细节,可以结合Method Tracing的结果,定位到具体的类和行号。
|
||||
|
||||
3. **高级技巧:BlockCanary**
|
||||
- **集成:** 在`Application`中初始化 `BlockCanary.install(this, new AppBlockCanaryContext()).start();`
|
||||
- **原理:** 利用Looper的`Printer`监控主线程消息执行时间。超过阈值(如1秒)则自动打印堆栈信息,直接指出卡顿代码。
|
||||
|
||||
### 2.2 内存泄漏排查
|
||||
|
||||
**目标:** 检测并定位无法被GC回收的无用对象,防止OOM。
|
||||
|
||||
#### 标准流程:Heap Dump 分析 + LeakCanary
|
||||
|
||||
1. **初步筛查:LeakCanary**
|
||||
- **集成:** 在`build.gradle`中添加 `debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.x'`
|
||||
- **自动化检测:** LeakCanary会自动检测Activity/Fragment/ViewModel等生命周期对象是否在销毁后仍被持有。发现泄漏时,系统通知栏会弹出提示,点击可查看详细的泄漏引用链。
|
||||
- **优势:** 零编码,自动化,直接给出根因路径。
|
||||
|
||||
2. **深度分析:Heap Dump**
|
||||
- **触发Dump:**
|
||||
- **场景A:怀疑有累积性泄漏。** 反复进入和退出同一个Activity多次,然后点击Android Studio Profiler -> Memory -> **Dump Java Heap**。
|
||||
- **场景B:分析某个特定操作后的内存状态。** 执行操作前先记录一个Dump (Baseline),操作后再记录一个Dump,进行对比。
|
||||
- **分析Dump文件 (.hprof):**
|
||||
- Android Studio会自动打开`.hprof`文件并转换。
|
||||
- **主要视图:**
|
||||
- **Class List:** 按类名列出所有存活对象。
|
||||
- **技巧:** 按Shallow Size或Retained Size排序,寻找占用内存最大的对象,往往就是问题所在。
|
||||
- **技巧:** 分析目标类(如MainActivity)的实例数量。如果预期应该只有1个,但实际有多个,且都能通过GC Root访问,则说明存在泄漏。
|
||||
- **Heap Dump Differences:** 对比两个Dump文件,查看哪些类的新增实例最多,这是最直接的泄漏排查方法。
|
||||
- **分析Instance:** 右键点击可疑的实例 -> `Show Instance Fields` 查看其持有的引用。
|
||||
- **查找GC Root:** 选中一个可疑实例 -> 右键 -> `Merge Shortest Paths to GC Root` -> `exclude all phantom/weak/soft etc. references`。这会展示出强引用链,清晰地指出是谁“拉着”这个对象不让它被回收。
|
||||
- **常见GC Root:** 静态变量、活跃线程、JNI全局引用等。
|
||||
|
||||
3. **常见内存泄漏反模式:**
|
||||
- 匿名内部类/Handler持有外部Activity引用。
|
||||
- 未取消注册的`BroadcastReceiver`、`EventBus`订阅者。
|
||||
- 单例对象持有Context引用(应用Context无碍,Activity Context危险)。
|
||||
- 长时间运行的线程或`TimerTask`。
|
||||
- `WebView`引起的泄漏(建议放在独立的进程中运行)。
|
||||
|
||||
### 2.3 启动耗时优化
|
||||
|
||||
**目标:** 缩短从点击图标到用户可交互的时间(冷启动)。
|
||||
|
||||
#### 标准流程:启动阶段Trace分析
|
||||
|
||||
1. **区分启动类型:**
|
||||
- **冷启动:** 应用进程从无到有,从头开始创建。
|
||||
- **热启动:** 应用从后台切换到前台,只需恢复Activity即可。
|
||||
- **温启动:** 介于两者之间,进程存在,但Activity需要重建。
|
||||
|
||||
2. **在代码中埋点:**
|
||||
```kotlin
|
||||
// 在Application.attachBaseContext
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
Trace.beginSection("App Creation")
|
||||
}
|
||||
|
||||
// 在Application.onCreate
|
||||
override fun onCreate() {
|
||||
Trace.beginSection("App Initialization")
|
||||
super.onCreate()
|
||||
// ... 初始化工作 ...
|
||||
Trace.endSection()
|
||||
}
|
||||
|
||||
// 在第一个Activity.onCreate
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Trace.beginSection("FirstActivity Creation")
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
// ... 初始化View和数据 ...
|
||||
Trace.endSection()
|
||||
}
|
||||
```
|
||||
**注意:** 确保在Application和Activity的`onCreate`结束时调用`Trace.endSection()`。
|
||||
|
||||
3. **抓取和分析Trace:**
|
||||
- **命令:** `python systrace.py -t 5 -o boot_trace.html am wm view`。`am`和`wm`标签对分析启动过程至关重要。
|
||||
- **打开Trace文件:**
|
||||
- **寻找起点:** 找到 `进程创建 (Process Creation)` 事件。
|
||||
- **观察主线程:** 关注从`Application`到`Activity`的整个生命周期。
|
||||
- **`Application.onCreate`:** 这段区域是否过长?是否有耗时操作(如IO、网络、大图解码)?这些应该异步化或延迟加载。
|
||||
- **`Activity.onCreate` & `onResume`:** 布局加载(`setContentView`)是否耗时?首帧绘制(`performTraversals`)前做了什么?
|
||||
- **观察`bindApplication`:** 这是AMS通知应用进程初始化的关键,耗时通常与`Application.onCreate`相关。
|
||||
- **观察`scheduleLaunchActivity`到`reportFullyDrawn`:** 这段区域就是启动时间的主要构成。
|
||||
|
||||
4. **启动优化策略:**
|
||||
- **Application优化:** 延迟初始化第三方SDK、使用`ContentProvider`启动优化库(通过`App Startup`库控制初始化顺序和时机)。
|
||||
- **UI优化:** 使用`AsyncLayoutInflater`异步加载非首屏布局,精简布局层级,使用`ConstraintLayout`。
|
||||
- **首页数据优化:** 采用分页加载,优先展示骨架屏,避免在`onCreate`/`onResume`中执行网络请求和数据库查询。
|
||||
|
||||
---
|
||||
|
||||
## 3. 工具使用规范
|
||||
|
||||
| 工具名称 | 适用场景 | 最佳使用时机 | 关键参数/配置 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **CPU Profiler** | 定位方法耗时、热点函数分析 | 1. 开发阶段,怀疑某功能卡顿时<br>2. 配合Systrace,深入分析可疑方法 | `Sampled`: 低开销,宏观分析<br>`Instrumented`: 高精度,微观分析 |
|
||||
| **Memory Profiler** | 排查内存泄漏、分析内存抖动 | 1. 功能测试后<br>2. 出现OOM时<br>3. 配合LeakCanary做深度确认 | 定期点击`Dump Java Heap`并保存,用于对比分析 |
|
||||
| **Network Profiler** | 监控网络请求频率、数据量、耗时 | 1. 页面加载慢<br>2. 流量消耗异常时 | 结合`Inspector`查看请求和响应头、Body |
|
||||
| **Systrace / Perfetto** | 系统级性能瓶颈分析,如渲染、GC、Binder调用 | 1. 卡顿问题<br>2. 启动时间优化<br>3. 分析系统资源竞争 | 选择相关标签 (e.g., `gfx`, `view`, `am`, `input`) |
|
||||
| **LeakCanary** | 自动化内存泄漏检测 | **持续集成**和日常开发调试中 | 默认配置即可,可自定义监听哪些对象的泄漏 |
|
||||
| **adb shell dumpsys** | 获取系统服务状态信息 | 1. 分析内存 (`dumpsys meminfo`)<br>2. 分析电池 (`dumpsys batterystats`)<br>3. 分析CPU (`dumpsys cpuinfo`) | 结合`grep`过滤关键信息 |
|
||||
| **Battery Historian** | 可视化分析电量消耗 | 分析后台耗电异常、WakeLock使用不当 | 导出`bugreport`并上传至Historians |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. 问题排查决策树
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[发现性能问题] --> B{问题类型?}
|
||||
|
||||
B --> C[界面卡顿/掉帧]
|
||||
C --> D[使用Systrace/Perfetto抓取Trace]
|
||||
D --> E{UI线程是否有长时间任务?}
|
||||
E -- 是 --> F[定位到耗时方法<br>CPU Profiler/Method Tracing]
|
||||
F --> G[优化方法逻辑/异步化]
|
||||
E -- 否 --> H{是否存在过度绘制?}
|
||||
H -- 是 --> I[使用Layout Inspector/GPU过度绘制调试<br>优化布局层级/使用ClipRect]
|
||||
H -- 否 --> J{是否频繁GC?}
|
||||
J -- 是 --> K[检查Memory Profiler中对象分配<br>优化临时对象创建/使用对象池]
|
||||
J -- 否 --> D
|
||||
|
||||
B --> L[内存过高/OOM]
|
||||
L --> M[集成LeakCanary]
|
||||
M --> N{是否检测到泄漏?}
|
||||
N -- 是 --> O[根据LeakCanary报告<br>修复引用链]
|
||||
N -- 否 --> P[使用Memory Profiler抓取Heap Dump]
|
||||
P --> Q[分析Retained Size最大的对象<br>或对比不同时间点的Dump]
|
||||
Q --> R{是否存在大量无法解释的实例?}
|
||||
R -- 是 --> S[分析GC Root<br>找到持有者]
|
||||
R -- 否 --> T[检查大对象如Bitmap<br>是否及时释放]
|
||||
|
||||
B --> U[启动速度慢]
|
||||
U --> V[在关键生命周期埋点<br>抓取Systrace]
|
||||
V --> W[分析Application和<br>FirstActivity的onCreate]
|
||||
W --> X[识别耗时初始化任务]
|
||||
X --> Y[延迟加载、异步初始化、<br>App Startup优化]
|
||||
|
||||
B --> Z[耗电快/流量高]
|
||||
Z --> AA[导出BugReport<br>使用Battery Historian/Network Profiler分析]
|
||||
AA --> AB[检查WakeLock、Alarm、后台Service]
|
||||
AB --> AC[优化网络请求/使用合并网络请求]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 性能数据记录与分析模板
|
||||
|
||||
### 性能分析报告模板
|
||||
|
||||
| 项目 | 内容 |
|
||||
| :--- | :--- |
|
||||
| **问题标题** | 首页列表快速滑动严重卡顿 |
|
||||
| **测试环境** | 设备:Pixel 4 (Android 12);应用版本:v2.3.0 (Debug) |
|
||||
| **复现步骤** | 1. 打开应用并进入首页。 2. 以最快速度上下快速滑动列表10次。 3. 观察列表滑动流畅度。 |
|
||||
| **性能数据** | - **帧率:** 平均35fps,存在大量掉帧,最长单帧耗时236ms。 <br> - **Jank率:** 23% (远超5%的标准) <br> - **内存:** 无明显泄漏,但GC Alloc次数频繁 |
|
||||
| **数据截图** | [此处插入Systrace截图,显示UI线程被长时间占用] <br> [此处插入Memory Profiler截图,显示内存抖动] |
|
||||
| **根因分析** | 1. 通过Systrace发现,UI线程在`bindViewHolder`中频繁调用`ImageLoader.load()`并立即解码图片。 <br> 2. 通过CPU Profiler定位到`BitmapFactory.decodeStream`是耗时操作。 <br> 3. 通过Memory Profiler观察到每次滑动都分配了大量byte[]用于图片解码,引发频繁的GC。 |
|
||||
| **优化建议** | 1. **引入图片缓存:** 在内存和磁盘两个层面缓存已加载的图片,避免重复解码。 <br> 2. **优化图片解码:** 使用`inSampleSize`对图片进行下采样,减少内存占用。 <br> 3. **滑动监听优化:** 在列表快速滑动时暂停图片加载,待停止时再加载。 <br> 4. **使用异步解码:** 将图片解码操作放到子线程。 |
|
||||
| **预期优化效果** | 帧率稳定在55fps以上,Jank率低于5%,GC频率降低80%以上。 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见性能反模式案例库
|
||||
|
||||
- **反模式1:在UI线程读写SharedPreferences**
|
||||
- **症状:** UI卡顿,Trace中看到`commit`或`apply`操作,伴随文件I/O。
|
||||
- **改进:** 使用异步API或迁移至`DataStore`。
|
||||
|
||||
- **反模式2:在`onDraw`中创建对象**
|
||||
- **症状:** 内存抖动,频繁GC,UI卡顿。
|
||||
- **改进:** 在构造函数中预先创建并复用Paint、Path等对象。
|
||||
|
||||
- **反模式3:使用枚举而非`@IntDef`**
|
||||
- **症状:** 相对于静态常量,枚举会占用更多内存(2倍以上)。
|
||||
- **改进:** 对于纯整型常量,使用`@IntDef`注解来获得类型安全。
|
||||
|
||||
- **反模式4:ImageView使用wrap_content并加载大图**
|
||||
- **症状:** 布局测量复杂,内存占用大,解码时间长。
|
||||
- **改进:** 给ImageView设置固定宽高,或重写`onMeasure`进行合理测算。
|
||||
|
||||
- **反模式5:过度使用WakeLock**
|
||||
- **症状:** 后台耗电快。
|
||||
- **改进:** 确保WakeLock使用完毕后立即释放,或使用`AlarmManager`配合`JobScheduler`替代常驻后台服务。
|
||||
|
||||
---
|
||||
|
||||
本手册提供了一个全面的框架。建议团队在实际项目中,根据自身应用的业务特点和常见问题,不断丰富和迭代这个手册,使其真正成为团队性能优化的“作战地图”。
|
||||
433
docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md
Normal file
433
docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md
Normal file
@@ -0,0 +1,433 @@
|
||||
好的,收到您的任务。作为一名资深Android性能优化专家,我将为您撰写一份详尽、可操作的《Android游戏整机性能卡顿与丢帧根因分析与优化指南》。本指南将严格遵循您提出的技术要求、分析维度和交付标准。
|
||||
|
||||
---
|
||||
|
||||
### **Android游戏整机性能卡顿与丢帧根因分析与优化指南**
|
||||
|
||||
**版本:** 1.0
|
||||
**适用对象:** Android游戏性能优化工程师、系统工程师、高级开发人员
|
||||
**核心目标:** 建立一套从现象到根因,再到优化的系统性分析方法,根治Android游戏中出现的卡顿与丢帧问题。
|
||||
|
||||
---
|
||||
|
||||
### **第一部分:问题表征与数据收集**
|
||||
|
||||
在开始任何优化之前,必须能够准确、一致地复现和捕获问题数据。本部分旨在建立性能基线和标准化的数据收集流程。
|
||||
|
||||
#### **1.1 关键性能指标(KPI)定义与基线建立**
|
||||
|
||||
不要只看平均帧率,那会掩盖严重的卡顿问题。请建立以下多维度的KPI体系:
|
||||
|
||||
* **平均帧率(Average FPS):** 宏观性能指标,反映整体流畅度,但不够敏感。
|
||||
* **帧时间(Frame Time):** **核心指标**。记录每帧渲染所消耗的时间(毫秒)。对于60fps的目标,帧时间应为16.6ms;120fps则为8.3ms。
|
||||
* **百分位帧时间(P99 / P95 Frame Time):** **卡顿敏感指标**。P99帧时间表示99%的帧都在此时间内完成渲染。如果P99帧时间远高于16.6ms,说明有严重的尾部延迟,即卡顿。例如,“平均55fps,但P99帧时间为48ms”才是问题的真实写照。
|
||||
* **1% Low FPS:** 另一种表达尾部延迟的方式,指最低1%帧的平均帧率,数值越低,卡顿感越强。
|
||||
* **帧时间直方图(Frame Time Histogram):** 可视化每一帧的时间分布,能直观地看出帧时间是否集中在16.6ms附近,还是有大量长尾帧。
|
||||
|
||||
**建立基线:** 在游戏的一个**稳定、简单场景**(如游戏主界面、单人静止场景)下运行5-10分钟,记录上述所有KPI,作为后续对比的“黄金基线”。
|
||||
|
||||
#### **1.2 捕获高质量的 `Perfetto` 跟踪文件**
|
||||
|
||||
`Perfetto` 是系统级性能分析的基石。一次成功的跟踪捕获,是成功分析的一半。
|
||||
|
||||
**目标:** 捕获一个从卡顿发生前2秒到发生后3秒,包含完整系统信息的跟踪文件。
|
||||
|
||||
**推荐配置(`game_performance.cfg`):**
|
||||
```protobuf
|
||||
# game_performance.cfg
|
||||
buffers {
|
||||
size_kb: 204800 # 200MB 缓冲区,足够捕获几秒钟的详细数据
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
buffers {
|
||||
size_kb: 4096
|
||||
fill_policy: DISCARD
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "linux.process_stats"
|
||||
target_buffer: 1
|
||||
process_stats_config {
|
||||
scan_all_processes_on_start: true
|
||||
proc_stats_poll_ms: 100 # 每100ms采样一次进程CPU/内存
|
||||
}
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.log"
|
||||
android_log_config {
|
||||
log_ids: LID_DEFAULT
|
||||
log_ids: LID_SYSTEM
|
||||
log_ids: LID_CRASH
|
||||
}
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.game_intervention_listener" # 捕获游戏模式相关的系统干预
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.surfaceflinger" # 捕获SurfaceFlinger的合成过程和VSync信息
|
||||
surfaceflinger_config {
|
||||
trace_messages: true
|
||||
}
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.hwcomposer" # 捕获硬件合成信息
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.gpu.memory" # GPU内存信息
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.gpu.frequency" # GPU频率信息
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "linux.sys_stats" # 系统整体CPU/磁盘/内存统计
|
||||
sys_stats_config {
|
||||
meminfo_period_ms: 100
|
||||
vmstat_period_ms: 100
|
||||
stat_period_ms: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
data_sources {
|
||||
config {
|
||||
name: "android.heapprofd" # 用于Native内存分析,可按需启用
|
||||
heapprofd_config {
|
||||
sampling_interval_bytes: 4096
|
||||
}
|
||||
}
|
||||
}
|
||||
# 核心数据源:包含所有关键tag
|
||||
data_sources {
|
||||
config {
|
||||
name: "linux.ftrace"
|
||||
ftrace_config {
|
||||
ftrace_events: "power/*" # CPU频率、进入退出idle
|
||||
ftrace_events: "sched/*" # 调度器事件:任务迁移、唤醒、切换
|
||||
ftrace_events: "kmem/*" # 内核内存分配活动 (可能会产生大量数据,慎用)
|
||||
ftrace_events: "mm_vmscan/*" # 内存回收事件
|
||||
ftrace_events: "ion/*" # ION缓冲区分配/释放
|
||||
ftrace_events: "f2fs/*" # 文件系统事件
|
||||
ftrace_events: "ext4/*" # 文件系统事件
|
||||
ftrace_events: "sync/*" # 同步原语
|
||||
ftrace_events: "workqueue/*" # 工作队列
|
||||
ftrace_events: "gpu_mem/*" # GPU内存事件
|
||||
ftrace_events: "camera/*" # 可能的后台干扰
|
||||
# Android 特定事件
|
||||
atrace_apps: "*" # 跟踪所有应用的自定义atrace标记
|
||||
atrace_categories: "gfx" # 图形系统 (Choreographer, 渲染)
|
||||
atrace_categories: "input" # 输入事件
|
||||
atrace_categories: "view" # 视图系统
|
||||
atrace_categories: "webview" # WebView
|
||||
atrace_categories: "wm" # 窗口管理器
|
||||
atrace_categories: "am" # 活动管理器 (Activity启动)
|
||||
atrace_categories: "sm" # 同步管理器
|
||||
atrace_categories: "audio" # 音频
|
||||
atrace_categories: "video" # 视频
|
||||
atrace_categories: "camera" # 相机
|
||||
atrace_categories: "hal" # 硬件抽象层
|
||||
atrace_categories: "res" # 资源加载
|
||||
atrace_categories: "dalvik" # Dalvik/ART (包含GC)
|
||||
atrace_categories: "bionic" # bionic库调用
|
||||
atrace_categories: "power" # 电源管理
|
||||
atrace_categories: "pm" # 电源管理
|
||||
atrace_categories: "ss" # 系统服务
|
||||
atrace_categories: "database" # 数据库
|
||||
atrace_categories: "network" # 网络
|
||||
atrace_categories: "adb" # ADB
|
||||
atrace_categories: "vibrator" # 振动器
|
||||
atrace_categories: "aidl" # AIDL调用
|
||||
}
|
||||
}
|
||||
}
|
||||
duration_ms: 10000 # 跟踪持续10秒,可根据需要调整
|
||||
```
|
||||
|
||||
**捕获命令:**
|
||||
```bash
|
||||
# 1. 推送配置到设备
|
||||
adb push game_performance.cfg /data/local/tmp/
|
||||
|
||||
# 2. 开始跟踪(在游戏运行到目标场景前执行)
|
||||
adb shell perfetto -c /data/local/tmp/game_performance.cfg --txt -o /data/misc/perfetto-traces/trace.perfetto-trace
|
||||
|
||||
# 3. 在游戏中进行复现卡顿的操作
|
||||
|
||||
# 4. 停止跟踪(Ctrl+C 或等待duration_ms超时),然后拉取文件
|
||||
adb pull /data/misc/perfetto-traces/trace.perfetto-trace .
|
||||
```
|
||||
|
||||
#### **1.3 辅助数据收集**
|
||||
|
||||
* **`dumpsys gfxinfo`:** 在卡顿发生后立即执行,获取最近128帧的详细帧时间直方图。
|
||||
```bash
|
||||
adb shell dumpsys gfxinfo <游戏包名>
|
||||
```
|
||||
重点关注 `Total frames rendered` 和 `frames` 部分的时间分布。
|
||||
* **`dumpsys SurfaceFlinger`:** 获取显示系统的合成信息、BufferQueue状态、HWC信息。
|
||||
```bash
|
||||
adb shell dumpsys SurfaceFlinger
|
||||
```
|
||||
查看 `[包名/Activity]` 对应的Layer,关注 `queue` 和 `buffer count` 状态。
|
||||
* **`top` / `vmstat`:** 实时监控系统整体负载。虽然Perfetto已有,但可做快速现场检查。
|
||||
```bash
|
||||
adb shell top -d 1 -n 10 | grep <游戏包名> # 监控游戏进程CPU占用
|
||||
adb shell vmstat 1 10 # 监控系统级CPU、内存、IO情况
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **第二部分:分层诊断流程**
|
||||
|
||||
拿到 `perfetto` 跟踪文件后,打开 [Perfetto UI](https://ui.perfetto.dev/),加载文件,开始以下分层诊断。
|
||||
|
||||
#### **步骤一:应用层分析**
|
||||
|
||||
**目标:** 确定卡顿是否由应用进程自身(主线程/渲染线程)的逻辑耗时引起。
|
||||
|
||||
1. **识别卡顿帧:**
|
||||
* 在 `SurfaceFlinger` 或 `gfx` 轨道下找到应用的 `FrameLifecycle` (或 `DrawFrame`) 切片。如果一个帧的开始到结束时间超过了16.6ms(对于60fps),或者相邻帧之间有巨大空隙,则标记为卡顿帧。
|
||||
|
||||
2. **分析主线程(Main Thread,通常为 `[包名]`):**
|
||||
* 找到卡顿帧对应时间点的主线程切片。
|
||||
* **查找长耗时函数:** 寻找宽度明显大于其他区域的切片。常见元凶:
|
||||
* **`Choreographer#doFrame`:** 这是处理输入、动画、绘制入口的核心。如果它延迟启动,说明主线程之前被阻塞了。
|
||||
* **GC 事件:** 搜索 `art::gc::` 相关切片,如 `ConcurrentMarking`, `Pause`。一次几十毫秒的GC暂停会直接导致丢帧。**量化:** 一次GC暂停了24ms,导致 `doFrame` 延迟。
|
||||
* **锁竞争(Lock Contention):** 切片上出现 `Lock` 或 `Mutex` 字样,颜色通常是红色或黄色。点击它可以看到是哪个线程持有锁(`LockOwner`)。**量化:** 主线程在 `android.view.View#invalidate` 上等待某锁释放15ms,该锁被一个后台线程持有。
|
||||
* **Binder 调用:** 切片以 `binder transaction` 开头。这表明主线程在等待系统服务响应,如 `PackageManager`, `WindowManager`。**量化:** 主线程调用 `IActivityManager` 的Binder接口,等待了8ms。
|
||||
* **文件I/O:** 切片以 `open`, `read`, `write`, `fopen` 开头。意味着主线程直接进行磁盘操作。
|
||||
|
||||
3. **分析渲染线程(RenderThread):**
|
||||
* 在 `RenderThread` (通常为 `[包名]:GPU` 或 `[包名]:RenderThread`) 轨道上分析。
|
||||
* **查找长耗时函数:**
|
||||
* **`eglSwapBuffers` 或 `queueBuffer`:** 这是一个关键点,它标志着CPU侧渲染工作已完成,等待Buffer被SurfaceFlinger消费。
|
||||
* 如果 `eglSwapBuffers` 阻塞时间很长,说明**GPU负载过高**或 **BufferQueue 满了**(后面会分析)。
|
||||
* **`flush commands`:** 向GPU提交绘制命令的开销。
|
||||
* **`draw` 相关的调用:** 如 `glDrawElements`, 大量耗时可能意味着Draw Call过多。
|
||||
* **检查渲染线程优先级:** 查看其调度切片,是否经常被其他线程抢占。
|
||||
|
||||
#### **步骤二:系统层分析**
|
||||
|
||||
**目标:** 确定卡顿是否由系统服务(如SurfaceFlinger)或系统资源管理(CPU调度、内存)引起。
|
||||
|
||||
1. **分析显示系统(SurfaceFlinger):**
|
||||
* 找到 `SurfaceFlinger` 进程的主线程轨道。
|
||||
* **检查合成周期:** `onMessageReceived` 或 `doComposition` 切片。如果这些切片的执行时间超过了VSync间隔,会导致Buffer不能及时被消费,从而阻塞游戏的生产者线程(如RenderThread的 `eglSwapBuffers`)。
|
||||
* **检查VSync信号:** `VSync-app` 和 `VSync-sf` 轨道。如果VSync信号本身出现抖动或丢失,`Choreographer` 就无法准时被唤醒,导致应用错过帧。这通常与系统负载高或中断处理延迟有关。
|
||||
* **检查BufferQueue状态:** 通过 `dumpsys SurfaceFlinger` 或在Perfetto中搜索相关切片。如果 `BufferQueue` 的 `dequeueBuffer` 或 `queueBuffer` 出现阻塞,可能是因为 `SurfaceFlinger` 来不及消费,导致队列满(例如,三缓冲都满了)。
|
||||
|
||||
2. **分析CPU调度器(`sched` 轨道):**
|
||||
* 这是诊断CPU瓶颈的核心。切换到 `Scheduling` 跟踪视图。
|
||||
* **检查关键线程的调度:**
|
||||
* 找到游戏的主线程和渲染线程。
|
||||
* **CPU迁移:** 在卡顿发生瞬间,这两个线程是否被迁移到了**小核(LITTLE core)** 上运行?由于小核性能差,这会导致计算能力突然下降。**量化:** 卡顿时刻,主线程从大核(CPU4)被迁移到小核(CPU0)上运行了15ms。
|
||||
* **被抢占(Preempted):** 查看是否有更高优先级的系统线程(如 `kswapd`, `irq`, `cfinteractive`)长时间抢占了游戏线程。
|
||||
* **就绪态等待(Runnable):** 线程状态为 `R`(绿色)但未运行,表示它在等待CPU。如果等待时间过长,说明CPU核心不足或调度器决策不当。
|
||||
|
||||
3. **分析内存回收压力(`memreclaim` 和 `mm_vmscan` 事件):**
|
||||
* 在Perfetto的搜索框中输入 `kswapd` 或 `direct reclaim`。
|
||||
* **`kswapd` 唤醒:** 如果 `kswapd` (内核交换守护进程)被频繁唤醒且运行时间较长,说明系统内存压力大,正在后台回收页面。
|
||||
* **直接内存回收(Direct Reclaim):** **这是严重卡顿的信号**。当 `kswapd` 回收速度跟不上时,分配内存的线程(如游戏主/渲染线程)会自己进入内存回收流程。这会导致该线程被阻塞几十甚至上百毫秒。搜索 `mm_vmscan:direct_reclaim_begin` 和 `_end` 切片。**量化:** 渲染线程在分配一个纹理时触发了直接内存回收,被阻塞了35ms。
|
||||
|
||||
#### **步骤三:内核/硬件层分析**
|
||||
|
||||
**目标:** 确定卡顿是否由硬件频率、温度限制或特定硬件单元瓶颈引起。
|
||||
|
||||
1. **分析CPU频率(`power/cpu_frequency`):**
|
||||
* 在Perfetto的 `Frequency` 视图中,查看大核和中核的CPU频率曲线。
|
||||
* **热限频(Thermal Throttling):** 在卡顿时刻,CPU频率是否出现**断崖式下跌**?如果所有核心频率突然降到最低,很可能是触发了温控。可以结合 `thermal` 事件或 `temperature` 传感器数据确认。
|
||||
* **频率提升不足:** 在进入团战等高负载场景时,CPU频率是否及时拉升到最高?如果拉升缓慢,会导致性能不足。
|
||||
|
||||
2. **分析GPU频率与负载(需要厂商工具或Perfetto的`gpu`事件):**
|
||||
* 使用高通Snapdragon Profiler或ARM Streamline可以获取GPU的硬件计数器。
|
||||
* **GPU频率过低:** 类似CPU,检查GPU频率是否在高负载场景下未提升。
|
||||
* **着色器核心(Shader Core)瓶颈:** 计数器显示ALUs(算术逻辑单元)占用率100%,而纹理单元(TMU)空闲,说明是计算瓶颈,可能Shader太复杂。
|
||||
* **内存带宽瓶颈:** 计数器显示带宽接近极限,而核心利用率不高,说明GPU在等待从内存获取数据,通常由大量纹理采样或复杂帧缓冲操作引起。
|
||||
|
||||
3. **分析I/O与存储:**
|
||||
* 在Perfetto中搜索 `f2fs_read`, `ext4_read`, `ion allocation`。
|
||||
* **资源加载卡顿:** 当游戏进入新场景时,主线程或加载线程是否在等待 `read` 调用完成?如果是,说明磁盘I/O是瓶颈,可能需要优化资源加载策略或使用异步加载。
|
||||
* **Shader编译卡顿:** 搜索 `ShaderCompiler` 或 `ProgramCache` 相关切片。首次运行游戏或更新后,GPU驱动编译着色器是一个非常耗时的操作,经常导致卡顿。
|
||||
|
||||
#### **步骤四:关联分析——构建根因逻辑链**
|
||||
|
||||
这是最重要的步骤,将以上各层的信息串联成一个完整的故事。
|
||||
|
||||
**逻辑链示例:**
|
||||
|
||||
1. **现象(Perfetto中观察):** 在时间戳T,游戏帧 `FrameLifecycle` 切片显示该帧耗时48ms。随后,`SurfaceFlinger` 因为没有新帧而进行了重复合成(`idle`)。
|
||||
2. **应用层分析(主线程):** 放大T时刻主线程切片,发现 `Choreographer#doFrame` 直到T+20ms才开始。在它之前,有一个非常宽的 `art::gc::ConcurrentMarking` 暂停切片,持续了20ms。
|
||||
3. **系统层分析(调度/内存):** 观察GC暂停期间的调度。GC线程(`HeapTaskDaemon`)正在运行,而主线程处于`Uninterruptible Sleep` (D状态) 等待。同时,内存统计数据显示系统`free memory`极低,`kswapd` 正在高频运行。
|
||||
4. **内核/硬件层分析(频率):** 在GC发生前,CPU大核由于持续高负载温度上升,在GC开始的瞬间触发了温控,频率从2.4GHz骤降至1.0GHz,导致GC执行变慢,进一步拉长了暂停时间。
|
||||
|
||||
**最终根因结论:**
|
||||
**由于系统内存压力过大,触发了垃圾回收(GC),而此时CPU因热限频导致性能下降,GC暂停时间被拉长到20ms,直接阻塞了游戏主线程,使其错过了随后的VSync信号,最终导致一帧耗时48ms,用户感知为一次严重卡顿。**
|
||||
|
||||
优化方向也随之清晰:减少应用内存占用以减少GC频率;优化GC参数;优化温控策略。
|
||||
|
||||
---
|
||||
|
||||
### **第三部分:优化策略与建议**
|
||||
|
||||
针对已识别的根因,提供具体的、分层的优化建议。
|
||||
|
||||
#### **3.1 应用层优化**
|
||||
|
||||
* **针对GC卡顿:**
|
||||
* **内存优化:** 使用对象池减少频繁的对象分配;避免在每帧的循环中创建新对象;使用`SparseArray`替代`HashMap`;优化数据结构。
|
||||
* **内存泄漏检测:** 使用LeakCanary定期检测并修复Java内存泄漏。
|
||||
* **Native层内存分配:** 对于大型数据(如纹理、音频),尽量使用Native堆(`malloc`, `new`),避免Java堆压力。使用 `ByteBuffer.allocateDirect()` 创建直接缓冲区。
|
||||
* **针对锁竞争:**
|
||||
* **最小化锁粒度:** 只锁必要的代码块,而不是整个方法。
|
||||
* **读写锁分离:** 对于读多写少的场景,使用 `ReentrantReadWriteLock`。
|
||||
* **避免在临界区中执行耗时操作。**
|
||||
* **针对渲染过载:**
|
||||
* **LOD技术:** 根据距离使用不同精细度的模型。
|
||||
* **遮挡剔除:** 不渲染玩家看不到的物体。
|
||||
* **减少Overdraw:** 使用工具检查并减少重复绘制的像素。
|
||||
* **优化Shader:** 减少复杂数学运算,合并材质。
|
||||
* **合批渲染:** 减少Draw Call数量。
|
||||
* **针对I/O卡顿:**
|
||||
* **资源预加载:** 在进入新场景前,提前在后台线程加载必要的资源。
|
||||
* **分帧加载:** 将大型资源的加载分摊到多帧中,避免单帧阻塞。
|
||||
* **异步加载:** 使用`AssetManager`的异步接口或在加载线程中进行所有I/O操作。
|
||||
* **针对Shader编译卡顿:**
|
||||
* **使用Pipelines库:** Vulkan API使用Pipeline Cache,OpenGL ES使用Program Binary,在首次编译后保存,后续启动时直接加载。
|
||||
* **后台编译:** 如果引擎支持,让Shader编译在加载屏幕或非关键路径上完成。
|
||||
|
||||
#### **3.2 系统层优化(需要与OEM/芯片厂商合作或利用游戏模式API)**
|
||||
|
||||
* **针对CPU调度不当:**
|
||||
* **设置关键线程优先级:** 使用 `android.os.Process.setThreadPriority()` 和 `setThreadGroupAndCpuset()` 将主线程、渲染线程绑定到大核组,并赋予高优先级。
|
||||
* **使用游戏模式API:** Google Play Console允许配置游戏模式,向系统申请性能资源。
|
||||
* **与OEM合作:** 在系统服务中为特定游戏配置白名单,避免后台服务抢占CPU资源。
|
||||
* **针对内存压力:**
|
||||
* **使用 `ActivityManager` 的 `setMemoryClass()` 或 `setLargeHeapClass()` 请求更多堆内存。**
|
||||
* **监控 `onTrimMemory()` 回调:** 当收到 `TRIM_MEMORY_MODERATE` 或更高级别时,主动释放缓存资源(如纹理、缓存数据)。
|
||||
* **针对SurfaceFlinger阻塞:**
|
||||
* **启用三重缓冲:** 在应用代码中请求三缓冲 `mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT)` 等操作有时会触发,但更可靠的是在引擎层配置。
|
||||
* **使用Vulkan API:** Vulkan提供了对BufferQueue和渲染流水线更精细的控制,可以减少对SurfaceFlinger的依赖。
|
||||
|
||||
#### **3.3 内核/硬件层优化**
|
||||
|
||||
* **针对CPU/GPU频率/热限频:**
|
||||
* **功耗优化:** 应用层的性能优化也意味着功耗降低,能有效延缓温控触发。
|
||||
* **了解温控策略:** 与OEM团队沟通,了解温控阈值,并确保游戏在温控触发前能将热点任务迁移回大核。
|
||||
* **选择目标GPU频率:** 在某些厂商工具中,可以针对特定游戏设置最小的GPU频率,防止因频率切换造成的瞬时性能下降。
|
||||
* **针对I/O瓶颈:**
|
||||
* **文件系统对齐:** 确保资源文件的存储对齐方式与文件系统(如F2FS)一致。
|
||||
* **使用异步I/O接口:** 内核层面确保I/O操作不会阻塞关键线程。
|
||||
|
||||
---
|
||||
|
||||
### **第四部分:工具与脚本**
|
||||
|
||||
#### **4.1 自动化数据收集脚本 (Python示例)**
|
||||
|
||||
```python
|
||||
# collect_game_perf.py
|
||||
import subprocess
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
PACKAGE_NAME = sys.argv[1] # 从命令行传入包名
|
||||
TRACE_TIME = 10 # 跟踪时间(秒)
|
||||
PERFETTO_CONFIG = "/data/local/tmp/game_performance.cfg"
|
||||
OUTPUT_DIR = "./perf_traces"
|
||||
|
||||
def run_adb_command(cmd):
|
||||
result = subprocess.run(f"adb shell {cmd}", shell=True, capture_output=True, text=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
def main():
|
||||
if not os.path.exists(OUTPUT_DIR):
|
||||
os.makedirs(OUTPUT_DIR)
|
||||
|
||||
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
||||
trace_file = f"{OUTPUT_DIR}/trace_{PACKAGE_NAME}_{timestamp}.perfetto-trace"
|
||||
|
||||
print(f"1. 确保 {PACKAGE_NAME} 正在前台运行...")
|
||||
input("按 Enter 键继续...")
|
||||
|
||||
print("2. 启动 Perfetto 跟踪...")
|
||||
run_adb_command(f"perfetto -c {PERFETTO_CONFIG} --txt -o /data/misc/perfetto-traces/trace_tmp.perfetto-trace &")
|
||||
|
||||
print(f"3. 请在 {TRACE_TIME} 秒内执行卡顿复现操作...")
|
||||
time.sleep(TRACE_TIME)
|
||||
|
||||
print("4. 停止跟踪...")
|
||||
run_adb_command("killall -SIGINT perfetto")
|
||||
time.sleep(2) # 等待写入完成
|
||||
|
||||
print("5. 拉取跟踪文件...")
|
||||
run_adb_command(f"pull /data/misc/perfetto-traces/trace_tmp.perfetto-trace {trace_file}")
|
||||
run_adb_command("rm /data/misc/perfetto-traces/trace_tmp.perfetto-trace")
|
||||
|
||||
print(f"6. 获取 dumpsys gfxinfo...")
|
||||
gfx_info = run_adb_command(f"dumpsys gfxinfo {PACKAGE_NAME}")
|
||||
with open(f"{OUTPUT_DIR}/gfxinfo_{PACKAGE_NAME}_{timestamp}.txt", "w") as f:
|
||||
f.write(gfx_info)
|
||||
|
||||
print(f"完成!跟踪文件已保存至: {trace_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("用法: python collect_game_perf.py <游戏包名>")
|
||||
sys.exit(1)
|
||||
main()
|
||||
```
|
||||
|
||||
#### **4.2 推荐 `Perfetto` UI 配置**
|
||||
|
||||
在Perfetto UI中,保存以下配置为 `game_config.json`,通过 `Load Config` 加载,可以快速显示关键轨道。
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"cpu": true,
|
||||
"ftrace": true,
|
||||
"gpu": true,
|
||||
"power": true,
|
||||
"mem": true,
|
||||
"sched": true
|
||||
},
|
||||
"pinnedCpus": [],
|
||||
"visibleColumns": ["name", "utid", "dur", "avg_cpu"],
|
||||
"traceProcessorConfig": {
|
||||
"http": {
|
||||
"port": 9001,
|
||||
"browserUrl": "http://localhost:9001/"
|
||||
}
|
||||
},
|
||||
"displayConfig": {
|
||||
"showThreadNames": true,
|
||||
"showCpuTimes": true,
|
||||
"showCpuFreq": true,
|
||||
"showMemUsage": true,
|
||||
"showGpuFreq": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**在Perfetto UI中的快速过滤清单:**
|
||||
* 搜索游戏包名,快速定位其所有线程。
|
||||
* 搜索 `GC` 或 `art` 查看垃圾回收。
|
||||
* 搜索 `SurfaceFlinger` 查看合成。
|
||||
* 搜索 `kswapd` 和 `direct_reclaim` 查看内存压力。
|
||||
* 搜索 `cpu_frequency` 查看降频情况。
|
||||
* 关注状态为 `Runnable` (绿色) 但长时间未运行的线程。
|
||||
|
||||
通过遵循本指南,您将有能力系统性地诊断和解决Android游戏中最棘手的卡顿问题,为用户提供极致流畅的游戏体验。
|
||||
45
docs/学习笔记/两份提示词对比.md
Normal file
45
docs/学习笔记/两份提示词对比.md
Normal file
@@ -0,0 +1,45 @@
|
||||
这是一个非常典型的提示词工程对比场景。我们可以从**结构性、专业性、可执行性、以及交付标准**四个维度进行详细对比。
|
||||
|
||||
### 综合结论:**第二个更好**
|
||||
|
||||
虽然两个提示词都具备很高的专业水准,但第二个提示词在**逻辑闭环**、**受众同理心**以及**交付物的实用性**上更胜一筹。第一个提示词更像一份“技术需求文档”,而第二个更像一份“课程设计蓝图”。
|
||||
|
||||
以下是详细的对比分析:
|
||||
|
||||
---
|
||||
|
||||
### 1. 结构性对比
|
||||
* **第一个**:结构非常清晰,采用了标准的工程文档格式(背景、规范、约束、交付、评估)。这种格式的好处是严谨,但略显生硬,像在给AI下达“开发任务”。
|
||||
* **第二个**:同样结构清晰,但加入了更多的引导性语言。例如在“背景”中解释了“为什么”要学(解决疑难杂症),在“质量”中强调了“平衡”。这使得提示词不仅是命令,更像是在与一个协作者沟通。
|
||||
|
||||
### 2. 专业性深度
|
||||
* **第一个**:技术点罗列非常密集,几乎覆盖了WMS的所有核心细节(如 `WindowManagerGlobalLock`、`TaskFragment`、`BadTokenException`)。适合作为一份技术检查清单。
|
||||
* **第二个**:不仅罗列了技术点,还梳理了**逻辑关系**。例如,它明确指出了 **“MeasureSpec如何从WMS传递到应用层”** 以及 **“WMS作为仲裁者”** 的概念。这比单纯列出“布局计算”更能帮助理解系统的运行机制,体现了“深度解析”而非“功能罗列”。
|
||||
|
||||
### 3. 可执行性(对AI的友好度)
|
||||
* **第一个**:对代码片段和文件路径的要求极其具体(如 `frameworks/base/services/core/java/com/android/server/wm/`),这在生成精确内容时非常有用。但它要求“可验证性”和“最新分支”,对于AI来说,知识截止日期可能导致无法保证“最新”。
|
||||
* **第二个**:对代码版本的要求更宽容(“建议以最新稳定版为主”),同时要求揭示“内部状态机”和“锁竞争”,这更侧重于**原理的通用性**。在提示词工程中,引导AI讲“道理”往往比讲“最新代码”更容易产生有深度的内容。
|
||||
|
||||
### 4. 交付标准与实用性
|
||||
* **第一个**:评估标准很量化(完整性、准确性、可验证性)。但其中“行号可追溯”的要求对于AI生成内容来说难度极高,且容易产生虚假信息。
|
||||
* **第二个**:评估标准更偏向**认知提升**。例如“超越了官方文档和普通博客的深度”、“能帮助高级工程师调试(分析dumpsys window命令输出)”。这表明第二个提示词的最终目标是**赋能听众**,而不是仅仅完成一份文档。
|
||||
|
||||
---
|
||||
|
||||
### 具体差异分析表
|
||||
|
||||
| 维度 | 第一个提示词 | 第二个提示词 | 胜出方 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **开场白** | 平铺直叙,直接进入背景 | 通过“问题域”和“核心价值”切入,建立学习动机 | 第二个 |
|
||||
| **核心概念讲解** | 列出了模块名(如 `WindowState`),但偏静态 | 不仅列出类名,还强调层级管理关系和交互链路(如从`ViewRootImpl`到WMS) | 第二个 |
|
||||
| **难点处理** | 提到了锁机制和优化策略 | 同样提到了锁机制,但强调了“避免陷入无关代码的逐行解读”,注重主干清晰 | 第二个 |
|
||||
| **对代码的要求** | 极严格(要求行号、最新分支),可能导致AI产生幻觉 | 严格但合理(要求核心逻辑片段,注重通用原理) | 第一个(理想状态下更精确),但第二个更务实 |
|
||||
| **交付物导向** | 更像一份技术手册目录 | 更像一份面向高级工程师的**培训课程大纲** | 第二个 |
|
||||
|
||||
### 总结
|
||||
|
||||
* **第一个提示词的优点**:极其严谨,适合用来生成一份**技术参考手册**或**源码导读文档**。如果你需要AI帮你整理一份包含大量精确代码路径和API用法的文档,第一个更好。
|
||||
* **第二个提示词的优点**:具备教学法和逻辑性,适合用来生成一份**培训课件**。它告诉AI不仅要输出“是什么”,还要输出“为什么”以及“如何思考”。
|
||||
|
||||
**建议:**
|
||||
如果你打算给团队做技术分享,**建议采用第二个提示词**,或者以第二个为主框架,将第一个中那些具体的“技术点清单”(如 `BadTokenException` 触发条件、多显示器支持)作为补充要求填入第二个的“高级主题”部分。这样既能保证课程的逻辑性,又能确保技术覆盖的完整性。
|
||||
Reference in New Issue
Block a user