鏇存柊鏂囨。
This commit is contained in:
22
.obsidian/workspace.json
vendored
22
.obsidian/workspace.json
vendored
@@ -223,8 +223,17 @@
|
||||
"command-palette:打开命令面板": false
|
||||
}
|
||||
},
|
||||
"active": "c40cc65d2419fc22",
|
||||
"active": "5b497a77c6d68c73",
|
||||
"lastOpenFiles": [
|
||||
"docs/学习笔记/两份提示词对比.md",
|
||||
"docs/学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md",
|
||||
"docs/学习笔记/Android整机性能分析与问题定位实战指南.md",
|
||||
"docs/学习笔记/Android性能分析标准化操作手册.md",
|
||||
"docs/学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md",
|
||||
"docs/学习笔记/Android WindowManagerService (WMS) 架构深度解析(第一次提示词).md",
|
||||
"docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md",
|
||||
"docs/Obsidian笔记体系/Projects/aitsc",
|
||||
"docs/# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md",
|
||||
"docs/学习笔记/看应用卡顿问题.md",
|
||||
"未命名 1.base",
|
||||
"docs/学习笔记/perfeto看trace技巧.md",
|
||||
@@ -245,14 +254,6 @@
|
||||
"docs/学习笔记/产品经理/需求管理详解.md",
|
||||
"docs/学习笔记/产品经理/项目管理详解.md",
|
||||
"docs/Obsidian/高频命令.md",
|
||||
"docs/学习笔记/基础性能问题分析(ODM).md",
|
||||
"docs/Obsidian笔记体系/Areas/09-调试与工具链/Systrace_Perfetto全解读.md",
|
||||
"docs/学习笔记/php/未命名.md",
|
||||
"docs/git/git设置用户名和邮箱.md",
|
||||
"docs/学习笔记/产品经理/产品经理--写文档.md",
|
||||
"docs/学习笔记/产品经理/沟通与表达详解.md",
|
||||
"docs/学习笔记/产品经理/数据分析详解.md",
|
||||
"docs/学习笔记/产品经理/产品运营详解.md",
|
||||
"docs/学习笔记/产品经理",
|
||||
"Pasted image 20260129111501.png",
|
||||
"Pasted image 20260129111451.png",
|
||||
@@ -269,7 +270,6 @@
|
||||
"docs/学习笔记/php",
|
||||
"docs/cursor/php",
|
||||
"com.xiaomi.appstore.appclaim.signature.verification.apk",
|
||||
"docs/android面试/系统原理/DPMS常见面试题",
|
||||
"docs/DPMS常见面试题"
|
||||
"docs/android面试/系统原理/DPMS常见面试题"
|
||||
]
|
||||
}
|
||||
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图形系统、输入系统的必备知识。
|
||||
265
docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md
Normal file
265
docs/Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md
Normal file
@@ -0,0 +1,265 @@
|
||||
系统维护常用语:
|
||||
以行业和职业的维度,继续追加生成更多的提示词模板
|
||||
以行业和职业的维度,继续追加生成更多不同的技术方向研发工程师的提示词模板
|
||||
以行业和职业的维度,继续追加生成更多不同的ui设计方向设计师的提示词模板
|
||||
以行业和职业的维度,继续追加生成更多不同的设计软件系统整体架构方向的架构师的提示词模板
|
||||
以行业和职业的维度,继续追加生成python全栈开发的架构师的提示词模板
|
||||
以行业和职业的维度,继续追加生成软件产品经理的提示词模板
|
||||
以行业和职业的维度,继续追加生成微信小程序开发工程师的提示词模板
|
||||
以行业和职业的维度,继续追加生成android开发工程师的提示词模板
|
||||
以行业和职业的维度,继续追加生成考公的提示词模板
|
||||
按照行业和职业的维度,将生成好的提示词模板进行分类
|
||||
|
||||
将项目中的所有接口都写出文档,保存保存在 flask_prompt_master/docs/api_documentation.txt 路径下
|
||||
更新API文档,包含项目中的所有接口。我会在现有文档的基础上添加其他接口的说明
|
||||
|
||||
|
||||
提示词大师
|
||||
智能生成高质量提示词,提升您的工作效率。
|
||||
基于先进的大模型技术,快速生成精准提示词。
|
||||
|
||||
|
||||
完成核心功能后我们就开始测试mvp
|
||||
|
||||
|
||||
常用命令:
|
||||
初始化数据库 python -m flask_prompt_master.init_db
|
||||
|
||||
更新数据库 python test_db.py
|
||||
|
||||
|
||||
# 激活虚拟环境
|
||||
.\myenv\Scripts\activate
|
||||
.venv\Scripts\activate
|
||||
# 激活虚拟环境
|
||||
source venv/bin/activate
|
||||
启动项目
|
||||
python run_dev.py
|
||||
|
||||
|
||||
|
||||
linux数据库操作
|
||||
mysql -uroot -p
|
||||
数据库密码:123456
|
||||
创建数据表
|
||||
数据库名:food_db
|
||||
CREATE DATABASE `food_db` DEFAULT CHARACTER SET = `utf8mb4`;
|
||||
删除表 DROP DATABASE IF EXISTS food_db;
|
||||
查看表
|
||||
show databases;
|
||||
进入数据表
|
||||
use food_db;
|
||||
show tables;
|
||||
select * from user ;
|
||||
show create table user;
|
||||
|
||||
|
||||
1. 开启mysql service mysqld start
|
||||
2. 查看mysql是否开启 ps -ef |grep mysql
|
||||
|
||||
|
||||
创建数据表
|
||||
CREATE TABLE `container_orchestration_assistant` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '助手ID',
|
||||
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '助手名称',
|
||||
`description` varchar(255) NOT NULL DEFAULT '' COMMENT '助手描述',
|
||||
`category` varchar(50) NOT NULL DEFAULT '' COMMENT '分类(架构设计)',
|
||||
`industry` varchar(50) NOT NULL DEFAULT '' COMMENT '行业(技术研发)',
|
||||
`profession` varchar(50) NOT NULL DEFAULT '' COMMENT '职业(高级工程师)',
|
||||
`sub_category` varchar(50) NOT NULL DEFAULT '' COMMENT '子分类(容器编排)',
|
||||
`system_prompt` text NOT NULL COMMENT '系统提示词(容器化需求转换规则)',
|
||||
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:1-有效,0-无效',
|
||||
`updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后一次更新时间',
|
||||
`created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_category` (`category`),
|
||||
KEY `idx_industry` (`industry`),
|
||||
KEY `idx_profession` (`profession`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Python容器编排助手表';
|
||||
|
||||
|
||||
|
||||
|
||||
插入一条user数据
|
||||
INSERT INTO `user` (`uid`, `nickname`, `mobile`, `email`, `sex`, `avatar`, `login_name`, `login_pwd`, `login_salt`, `status`, `updated_time`, `created_time`)
|
||||
VALUES
|
||||
(2, '编程浪子www.54php.cn', '11012345679', 'apanly@163.com', 1, '', '54php.cn', '816440c40b7a9d55ff9eb7b20760862c', 'cF3JfH5FJfQ8B2Ba', 1, '2017-03-15 14:08:48', '2017-03-15 14:08:48');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1. 项目结构
|
||||
根目录下主要内容如下:
|
||||
flask_prompt_master/:主应用目录,包含核心业务逻辑
|
||||
static/、templates/:静态资源和前端模板
|
||||
migrations/:数据库迁移相关
|
||||
tests/:测试代码
|
||||
scripts/:脚本工具
|
||||
config.py:全局配置文件
|
||||
run_dev.py:开发环境启动入口
|
||||
gunicorn.conf.py:Gunicorn 配置
|
||||
start_gunicorn.sh、stop_gunicorn.sh:Gunicorn 启动/停止脚本
|
||||
requirements.txt:依赖列表
|
||||
各类产品、开发、运维、测试文档
|
||||
2. 主要依赖
|
||||
Flask 2.x
|
||||
Flask-SQLAlchemy
|
||||
Flask-Migrate
|
||||
Flask-Cors
|
||||
Gunicorn
|
||||
其他常用 Flask 扩展
|
||||
3. 启动方式
|
||||
开发环境:python run_dev.py
|
||||
生产环境:./start_gunicorn.sh(Gunicorn + Nginx 反向代理)
|
||||
4. 配置说明
|
||||
config.py 负责数据库、API Key、微信小程序等配置
|
||||
Gunicorn 监听本地 8001 端口,Nginx 代理 80 端口对外服务
|
||||
支持通过环境变量灵活切换配置
|
||||
5. 主要功能模块
|
||||
用户认证与管理
|
||||
提示词管理
|
||||
微信小程序集成
|
||||
日志与运维
|
||||
丰富的产品、测试、运维文档
|
||||
6. 典型部署架构
|
||||
Gunicorn 只监听本地(127.0.0.1:8001)
|
||||
Nginx 监听 80 端口,反向代理到 Gunicorn
|
||||
外部通过域名或 IP 访问 Nginx
|
||||
若有 SELinux,需允许 Nginx 访问本地端口
|
||||
7. 典型问题与解决
|
||||
502 Bad Gateway:多为 SELinux 拒绝,需 sudo setsebool -P httpd_can_network_connect 1
|
||||
端口冲突:Gunicorn/Nginx 配置端口需唯一
|
||||
依赖不兼容:Flask 与 Flask-SQLAlchemy 需版本匹配
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
写一个android framework入门教程的设计方案,使用章节形式。
|
||||
|
||||
写一个android framework高级教程的设计方案,使用章节形式。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
代码上库
|
||||
TicketNo:3
|
||||
Description:历史记录版本
|
||||
Team:ruilaizi
|
||||
Feature or Bugfix:Feature Binary Source:No
|
||||
PrivateCode(Yes/No):No
|
||||
|
||||
|
||||
提交的是Python编译文件(.pyc文件)。通常这些文件不应该被提交到版本控制中。让我先取消暂存这些文件,然后添加一个.gitignore文件来忽略它们:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
3. 读书笔记/摘要
|
||||
|
||||
场景:输入书名 + 要点或长文,生成摘要、金句、思维导图式大纲或读后感。
|
||||
|
||||
价值:学习与知识管理,和古诗词一样偏「文化/学习」类。
|
||||
|
||||
4. 育儿/教育助手
|
||||
|
||||
场景:按年龄、主题(习惯、情绪、学科)生成育儿建议、睡前故事、简单科普或习题讲解。
|
||||
|
||||
价值:家庭用户高频需求,可做「按主题 + 历史」管理。
|
||||
|
||||
5. 健身/运动计划
|
||||
|
||||
场景:根据目标(减脂/增肌/体能)、天数、场地(家/健身房),生成一周训练计划与动作要点。
|
||||
|
||||
价值:和「饭菜规划」搭配成「吃+练」健康组合。
|
||||
|
||||
6. 合同/条款解读
|
||||
|
||||
场景:上传或粘贴合同/条款原文,生成「人话版」要点、风险提示、建议关注条款。
|
||||
|
||||
价值:个人与小微商户都适用,可仅做解读不替代法律意见。
|
||||
|
||||
7. 小红书/短视频脚本
|
||||
|
||||
场景:输入产品/主题、风格(种草/教程/剧情),生成标题、分镜文案、口播稿。
|
||||
|
||||
价值:内容创作者刚需,和「周报」一样偏「写作类」但场景不同。
|
||||
|
||||
8. 面试模拟/问答准备
|
||||
|
||||
场景:选择岗位类型(技术/产品/运营等),AI 出题并给参考答案与点评,可多轮模拟。
|
||||
|
||||
价值:与「简历优化」形成求职一条龙。
|
||||
|
||||
9. 节日/祝福语生成
|
||||
|
||||
场景:选择节日或场合(春节、生日、婚礼、开业),生成祝福语、贺卡文案、红包配文。
|
||||
|
||||
价值:轻量、节日流量高,易做「收藏/历史」。
|
||||
|
||||
10. 个人复盘/周复盘
|
||||
|
||||
场景:输入本周大事、数据、感受,生成结构化复盘(做得好的、待改进、下周重点)。
|
||||
|
||||
价值:与「智能周报」互补:周报偏汇报,复盘偏自我总结。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
继续开发其他的ai应用
|
||||
特色功能中,还能上线哪些AI智能应用呢,推荐20个,可以先保留占位,功能可以先不开发
|
||||
|
||||
|
||||
|
||||
|
||||
## 新增 60 个占位应用一览(仅入口 + 占位页,功能未开发)
|
||||
|
||||
办公/效率(10)
|
||||
|
||||
会议议程生成、待办转日历、邮件模板库、周报模板、日报模板、工作日志、项目复盘、OKR 起草、会议邀请函、请假条/报销单
|
||||
|
||||
教育/学习(10)
|
||||
|
||||
错题本整理、知识点总结、作文批改、数学解题步骤、单词记忆计划、阅读理解题生成、口语练习话题、论文大纲、课堂笔记整理、考试押题建议
|
||||
|
||||
创作/内容(10)
|
||||
|
||||
公众号标题、知乎回答、抖音文案、B站视频简介、播客大纲、小说开篇、诗歌创作、对联生成、段子/笑话、广告语
|
||||
|
||||
生活/实用(10)
|
||||
|
||||
搬家清单、旅行 packing 清单、婚礼筹备清单、装修预算表、宠物喂养计划、植物养护指南、家电选购建议、保险对比解读、理财规划建议、记账分类建议
|
||||
|
||||
健康(8)
|
||||
|
||||
饮食建议、睡眠改善建议、久坐提醒/拉伸建议、体检报告解读、用药提醒说明、症状自查引导、心理健康自测、戒烟/戒糖计划
|
||||
|
||||
法律(4)
|
||||
|
||||
劳动合同要点、租房合同要点、借条/欠条模板、免责声明生成
|
||||
|
||||
技术/开发(8)
|
||||
|
||||
SQL 生成/优化、API 文档生成、正则表达式生成、命令行帮助、错误信息解读、Git 提交信息、技术博客大纲、需求转用户故事
|
||||
|
||||
|
||||
|
||||
|
||||
继续开发其他的ai应用
|
||||
特色功能中,还能上线哪些AI智能应用呢,我是一名高级android工程师,能对我工作起到帮助作用的AI智能应用推荐10个,可以先保留占位,功能可以先不开发
|
||||
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`替代常驻后台服务。
|
||||
|
||||
---
|
||||
|
||||
本手册提供了一个全面的框架。建议团队在实际项目中,根据自身应用的业务特点和常见问题,不断丰富和迭代这个手册,使其真正成为团队性能优化的“作战地图”。
|
||||
235
docs/学习笔记/Android整机性能分析与问题定位实战指南.md
Normal file
235
docs/学习笔记/Android整机性能分析与问题定位实战指南.md
Normal file
@@ -0,0 +1,235 @@
|
||||
好的,遵照您的指示。作为一名高级Android开发工程师,我将基于您提供的详细规范,为您撰写一份关于“Android整机性能分析与问题定位”的实战指南。这份指南将严格遵循您设定的技术背景、流程规范、质量标准和交付要求。
|
||||
|
||||
---
|
||||
|
||||
# 版本历史
|
||||
| 版本 | 日期 | 作者 | 变更描述 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| 1.0 | 2024-05-24 | AI Assistant | 初始版本,根据高级开发工程师规范制定 |
|
||||
|
||||
## 一、引言
|
||||
本指南旨在为高级Android开发工程师提供一个系统化、标准化的整机性能分析框架。当面对海量性能日志和Trace数据时,工程师可依据此文档进行高效、精准的问题定位,并产出高质量的根因分析与优化建议。
|
||||
|
||||
## 二、分析准备:数据预处理与验证
|
||||
|
||||
在开始深入分析前,必须确保输入数据的完整性和一致性。这是所有后续分析的基石。
|
||||
|
||||
### 2.1 数据完整性检查清单
|
||||
收到测试团队的bugreport或日志包后,请使用自动化脚本或手动验证以下核心文件是否存在且内容有效:
|
||||
|
||||
```bash
|
||||
# 示例脚本片段: check_integrity.sh
|
||||
#!/bin/bash
|
||||
BUGREPORT_DIR=$1
|
||||
echo "正在验证数据完整性于: $BUGREPORT_DIR"
|
||||
|
||||
# 必须文件列表
|
||||
REQUIRED_FILES=(
|
||||
"bugreport-*.txt" # 主bugreport报告
|
||||
"logcat.txt" # 完整logcat
|
||||
"kernel.log" | "dmesg.txt" # 内核日志
|
||||
"traces.txt" # ANR traces
|
||||
"perfetto-trace.perfetto-trace" | "systrace.html" # Perfetto或Systrace
|
||||
"dumpstate_board.txt" # 板级信息
|
||||
)
|
||||
|
||||
for pattern in "${REQUIRED_FILES[@]}"; do
|
||||
if ! ls $BUGREPORT_DIR/$pattern 1> /dev/null 2>&1; then
|
||||
echo "警告: 未找到匹配模式 '$pattern' 的文件。"
|
||||
else
|
||||
echo "✓ 找到: $(ls $BUGREPORT_DIR/$pattern | head -n 1)"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
### 2.2 时间同步与归一化
|
||||
不同日志源(如`logcat`和`kernel`)可能使用不同的时间基准。为统一分析,需将所有日志时间戳转换为相对时间(例如,相对于开机时间或第一个关键事件的时间)。
|
||||
|
||||
- **kernel (dmesg)**:通常为`[ 秒.微秒]`格式,表示系统启动后的时间。
|
||||
- **logcat**:可显示绝对时间或相对时间。在bugreport中通常包含绝对时间。
|
||||
|
||||
**最佳实践**:找到第一个ANR或关键事件在`logcat`中的绝对时间,然后在`dmesg`中找到对应时间点的内核日志,以建立时间关联。
|
||||
|
||||
## 三、系统性分析流程(黄金四步法)
|
||||
|
||||
按照“由粗到细、由表及里”的原则,分步执行分析。
|
||||
|
||||
### 3.1 第一步:初步症状定位与关键事件捕获 (5-10分钟)
|
||||
目标是快速识别问题的表象、发生时间和影响的进程。
|
||||
|
||||
1. **快速扫描ANR与崩溃**:
|
||||
```bash
|
||||
# 扫描logcat.txt
|
||||
grep -n "ANR in" logcat.txt --color=always
|
||||
grep -n "FATAL EXCEPTION" logcat.txt --color=always
|
||||
grep -n "am_anr" logcat.txt # ActivityManagerService的ANR事件日志
|
||||
```
|
||||
|
||||
2. **定位UI卡顿**:
|
||||
```bash
|
||||
# 查找Choreographer丢帧记录,通常以"Choreographer"或"Skipped X frames"开头
|
||||
grep -n "Choreographer.*Skipped" logcat.txt --color=always
|
||||
```
|
||||
|
||||
3. **检查系统关键错误**:
|
||||
```bash
|
||||
# 内核错误、低内存、服务死亡等
|
||||
grep -n "cancelled due to low memory" logcat.txt
|
||||
grep -ni "service.*died" logcat.txt
|
||||
grep -n "Out of memory" kernel.log
|
||||
```
|
||||
|
||||
4. **记录关键信息**:
|
||||
- **问题进程名与PID**:`com.example.app` (PID: 1234)
|
||||
- **发生时间戳**:`2024-05-24 10:30:15.123`
|
||||
- **问题类型**:`ANR (Input dispatching timed out)` 或 `UI Jank`
|
||||
|
||||
### 3.2 第二步:系统资源瓶颈分析 (15-30分钟)
|
||||
判断问题是否由系统全局资源(CPU、内存、IO)紧张引起。
|
||||
|
||||
#### 3.2.1 CPU调度分析
|
||||
- **工具**:Perfetto UI (ui.perfetto.dev) 或 `systrace`。
|
||||
- **操作**:加载trace文件。
|
||||
- **关注点**:
|
||||
1. **整体负载**:查看CPU Frequency和Core C-State图,是否存在长时间满频运行或核心被限频?
|
||||
2. **关键进程调度**:找到问题进程(PID)的“Slices”轨道。分析其在ANR/卡顿期间的调度情况:
|
||||
- **Running**:绿色,表示正在执行。如果占比高,说明它在忙于自己的计算。
|
||||
- **Runnable**:蓝色或深色,表示它想运行但CPU被其他任务抢占。这指向**CPU资源竞争**。
|
||||
- **Sleep/Uninterruptible Sleep**:灰色,表示它在等待(锁、IO、Binder)。这指向**锁竞争或IO阻塞**。
|
||||
3. **寻找“大胃王”**:按CPU占用排序,找出在问题时间段内抢占CPU的高负载进程(如媒体编解码、后台压缩任务)。
|
||||
|
||||
#### 3.2.2 内存压力检测
|
||||
- **工具**:`bugreport.txt` 中的 `meminfo` 部分,`dumpsys meminfo`,以及 `kernel.log`。
|
||||
- **操作**:在`bugreport`中搜索`Total RAM`和`Free Memory`。
|
||||
- **关注点**:
|
||||
1. **可用内存低**:`MemFree` + `Cached` 是否远低于总内存的20%?
|
||||
2. **Low Memory Killer(LMK) 日志**:在 `kernel.log` 或 `logcat` 中搜索 `lowmemorykiller` 或 `lmk`。频繁的LMK杀进程是内存严重不足的铁证。
|
||||
```
|
||||
[ pid= 937, uid= 123] (com.android.systemui) 杀进程以回收内存
|
||||
```
|
||||
3. **Zone Normal 水位**:在`dmesg`中查看`zoneinfo`,判断`normal` zone是否频繁进入`min`水位以下,触发直接或kswapd内存回收。内存回收(尤其是直接回收)是巨大的性能开销。
|
||||
|
||||
#### 3.2.3 I/O阻塞分析
|
||||
- **工具**:Perfetto (添加`f2fs`/`ext4` 和 `block` 数据源)。
|
||||
- **操作**:在Perfetto UI中放大问题时间窗口。
|
||||
- **关注点**:
|
||||
1. **进程状态为Uninterruptible Sleep (D状态)**:在进程的“State”轨道中,如果大量出现灰色(且工具提示为 `D`),表示进程正在执行不可中断的I/O等待。
|
||||
2. **Binder事务延迟**:查看Binder相关trace事件(`binder_transaction`, `binder_reply`)。在问题进程的Binder线程中,是否存在长时间未完成的Binder调用?调用方和被调用方分别是谁?
|
||||
3. **I/O操作**:打开`ftrace`事件中的`f2fs/iostat`或`block_rq_issue`,查看是否有大量或超大的块读写请求发生。
|
||||
|
||||
### 3.3 第三步:进程内细粒度分析 (30-60分钟)
|
||||
在排除或确认系统瓶颈后,深入分析问题进程自身的执行流。
|
||||
|
||||
#### 3.3.1 ANR根因分析
|
||||
- **工具**:`traces.txt` 文件,Perfetto。
|
||||
- **操作**:
|
||||
1. 在`traces.txt`中找到发生ANR的进程及其主线程的堆栈。
|
||||
2. **关键时间线分析 (P0要求)**:结合Perfetto,精确到微秒级,重建事件序列:
|
||||
- **T0**: Input事件发送到system_server。
|
||||
- **T1**: system_server将该事件分发给目标应用进程(通过Binder)。
|
||||
- **T2**: 目标应用主线程收到Binder调用,开始处理。
|
||||
- **T3 (ANR触发点)**: 5秒后,system_server未收到处理完成的信号,输出ANR。
|
||||
3. **调用栈深度分析 (P0要求)**:`traces.txt`中主线程的栈顶往往就是“罪魁祸首”。
|
||||
- **常见情况**:
|
||||
- **锁等待**:`waiting to lock <0x...>`,表明主线程在等待一个被其他线程持有的锁。需找到持锁线程的堆栈。
|
||||
- **Binder调用**:正在执行一个Binder同步调用,等待其他进程(通常是system_server)返回。需分析被调用端的耗时。
|
||||
- **自身耗时**:在应用代码或某系统库函数中循环,如复杂的布局、Json解析、数据库查询。
|
||||
|
||||
#### 3.3.2 UI Jank/渲染卡顿分析
|
||||
- **工具**:Perfetto (启用`vsync`, `gfx`, `input`)。
|
||||
- **操作**:
|
||||
1. 找到`SurfaceFlinger`和应用的`RenderThread`轨道。
|
||||
2. 分析一帧的完整流水线:
|
||||
- **App主线程 (UI Thread)**:处理Input,执行`onMeasure`, `onLayout`, `onDraw`。耗时过长会导致帧错过vsync。
|
||||
- **RenderThread**:将DisplayList绘制指令提交给GPU。耗时可能由复杂绘制指令引起。
|
||||
- **hwui**:等待GPU完成。可能由GPU负载过高或驱动问题引起。
|
||||
- **SurfaceFlinger**:合成各应用图层。如果此阶段耗时,可能是合成层数过多(Overdraw)或硬件合成器(HWC)能力不足。
|
||||
3. **定位最慢阶段**:哪一阶段的彩色条形图最长?哪个阶段导致了帧截止时间(红色垂直线)的错过?
|
||||
|
||||
### 3.4 第四步:关联分析与根因确认 (15分钟)
|
||||
将以上各步骤的发现关联起来,形成完整证据链。
|
||||
|
||||
- **案例**:初步定位有ANR,第二步发现内存紧张,LMK频繁杀进程。第三步发现被杀进程恰好是ANR进程依赖的一个关键服务(如ContentProvider所在进程)。那么,**根本原因**就不是主线程阻塞,而是**系统内存不足导致依赖服务被回收**,从而造成调用方ANR。
|
||||
- **验证**:查看ANR发生前,`logcat`中是否有`lmk`杀掉关键服务的日志?查看`traces.txt`中主线程是否在`binderTransaction`等待一个不存在的进程返回?
|
||||
|
||||
## 四、问题分类与优先级判定
|
||||
|
||||
根据分析结果,对问题进行定级。
|
||||
|
||||
- **P0级 (阻塞性)**:
|
||||
- **标准**:ANR持续时间 > 5秒,或连续丢帧 > 16帧。
|
||||
- **示例**:任何用户可见的ANR;启动应用时黑屏/白屏超过5秒;滑动列表持续卡顿。
|
||||
- **P1级 (严重)**:
|
||||
- **标准**:UI响应延迟 > 100ms,或内存泄漏 > 50MB,或发生系统服务重启。
|
||||
- **示例**:点击按钮后200ms才有反应;后台长期占用大量内存导致其他应用被LMK;system_server或surfaceflinger重启。
|
||||
- **P2级 (一般)**:
|
||||
- **标准**:单次操作偶尔延迟,或资源占用在极端场景下才出现问题。
|
||||
- **示例**:首次打开相册慢;在某些特定网络条件下,列表图片加载慢。
|
||||
|
||||
## 五、交付成果与质量标准
|
||||
|
||||
### 5.1 核心交付物:性能分析报告 (示例模板)
|
||||
|
||||
```markdown
|
||||
## 问题概述
|
||||
- **现象**:在视频播放界面快速滑动评论区时,界面严重卡顿,帧率降至个位数。
|
||||
- **影响范围**:视频类应用 `com.media.video` (PID: 9876) ,Android 12 设备。
|
||||
- **P等级**:P1 (严重 UI Jank)
|
||||
|
||||
## 根因分析
|
||||
### 关键时间线
|
||||
在Perfetto trace的时间窗口 `12:05:30.000` - `12:05:32.000` 内:
|
||||
|
||||
1. **UI Thread (TID 9876)**:在 `RecyclerView` 的 `onBindViewHolder` 中调用了 `MediaMetadataUtils.getAlbumArt()`,耗时约 **45ms**。
|
||||
2. **RenderThread (TID 9882)**:主线程耗时导致错过了 `Choreographer` 的 `doFrame` 回调,无法开始下一帧的绘制。
|
||||
3. **后续帧**:UI Thread 在多个连续的 `doFrame` 中均耗时 > 30ms,导致连续丢帧超过30帧。
|
||||
*(此处应插入Perfetto标记帧超时的截图)*
|
||||
|
||||
### 调用栈深度分析
|
||||
通过 `simpleperf` 对主线程采样,发现 `getAlbumArt()` 中的耗时操作最终调用到了 `BitmapFactory.decodeStream()` 从本地文件系统加载一张较大分辨率的图片。
|
||||
*(此处应插入调用栈火焰图或堆栈列表)*
|
||||
- **层1 (应用层)**:`com.media.video.adapter.CommentAdapter.onBindViewHolder`
|
||||
- **层2 (应用层)**:`com.media.video.utils.MediaMetadataUtils.getAlbumArt`
|
||||
- **层3 (Framework)**:`android.graphics.BitmapFactory.decodeStream`
|
||||
- **层4 (Native)**:`libskia.so` 中的 `SkCodec::getPixels`,显示正在解码一张 JPEG 图片。
|
||||
- **根因**:`RecyclerView` 滑动时,主线程同步解码磁盘上的高清图片,阻塞了UI绘制。
|
||||
|
||||
## 优化建议
|
||||
### 代码修改建议
|
||||
1. **异步加载**:使用 `Glide` 或 `Coil` 等图片加载库,在后台线程异步加载评论区的用户头像和缩略图。
|
||||
```kotlin
|
||||
// 原代码 (可能导致卡顿)
|
||||
// val bitmap = MediaMetadataUtils.getAlbumArt(context, mediaId)
|
||||
// holder.albumArt.setImageBitmap(bitmap)
|
||||
|
||||
// 优化后代码 (使用Coil)
|
||||
holder.albumArt.load(MediaMetadataUtils.getAlbumArtUri(mediaId)) {
|
||||
crossfade(true)
|
||||
placeholder(R.drawable.placeholder)
|
||||
}
|
||||
```
|
||||
2. **图片压缩**:如果必须直接解码,确保使用 `BitmapFactory.Options` 进行采样,降低加载到内存中的图片尺寸,减少解码时间。
|
||||
|
||||
### 配置调整方案
|
||||
- 无。此问题主要源于应用代码,无需系统配置调整。
|
||||
|
||||
## 支持证据
|
||||
1. `perfetto_trace_jank.html` (已标记问题区间)
|
||||
2. `trace_ui_thread_jank.txt` (主线程堆栈片段)
|
||||
3. `cpu_profile.data` (Simpleperf性能采样数据)
|
||||
```
|
||||
|
||||
### 5.2 可复现分析脚本
|
||||
创建自动化脚本来提取关键指标,例如:
|
||||
- `extract_anr_traces.py`:自动解析`traces.txt`和`logcat`,生成ANR摘要报告。
|
||||
- `perfetto_metric.sh`:调用Perfetto命令行工具,计算指定时间段内的平均帧率、Jank次数。
|
||||
|
||||
### 5.3 质量检查清单
|
||||
在提交报告前,使用以下清单进行自查:
|
||||
|
||||
- [x] **P0/P1问题全覆盖**:所有ANR、严重卡顿均有关联分析。
|
||||
- [x] **根因精确**:是否定位到具体函数/代码行/系统配置?(例如:`com.example.app$MyClass.onClick (line: 250)`, 而不是“主线程阻塞”)
|
||||
- [x] **证据链完整**:是否从现象、日志、trace到代码修改建议形成了逻辑闭环?
|
||||
- [x] **数据量化**:优化前后的性能对比是否有数据支撑?(例如:帧率从15fps提升到55fps,内存占用降低80MB)
|
||||
- [x] **报告规范**:报告格式是否符合Markdown模板要求?关键部分是否包含截图?
|
||||
- [x] **可复现性**:分析脚本是否能在Linux/macOS环境顺利运行?依赖是否清晰?
|
||||
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` 触发条件、多显示器支持)作为补充要求填入第二个的“高级主题”部分。这样既能保证课程的逻辑性,又能确保技术覆盖的完整性。
|
||||
@@ -88,6 +88,12 @@ nav:
|
||||
- 学习笔记/看应用卡顿问题.md
|
||||
- 学习笔记/perfetto分析trace提效的办法或者方案.md
|
||||
- 学习笔记/perfetto看trace关键词.md
|
||||
- 学习笔记/Android WindowManagerService (WMS) 架构深度解析(第一次提示词).md
|
||||
- 学习笔记/Android WindowManagerService核心原理深度解析(专家第二次提示词).md
|
||||
- 学习笔记/Android性能分析标准化操作手册.md
|
||||
- 学习笔记/Android整机性能分析与问题定位实战指南.md
|
||||
- 学习笔记/Android游戏整机性能卡顿与丢帧根因分析与优化指南.md
|
||||
- 学习笔记/两份提示词对比.md
|
||||
- Obsidian笔记:
|
||||
- Obsidian/2026-01-05 个人文档管理.md
|
||||
- Obsidian/高频命令.md
|
||||
@@ -244,6 +250,7 @@ nav:
|
||||
- Obsidian笔记体系/Projects/女童生长激素项目/配置完成.md
|
||||
- Obsidian笔记体系/Projects/女童生长激素项目/项目目录位置.md
|
||||
- Obsidian笔记体系/Projects/提示词大师/提示词大师.md
|
||||
- Obsidian笔记体系/Projects/aitsc/ai提示词常用命令.md
|
||||
- gerrit:
|
||||
- gerrit/gerrit上传代码详细指南.md
|
||||
- gerrit/gerrit分支规范.md
|
||||
@@ -385,6 +392,8 @@ nav:
|
||||
- Google开发文档体系/视频和教程/在线课程.md
|
||||
- Google开发文档体系/视频和教程/官方视频教程.md
|
||||
- Google开发文档体系/视频和教程/技术会议.md
|
||||
- 其他:
|
||||
- '# Android WindowManagerService (WMS) 架构深度解析(专家第一次提示词).md'
|
||||
|
||||
markdown_extensions:
|
||||
- pymdownx.highlight:
|
||||
|
||||
Reference in New Issue
Block a user