diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index fb3c69e..5ab58f7 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -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", diff --git a/docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md b/docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md new file mode 100644 index 0000000..f89dbda --- /dev/null +++ b/docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md @@ -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对,
一端给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 callback) { + mRoot.forAllLeafTasks(callback, true); +} + +// 遍历所有Activity +void forAllActivities(Consumer 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图形系统、输入系统的必备知识。 \ No newline at end of file diff --git a/docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md b/docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md new file mode 100644 index 0000000..f2b2c6b --- /dev/null +++ b/docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md @@ -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 { + // 指向持有该窗口的客户端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系统的设计哲学有更深刻的体会。 \ No newline at end of file diff --git a/docs/学习笔记/Android性能分析标准化操作手册.md b/docs/学习笔记/Android性能分析标准化操作手册.md new file mode 100644 index 0000000..c2e3573 --- /dev/null +++ b/docs/学习笔记/Android性能分析标准化操作手册.md @@ -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 ` 对目标应用进行全量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. 开发阶段,怀疑某功能卡顿时
2. 配合Systrace,深入分析可疑方法 | `Sampled`: 低开销,宏观分析
`Instrumented`: 高精度,微观分析 | +| **Memory Profiler** | 排查内存泄漏、分析内存抖动 | 1. 功能测试后
2. 出现OOM时
3. 配合LeakCanary做深度确认 | 定期点击`Dump Java Heap`并保存,用于对比分析 | +| **Network Profiler** | 监控网络请求频率、数据量、耗时 | 1. 页面加载慢
2. 流量消耗异常时 | 结合`Inspector`查看请求和响应头、Body | +| **Systrace / Perfetto** | 系统级性能瓶颈分析,如渲染、GC、Binder调用 | 1. 卡顿问题
2. 启动时间优化
3. 分析系统资源竞争 | 选择相关标签 (e.g., `gfx`, `view`, `am`, `input`) | +| **LeakCanary** | 自动化内存泄漏检测 | **持续集成**和日常开发调试中 | 默认配置即可,可自定义监听哪些对象的泄漏 | +| **adb shell dumpsys** | 获取系统服务状态信息 | 1. 分析内存 (`dumpsys meminfo`)
2. 分析电池 (`dumpsys batterystats`)
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[定位到耗时方法
CPU Profiler/Method Tracing] + F --> G[优化方法逻辑/异步化] + E -- 否 --> H{是否存在过度绘制?} + H -- 是 --> I[使用Layout Inspector/GPU过度绘制调试
优化布局层级/使用ClipRect] + H -- 否 --> J{是否频繁GC?} + J -- 是 --> K[检查Memory Profiler中对象分配
优化临时对象创建/使用对象池] + J -- 否 --> D + + B --> L[内存过高/OOM] + L --> M[集成LeakCanary] + M --> N{是否检测到泄漏?} + N -- 是 --> O[根据LeakCanary报告
修复引用链] + N -- 否 --> P[使用Memory Profiler抓取Heap Dump] + P --> Q[分析Retained Size最大的对象
或对比不同时间点的Dump] + Q --> R{是否存在大量无法解释的实例?} + R -- 是 --> S[分析GC Root
找到持有者] + R -- 否 --> T[检查大对象如Bitmap
是否及时释放] + + B --> U[启动速度慢] + U --> V[在关键生命周期埋点
抓取Systrace] + V --> W[分析Application和
FirstActivity的onCreate] + W --> X[识别耗时初始化任务] + X --> Y[延迟加载、异步初始化、
App Startup优化] + + B --> Z[耗电快/流量高] + Z --> AA[导出BugReport
使用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。
- **Jank率:** 23% (远超5%的标准)
- **内存:** 无明显泄漏,但GC Alloc次数频繁 | +| **数据截图** | [此处插入Systrace截图,显示UI线程被长时间占用]
[此处插入Memory Profiler截图,显示内存抖动] | +| **根因分析** | 1. 通过Systrace发现,UI线程在`bindViewHolder`中频繁调用`ImageLoader.load()`并立即解码图片。
2. 通过CPU Profiler定位到`BitmapFactory.decodeStream`是耗时操作。
3. 通过Memory Profiler观察到每次滑动都分配了大量byte[]用于图片解码,引发频繁的GC。 | +| **优化建议** | 1. **引入图片缓存:** 在内存和磁盘两个层面缓存已加载的图片,避免重复解码。
2. **优化图片解码:** 使用`inSampleSize`对图片进行下采样,减少内存占用。
3. **滑动监听优化:** 在列表快速滑动时暂停图片加载,待停止时再加载。
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`替代常驻后台服务。 + +--- + +本手册提供了一个全面的框架。建议团队在实际项目中,根据自身应用的业务特点和常见问题,不断丰富和迭代这个手册,使其真正成为团队性能优化的“作战地图”。 \ No newline at end of file diff --git a/docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md b/docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md new file mode 100644 index 0000000..8a7e3b6 --- /dev/null +++ b/docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md @@ -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游戏中最棘手的卡顿问题,为用户提供极致流畅的游戏体验。 \ No newline at end of file diff --git a/docs/学习笔记/两份提示词对比.md b/docs/学习笔记/两份提示词对比.md new file mode 100644 index 0000000..7a51289 --- /dev/null +++ b/docs/学习笔记/两份提示词对比.md @@ -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` 触发条件、多显示器支持)作为补充要求填入第二个的“高级主题”部分。这样既能保证课程的逻辑性,又能确保技术覆盖的完整性。 \ No newline at end of file