编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

    • 库的解读

    • 专栏博客

      • 系统启动Zygote
      • Binder通信原理
      • Handler消息机制
      • Activity启动原理
      • 四大组件原理分析
      • AMS与组件管理
      • View绑制与渲染
      • 事件分发机制
      • Surface渲染原理
      • 自定义View设计
      • WMS窗口管理
      • PMS与APK安装
      • 虚拟机与类加载
      • 内存管理与GC
      • 线程与并发编程
      • 性能优化与监控
        • 一、引言:什么是卡顿
        • 二、渲染管线与帧率
          • 2.1 一帧的渲染流程
          • 2.2 RenderThread与GPU渲染管线
          • 2.3 掉帧的几种情况
        • 三、主线程卡顿的根因分析
          • 3.1 常见卡顿原因分类
          • 3.2 StrictMode检测
        • 四、卡顿监控方案原理
          • 4.1 各方案对比
        • 五、Looper Printer监控
          • 5.1 BlockCanary核心原理
          • 5.2 监控实现
          • 5.3 方案的局限与改进
        • 六、Choreographer帧监控
          • 6.1 帧回调监控
          • 6.2 FrameMetrics API(Android 7+)
        • 七、ANR的触发与监控
          • 7.1 ANR触发条件
          • 7.2 ANR监控方案
          • 7.3 traces.txt分析
        • 八、启动速度优化
          • 8.1 启动阶段划分
          • 8.2 具体优化策略
          • 8.3 启动耗时测量
        • 九、布局渲染优化
          • 9.1 布局性能问题定位
          • 9.2 具体优化策略
        • 十、IO与线程优化
          • 10.1 SharedPreferences的坑
          • 10.2 线程优化策略
          • 10.3 数据库优化
        • 十一、Systrace与Perfetto分析
          • 11.1 Systrace使用
          • 11.2 Perfetto(Android 10+推荐)
          • 11.3 自定义Trace标记
          • 11.4 Perfetto Trace分析技巧
        • 十一点五、字节码插桩监控原理
          • 11.5.1 ASM插桩的工作原理
          • 12.2 Hook方案(运行时修改)
        • 十四、面试高频问题与深度分析
          • 14.1 如何检测卡顿?
          • 12.2 如何优化RecyclerView的卡顿?
          • 14.3 线上ANR如何排查?
        • 十三、性能优化的工程化实践
          • 13.1 性能监控体系搭建
          • 13.2 性能优化的CI/CD集成
          • 13.3 线上监控的数据分析
          • 14.4 性能优化常见面试题
        • 十五、总结
      • 序列化与数据存储
      • 组件化与路由设计
      • 插件化与热修复
      • NDK开发实践
      • WebView核心设计
      • ADB常见使用操作
    • 智能硬件

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

  • Apps
  • Android提升进阶
  • 专栏博客
杨充
2026-04-14
目录

性能优化与监控

# 16.性能优化与监控

# 目录介绍

  • 一、引言:什么是卡顿
  • 二、渲染管线与帧率
    • 2.1 一帧的渲染流程
    • 2.2 RenderThread与GPU渲染管线
    • 2.3 掉帧的几种情况
  • 三、主线程卡顿的根因分析
    • 3.1 常见卡顿原因分类
    • 3.2 StrictMode检测
  • 四、卡顿监控方案原理
    • 4.1 各方案对比
  • 五、Looper Printer监控
    • 5.1 BlockCanary核心原理
    • 5.2 监控实现
    • 5.3 方案的局限与改进
  • 六、Choreographer帧监控
    • 6.1 帧回调监控
    • 6.2 FrameMetrics API(Android 7+)
  • 七、ANR的触发与监控
    • 7.1 ANR触发条件
    • 7.2 ANR监控方案
    • 7.3 traces.txt分析
  • 八、启动速度优化
    • 8.1 启动阶段划分
    • 8.2 具体优化策略
    • 8.3 启动耗时测量
  • 九、布局渲染优化
    • 9.1 布局性能问题定位
    • 9.2 具体优化策略
  • 十、IO与线程优化
    • 10.1 SharedPreferences的坑
    • 10.2 线程优化策略
    • 10.3 数据库优化
  • 十一、Systrace与Perfetto分析
    • 11.1 Systrace使用
    • 11.2 Perfetto(Android 10+推荐)
    • 11.3 自定义Trace标记
    • 11.4 Perfetto Trace分析技巧
  • 十二、字节码插桩监控原理
    • 12.1 ASM插桩的工作原理
    • 12.2 Hook方案(运行时修改)
  • 十三、性能监控体系与工程化实践
    • 13.1 性能监控体系搭建
    • 13.2 性能优化的CI/CD集成
    • 13.3 线上监控的数据分析
  • 十四、面试高频问题与深度分析
    • 14.1 如何检测卡顿?
    • 14.2 如何优化RecyclerView的卡顿?
    • 14.3 线上ANR如何排查?
    • 14.4 性能优化常见面试题
  • 十五、总结

# 一、引言:什么是卡顿

用户感知到的"卡顿"本质是帧率下降——显示器需要每16.6ms(60fps)刷新一帧画面,如果某一帧的渲染超时,用户就会感受到不流畅。

帧率与用户感知:
60fps  → 16.6ms/帧 → 基本流畅
90fps  → 11.1ms/帧 → 丝滑顺畅
120fps → 8.3ms/帧  → 极致流畅
<30fps → >33ms/帧  → 明显卡顿
1
2
3
4
5

疑惑:如何自动检测和定位卡顿?BlockCanary、Matrix等工具的监控原理是什么?


# 二、渲染管线与帧率

# 2.1 一帧的渲染流程

一帧16.6ms内需要完成的工作:

VSync信号到来
    ↓ ┌──── 主线程 ────────────────────────┐
    │ │ Input处理(触摸/按键事件)           │
    │ │ Animation计算(属性动画/转场动画)   │
    │ │ Measure → Layout → Draw            │
    │ │ → 构建/更新DisplayList              │
    │ └──── sync到RenderThread ─────────────┘
    │ ┌──── RenderThread ─────────────────────┐
    │ │ 处理DisplayList → OpenGL/Vulkan命令   │
    │ │ GPU绘制 → swapBuffers                │
    │ └───────────────────────────────────────┘
    ↓
下一个VSync信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 2.2 RenderThread与GPU渲染管线

RenderThread的工作细节(Android 5.0+):

主线程与RenderThread的协作:

主线程:                          RenderThread:
┌──────────────────────┐        ┌──────────────────────┐
│ 处理Input事件          │        │                      │
│ 执行动画回调            │        │ 等待主线程sync...     │
│ measure / layout       │        │                      │
│ draw (构建DisplayList) │        │                      │
│ ────sync────────────→ │ sync   │ 接收DisplayList       │
│                        │  │     │ 遍历RenderNode树      │
│ 可以处理下一帧的Input   │  │     │ 转换为GPU命令         │
│                        │  │     │ glDrawXxx()           │
│                        │  │     │ eglSwapBuffers()      │
│                        │        │ → Buffer提交到SF      │
└──────────────────────┘        └──────────────────────┘

sync过程的细节:
  主线程将DisplayList树的变更同步到RenderThread
  这个过程需要两个线程短暂互斥(类似Stop-the-world)
  sync耗时一般 < 1ms
  但如果DisplayList太大(复杂布局),sync可能超时

RenderThread的独立优势:
  即使主线程被短暂阻塞(如GC)
  RenderThread仍在执行上一帧的GPU渲染
  → 部分掉帧可以被掩盖
  
  属性动画(alpha/translation/rotation):
  → RenderThread可以独立执行,不需要主线程参与
  → 即使主线程在做IO,动画仍然流畅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 2.3 掉帧的几种情况

情况1:主线程耗时(最常见)
主线程:|Input|Animation|───耗时操作───|Measure|Layout|Draw|
        ←──────────── 超过16.6ms ─────────────────────→

情况2:RenderThread耗时
主线程完成了,但GPU绘制太慢 → 帧没能按时提交

情况3:GC停顿
主线程被GC暂停 → 该帧所有工作被延迟

情况4:CPU频率被压制
省电模式/温控 → CPU降频 → 所有操作变慢
1
2
3
4
5
6
7
8
9
10
11
12

# 三、主线程卡顿的根因分析

# 3.1 常见卡顿原因分类

1. CPU密集型
   ├── 复杂计算(JSON解析、数据处理)
   ├── 反射调用(Method.invoke开销大)
   └── 正则表达式匹配

2. IO阻塞
   ├── 文件读写(SharedPreferences.commit)
   ├── 数据库操作(SQLite查询)
   └── ContentProvider查询

3. 锁等待
   ├── synchronized锁竞争
   ├── 等待子线程结果(Future.get)
   └── Binder调用等待

4. 渲染负担
   ├── 布局太深(measure/layout耗时)
   ├── 过度绘制(GPU负载高)
   └── 大量invalidate

5. 内存相关
   ├── GC频繁(内存抖动)
   └── 大对象分配(触发GC)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.2 StrictMode检测

// 开发阶段使用StrictMode检测主线程违规操作
if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .detectCustomSlowCalls()
            .penaltyLog()
            .build());
    
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
            .detectLeakedSqlLiteObjects()
            .detectLeakedClosableObjects()
            .detectActivityLeaks()
            .penaltyLog()
            .build());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 四、卡顿监控方案原理

# 4.1 各方案对比

┌─────────────────┬────────────────┬──────────────┬──────────────┐
│ 方案             │ 原理            │ 优点          │ 缺点          │
├─────────────────┼────────────────┼──────────────┼──────────────┤
│ Looper Printer  │ 消息处理耗时    │ 简单无侵入    │ 精度有限      │
│ Choreographer   │ 帧间隔         │ 精确帧率      │ 无堆栈信息    │
│ ANR Watchdog    │ 主线程阻塞检测  │ 获取ANR前堆栈 │ 阈值较高      │
│ 字节码插桩       │ 方法级耗时      │ 精确定位慢方法 │ 包体积增加    │
│ Systrace/Perfetto│系统级跟踪      │ 最全面        │ 仅线下分析    │
└─────────────────┴────────────────┴──────────────┴──────────────┘
1
2
3
4
5
6
7
8
9
各方案的适用阶段和选择建议:

开发阶段(线下):
  首选 Systrace/Perfetto → 最全面的系统级分析
  配合 GPU呈现模式 → 快速发现掉帧
  使用 StrictMode → 检测主线程IO等违规操作

测试阶段(线下):
  字节码插桩(Matrix TraceCanary) → 精确定位慢方法
  Choreographer帧监控 → 统计掉帧率
  Android Profiler → 实时监控CPU/内存/网络

线上监控:
  Looper Printer → 轻量级,监控主线程消息耗时
  Choreographer帧回调 → 实时帧率采集
  ANR Watchdog → 检测卡死
  结合采样 → 只采集部分用户数据,降低性能影响

推荐的线上监控组合:
  帧率监控(发现问题)+ Looper Printer(定位消息)+ 堆栈采样(定位方法)
  → 三者结合可以完整定位卡顿原因
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 五、Looper Printer监控

# 5.1 BlockCanary核心原理

// Looper.loop()中的关键代码:
public static void loop() {
    for (;;) {
        Message msg = queue.next();
        
        // 消息处理前打印
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target);
        }
        
        msg.target.dispatchMessage(msg);  // 处理消息
        
        // 消息处理后打印
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 5.2 监控实现

Looper.getMainLooper().setMessageLogging(new Printer() {
    private long startTime;
    private StackSampler stackSampler;
    
    @Override
    public void println(String x) {
        if (x.startsWith(">>>>>")) {
            startTime = System.currentTimeMillis();
            stackSampler.startSampling();  // 开始子线程采样堆栈
        } else if (x.startsWith("<<<<<")) {
            long duration = System.currentTimeMillis() - startTime;
            stackSampler.stopSampling();
            
            if (duration > THRESHOLD) {  // 如500ms
                String stack = stackSampler.getStackTraces();
                reportBlock(duration, stack);
            }
        }
    }
});

// 堆栈采样的关键:在子线程中定期获取主线程堆栈
class StackSampler {
    void sample() {
        StackTraceElement[] stackTrace = mainThread.getStackTrace();
        buffer.put(System.currentTimeMillis(), stackTrace);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 5.3 方案的局限与改进

局限性:
1. 只能检测消息级别的卡顿,无法发现消息之间的空档期卡顿
2. 堆栈采样是概率性的,可能采不到真正耗时的代码
3. Printer本身有一定开销(字符串拼接)

改进方案:
1. 高频采样(如每50ms采样一次)→ 提高命中率
2. 配合Choreographer监控帧率 → 更准确判断掉帧
3. 使用ASM字节码插桩 → 每个方法前后插入耗时统计代码
1
2
3
4
5
6
7
8
9

# 六、Choreographer帧监控

# 6.1 帧回调监控

Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    private long lastFrameTimeNanos = 0;
    
    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTimeNanos != 0) {
            long intervalMs = (frameTimeNanos - lastFrameTimeNanos) / 1_000_000;
            
            if (intervalMs > 16.6) {
                int droppedFrames = (int) (intervalMs / 16.6) - 1;
                Log.w("FPS", "丢弃 " + droppedFrames + " 帧, 耗时 " + intervalMs + "ms");
            }
            
            float fps = 1000f / intervalMs;
            fpsMonitor.report(fps);
        }
        
        lastFrameTimeNanos = frameTimeNanos;
        Choreographer.getInstance().postFrameCallback(this);
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 6.2 FrameMetrics API(Android 7+)

window.addOnFrameMetricsAvailableListener(
    (window1, frameMetrics, dropCount) -> {
        long inputDuration = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
        long animDuration = frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
        long measureDuration = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
        long drawDuration = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
        long syncDuration = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
        long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
        
        // 精确定位是哪个阶段导致掉帧
        if (totalDuration > 16_666_666) { // 纳秒
            analyzeSlowFrame(inputDuration, animDuration, measureDuration, 
                            drawDuration, syncDuration);
        }
    }, new Handler());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 七、ANR的触发与监控

# 7.1 ANR触发条件

ANR类型及超时时间:

1. InputDispatching ANR(5秒)
   → 输入事件5秒内未处理完毕

2. BroadcastReceiver ANR
   → 前台广播10秒 / 后台广播60秒

3. Service ANR
   → 前台Service 20秒 / 后台Service 200秒

4. ContentProvider ANR(10秒)
1
2
3
4
5
6
7
8
9
10
11
12

# 7.2 ANR监控方案

// 方案1:ANR-WatchDog
// 在子线程中定期检查主线程是否响应
class ANRWatchDog extends Thread {
    private volatile long lastTick = 0;
    private volatile boolean reported = false;
    
    private final Runnable tickRunnable = () -> {
        lastTick = 0;  // 主线程能执行说明没卡
        reported = false;
    };
    
    @Override
    public void run() {
        while (!isInterrupted()) {
            lastTick = SystemClock.uptimeMillis();
            mainHandler.post(tickRunnable);
            
            try {
                Thread.sleep(5000);  // 等待5秒
            } catch (InterruptedException e) {
                return;
            }
            
            if (lastTick != 0 && !reported) {
                // 5秒后主线程还没执行tickRunnable → ANR
                reported = true;
                StackTraceElement[] stack = Looper.getMainLooper().getThread().getStackTrace();
                reportANR(stack);
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 7.3 traces.txt分析

关键信息:
"main" prio=5 tid=1 Blocked
  at com.example.app.MainActivity.onResume(MainActivity.java:42)
  - waiting to lock <0x0123456> held by thread 15

"Thread-15" prio=5 tid=15 Waiting
  at java.lang.Thread.sleep(Native Method)
  - locked <0x0123456>

分析:主线程等待Thread-15释放锁,Thread-15在sleep → 死锁导致ANR
1
2
3
4
5
6
7
8
9
10

# 八、启动速度优化

# 8.1 启动阶段划分

冷启动时间 = T(进程创建) + T(Application) + T(Activity) + T(首帧渲染)

T1: 进程创建 → Zygote fork + 加载APK
T2: Application → attachBaseContext + ContentProvider.onCreate + onCreate
T3: Activity → onCreate + setContentView(inflate)
T4: 首帧 → onResume + measure + layout + draw

优化目标:冷启动 < 1秒
1
2
3
4
5
6
7
8

# 8.2 具体优化策略

1. Application.onCreate优化
   ├── 延迟初始化:非必要SDK放在IdleHandler
   ├── 异步初始化:线程池并行初始化
   └── 按需初始化:用到时才加载

2. Activity优化
   ├── 减少setContentView层级
   ├── ViewStub延迟加载
   └── AsyncLayoutInflater异步Inflate

3. 启动窗口优化
   ├── windowBackground设置启动背景图
   └── SplashScreen API(Android 12+)

4. 类加载优化
   ├── 减少Application中的类引用数量
   └── 关键类预加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 8.3 启动耗时测量

# 方法1:adb命令测量
adb shell am start -W com.example.app/.MainActivity
# 输出:
# ThisTime: 385ms  → 最后一个Activity启动耗时
# TotalTime: 520ms → 从startActivity到Activity可见的总耗时
# WaitTime: 545ms  → 包含前一个Activity的pause时间

# 方法2:代码埋点
class App : Application() {
    companion object {
        val launchStartTime = SystemClock.uptimeMillis()
    }
}

class MainActivity : Activity() {
    override fun onWindowFocusChanged(hasFocus: Boolean) {
        if (hasFocus) {
            val launchTime = SystemClock.uptimeMillis() - App.launchStartTime
            Log.d("Launch", "冷启动耗时: ${launchTime}ms")
        }
    }
}

# 方法3:Reported Fully Drawn(推荐)
// 在数据加载完成、首屏渲染完成后调用
reportFullyDrawn()
// 会在logcat中输出:Fully drawn xxx/.MainActivity: +1s200ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 九、布局渲染优化

# 9.1 布局性能问题定位

工具1:Layout Inspector
→ 查看View层级和属性

工具2:GPU呈现模式
→ 柱状图查看每帧渲染耗时
→ 绿线=16.6ms

工具3:Show GPU Overdraw
→ 无色(0x) → 蓝(1x) → 绿(2x) → 粉(3x) → 红(4x+)

工具4:Systrace/Perfetto
→ 精确到方法级别
1
2
3
4
5
6
7
8
9
10
11
12

# 9.2 具体优化策略

1. 减少层级
   ├── ConstraintLayout替代多层嵌套
   ├── merge标签减少include额外层级
   └── ViewStub延迟加载

2. 减少过度绘制
   ├── 移除不必要的背景
   ├── clipRect裁剪不可见区域
   └── 减少alpha使用

3. RecyclerView优化
   ├── DiffUtil增量更新
   ├── setHasFixedSize(true)
   ├── RecycledViewPool复用
   ├── 预加载setInitialPrefetchItemCount
   └── 避免onBindViewHolder创建对象

4. Compose优化(如使用Jetpack Compose)
   ├── 稳定类型标记@Stable
   ├── 避免不必要的重组
   └── derivedStateOf减少计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 十、IO与线程优化

# 10.1 SharedPreferences的坑

SP的性能问题:

1. apply()可能导致ANR
   → apply()的写操作加入QueuedWork
   → Activity.onPause/onStop时等待QueuedWork完成
   → 写入量大会阻塞主线程

2. 全量写入
   → 每次保存都是全量序列化XML

3. 加载阻塞
   → getString()等会等待文件加载完

替代方案:
├── MMKV → mmap高性能
├── DataStore → 协程异步
└── SQLite → 结构化数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 10.2 线程优化策略

线程数过多的问题:
→ 线程创建开销(栈默认1MB)
→ 上下文切换开销
→ 线程数超限导致OOM

优化方案:
1. 统一线程池
   → 全局共享一个线程池,避免各SDK各自创建
   → 区分IO密集型和CPU密集型任务

2. 合理的线程池配置
   // IO密集型
   val ioPool = Executors.newFixedThreadPool(
       Runtime.getRuntime().availableProcessors() * 2)
   
   // CPU密集型
   val cpuPool = Executors.newFixedThreadPool(
       Runtime.getRuntime().availableProcessors())

3. 协程替代线程
   → Kotlin协程开销远小于线程
   → 挂起而非阻塞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 10.3 数据库优化

SQLite性能优化:

1. 使用事务批量操作
   db.beginTransaction();
   try {
       for (item : items) {
           db.insert(...);
       }
       db.setTransactionSuccessful();
   } finally {
       db.endTransaction();
   }
   // 100条insert:无事务约500ms → 有事务约10ms

2. 使用索引
   CREATE INDEX idx_user_name ON users(name);
   // 10万条数据查询:无索引约200ms → 有索引约1ms

3. WAL模式
   db.enableWriteAheadLogging();
   // 允许读写并发,提升多线程访问性能

4. 预编译语句
   SQLiteStatement stmt = db.compileStatement("INSERT INTO users VALUES(?,?)");
   // 复用stmt多次执行,避免重复编译SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 十一、Systrace与Perfetto分析

# 11.1 Systrace使用

# 抓取Systrace
python systrace.py -a com.example.app -t 5 \
    gfx input view wm am sm sched freq idle \
    -o trace.html

# 关键标记查看:
# Choreographer#doFrame → 每帧的起点
# measure/layout/draw → 各阶段耗时
# RenderThread → GPU渲染耗时
# SurfaceFlinger → 合成耗时
1
2
3
4
5
6
7
8
9
10

# 11.2 Perfetto(Android 10+推荐)

# 使用Perfetto抓取
adb shell perfetto \
    -c - --txt \
    -o /data/misc/perfetto-traces/trace.perfetto-trace \
    <<EOF
buffers: { size_kb: 65536 fill_policy: RING_BUFFER }
data_sources: { config { name: "linux.ftrace" ftrace_config {
    ftrace_events: "sched/sched_switch"
    ftrace_events: "power/suspend_resume"
    atrace_categories: "gfx"
    atrace_categories: "view"
    atrace_categories: "wm"
    atrace_apps: "com.example.app"
}}}
duration_ms: 5000
EOF

# 在 https://ui.perfetto.dev 打开分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 11.3 自定义Trace标记

// 在代码中添加自定义跟踪标记
import android.os.Trace;

void loadData() {
    Trace.beginSection("loadData");  // 开始
    try {
        Trace.beginSection("parseJSON");
        parseJSON(data);
        Trace.endSection();  // 结束parseJSON
        
        Trace.beginSection("saveDB");
        saveToDB(parsed);
        Trace.endSection();  // 结束saveDB
    } finally {
        Trace.endSection();  // 结束loadData
    }
}

// 在Systrace/Perfetto中可以看到自定义区间的耗时
// 精确定位哪个方法是瓶颈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 11.4 Perfetto Trace分析技巧

Perfetto Trace的高级分析方法:

1. 识别帧时间线
   在Perfetto UI中查找:
   ├── SurfaceFlinger行 → 每帧的合成时间
   ├── App主线程行 → Choreographer#doFrame标记
   ├── RenderThread行 → DrawFrame标记
   └── GPU行 → GPU完成时间
   
   正常帧:
   |doFrame(8ms)|sync(0.5ms)|DrawFrame(4ms)|GPU(3ms)| < 16.6ms ✓
   
   掉帧:
   |doFrame(25ms)                           |sync|DrawFrame|GPU| > 16.6ms ✗
   └── 主线程耗时过长

2. CPU调度分析
   查看主线程的调度状态:
   ├── Running (绿色) → 正在CPU上执行
   ├── Runnable (蓝色) → 等待CPU调度(CPU繁忙)
   ├── Sleeping (灰色) → 等待唤醒(lock/wait/sleep)
   └── Uninterruptible Sleep (橙色) → 等待IO(磁盘/网络)
   
   如果主线程大量处于:
   Runnable → CPU竞争激烈,其他线程占用CPU
   Sleeping → 等待锁(检查是否有死锁/锁竞争)
   Uninterruptible Sleep → 主线程在做IO(必须移到后台线程)

3. Binder调用追踪
   在App进程→system_server进程之间的Binder调用:
   App主线程  → binder transaction → system_server处理 → binder reply → App继续
   
   如果system_server忙碌 → App主线程被阻塞等待 → 掉帧
   
   常见的Binder耗时调用:
   ├── PackageManager.getApplicationInfo()
   ├── WindowManager.addView()
   ├── ActivityManager.getRunningAppProcesses()
   └── ContentProvider.query()

4. 内存与GC分析
   在Perfetto中查看GC事件:
   ├── young_gc → Minor GC(通常 < 2ms)
   ├── full_gc → Major GC(可能 > 10ms)
   └── concurrent_gc → 并发GC(对主线程影响小)
   
   如果频繁看到GC事件 → 内存抖动 → 优化对象创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 十一点五、字节码插桩监控原理

# 11.5.1 ASM插桩的工作原理

字节码插桩(Matrix TraceCanary的方案):

编译时:ASM框架修改字节码
┌───────────────────────────────────┐
│ 原始代码:                         │
│ void doSomething() {               │
│     // 业务逻辑                     │
│ }                                  │
│                                    │
│ 插桩后:                           │
│ void doSomething() {               │
│     TraceMethod.i(methodId);       │  ← 方法开始
│     // 业务逻辑                     │
│     TraceMethod.o(methodId);       │  ← 方法结束
│ }                                  │
└───────────────────────────────────┘

运行时:记录每个方法的耗时
TraceMethod.i(id) → 记录方法开始时间戳
TraceMethod.o(id) → 记录方法结束时间戳 → 计算耗时

优化策略(避免全量插桩的性能影响):
├── 只插桩项目代码,排除系统API
├── 排除简单的getter/setter方法
├── 使用long数组存储时间戳(避免对象创建)
├── 方法ID使用int而非String(减少内存)
└── 环形缓冲区存储最近的调用记录

检测到卡顿时:
  回溯环形缓冲区 → 还原出方法调用树 → 精确定位耗时方法
  
  输出示例:
  ├── MainActivity.onCreate() [520ms]
  │   ├── initSDK() [200ms]
  │   │   └── ThirdPartySDK.init() [195ms]  ← 这是瓶颈!
  │   ├── setContentView() [150ms]
  │   │   └── LayoutInflater.inflate() [145ms]
  │   └── loadData() [170ms]
  │       ├── DatabaseHelper.query() [120ms]
  │       └── parseResult() [50ms]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 12.2 Hook方案(运行时修改)

运行时Hook方案对比:

1. Epic/Pine(Inline Hook)
   → 修改方法入口的机器码
   → 跳转到自定义的Hook函数
   → 优点:精确到单个方法
   → 缺点:兼容性问题(不同CPU架构/ART版本)

2. Frida(注入Hook)
   → 注入进程后修改ART内部结构
   → 适合调试分析
   → 缺点:不适合线上使用

3. ASM编译时插桩(推荐)
   → 编译期修改字节码
   → 无运行时兼容性问题
   → 优点:稳定可靠
   → 缺点:需要重新编译

线上监控推荐方案:
  ASM编译时插桩 + Looper Printer + Choreographer帧监控
  → 三种方案结合使用
  → 先通过帧监控发现掉帧
  → 再通过Looper Printer定位消息级别卡顿
  → 最后通过方法耗时还原调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 十四、面试高频问题与深度分析

# 14.1 如何检测卡顿?

方案1:Looper Printer
→ 监控每个Message处理耗时
→ 优点:简单,线上可用
→ 代表:BlockCanary

方案2:Choreographer FrameCallback
→ 监控每帧间隔
→ 优点:精确FPS数据

方案3:ANR-WatchDog
→ 子线程定时检查主线程响应
→ 优点:可以提前于系统ANR获取堆栈

方案4:字节码插桩
→ ASM在每个方法前后插入计时代码
→ 优点:精确到方法级别
→ 代表:Matrix TraceCanary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 12.2 如何优化RecyclerView的卡顿?

层面1:数据层
├── DiffUtil → 增量更新替代notifyDataSetChanged
├── Paging → 分页加载大数据集
└── 后台线程计算Diff

层面2:布局层
├── setHasFixedSize(true) → 避免多余requestLayout
├── 减少Item布局层级
└── 使用ConstraintLayout简化布局

层面3:绑定层
├── 避免onBindViewHolder中创建对象
├── 图片异步加载(Glide/Coil)
└── 预取setInitialPrefetchItemCount

层面4:缓存层
├── setItemViewCacheSize → 调整缓存大小
├── RecycledViewPool → 多个RecyclerView共享
└── setRecycleChildrenOnDetach → 控制回收时机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 14.3 线上ANR如何排查?

线上ANR排查流程:

1. 收集信息
   ├── ANR发生时的主线程堆栈
   ├── 所有线程堆栈
   ├── CPU使用率
   ├── 内存状况
   └── 磁盘IO状况

2. 分类分析
   ├── 主线程死锁 → 查找lock关键字
   ├── 主线程IO → 查找read/write/query
   ├── 主线程Binder调用 → 查找transact
   ├── CPU满载 → 查看CPU usage
   └── 内存不足 → 查看GC日志

3. 常见根因
   ├── SharedPreferences.apply()在onPause等待
   ├── ContentProvider查询耗时
   ├── 第三方SDK主线程初始化
   ├── WebView初始化
   └── 多进程共享文件锁竞争
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 十三、性能优化的工程化实践

# 13.1 性能监控体系搭建

一套完整的性能监控体系需要覆盖以下维度:

性能监控体系:

线上监控:
├── 启动耗时
│   ├── 冷启动时间(进程创建到首帧显示)
│   ├── 温启动时间(Activity重建到首帧显示)
│   └── 热启动时间(Activity从后台恢复)
│
├── 页面渲染
│   ├── 帧率FPS(目标60fps/90fps/120fps)
│   ├── 掉帧次数和严重程度
│   └── 页面加载完成时间
│
├── 卡顿检测
│   ├── 主线程耗时方法统计
│   ├── ANR发生率
│   └── 用户感知卡顿率
│
├── 内存监控
│   ├── Java堆内存使用量
│   ├── Native内存使用量
│   ├── OOM发生率
│   └── 大图检测
│
└── IO监控
    ├── 主线程IO操作检测
    ├── 数据库查询耗时
    └── 网络请求耗时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 13.2 性能优化的CI/CD集成

将性能监控融入开发流程:

1. 编译阶段
   ├── Lint规则检查:检测主线程IO、大图资源等
   ├── 方法数/包体积监控:每次构建对比增量
   └── 自动化字节码插桩:ASM插入性能监控代码

2. 测试阶段
   ├── 自动化性能测试(Macrobenchmark)
   │   ├── 冷启动时间
   │   ├── 滑动帧率
   │   └── 页面加载时间
   ├── Monkey测试 + 性能数据采集
   └── 内存泄漏自动检测(LeakCanary集成CI)

3. 发布阶段
   ├── 灰度发布 + 性能指标门禁
   │   └── 启动时间 > 基线1.2倍 → 阻断发布
   ├── A/B实验对比性能指标
   └── 自动回滚机制

4. 线上阶段
   ├── 实时性能大盘(启动、帧率、ANR率)
   ├── 异常报警(ANR率突增、OOM突增)
   └── 用户分群分析(低端机/高端机)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 13.3 线上监控的数据分析

线上性能数据的分析方法:

1. 指标定义
   ├── P50/P90/P99分位值
   │   └── P90启动时间 < 2秒为优秀
   ├── 绝对值 vs 相对值
   │   └── 版本间对比使用相对值
   └── 采样率设置
       └── 高端机10% / 低端机50%

2. 归因分析
   ├── 版本维度:哪个版本引入了劣化
   ├── 机型维度:哪些机型问题严重
   ├── 场景维度:哪个页面/操作最慢
   └── 堆栈维度:哪个方法最耗时

3. 常见优化收益评估
   ├── 启动优化1秒 → 用户留存提升约5%
   ├── 页面加载优化500ms → 转化率提升约2%
   └── ANR率降低到0.1%以下 → 用户投诉大幅下降

4. 长期治理策略
   ├── 建立性能基线,每个版本对比
   ├── 性能劣化自动归因到责任人
   ├── 定期的性能专项治理
   └── 低端机专项适配(功能降级策略)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 14.4 性能优化常见面试题

问题:如何监控应用的帧率?

// 方法1:使用Choreographer回调
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    long lastFrameTimeNanos = 0;
    int frameCount = 0;
    
    @Override
    public void doFrame(long frameTimeNanos) {
        if (lastFrameTimeNanos == 0) {
            lastFrameTimeNanos = frameTimeNanos;
        }
        
        frameCount++;
        long diff = frameTimeNanos - lastFrameTimeNanos;
        
        if (diff >= 1_000_000_000) { // 每秒计算一次
            float fps = frameCount * 1_000_000_000f / diff;
            Log.d("FPS", "当前帧率: " + fps);
            frameCount = 0;
            lastFrameTimeNanos = frameTimeNanos;
        }
        
        // 注册下一帧回调
        Choreographer.getInstance().postFrameCallback(this);
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

问题:如何定位ANR?

ANR定位方法:

1. 查看traces.txt
   /data/anr/traces.txt
   包含ANR时所有线程的堆栈信息
   找到main线程的堆栈 → 定位卡在哪里

2. 查看EventLog
   adb logcat -b events | grep anr
   包含ANR的时间、进程、原因

3. Systrace/Perfetto分析
   查看ANR前后主线程的执行情况
   分析是什么操作阻塞了主线程

4. 线上ANR监控
   ├── FileObserver监听/data/anr/目录
   ├── ANR-WatchDog定时检查主线程响应
   └── 收集ANR时的堆栈和系统信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 十五、总结

性能优化知识图谱:

卡顿原理
├── 渲染管线 → VSync + Choreographer + RenderThread
├── 掉帧原因 → 主线程耗时/GC/IO/锁等待
└── ANR → Input 5s / Broadcast 10s / Service 20s

监控方案
├── Looper Printer → BlockCanary
├── Choreographer → 帧率监控
├── ANR-WatchDog → ANR预警
├── 字节码插桩 → 方法级耗时
└── Systrace/Perfetto → 系统级分析

优化实战
├── 启动优化 → 延迟/异步/按需初始化
├── 布局优化 → 层级/过度绘制/RecyclerView
├── IO优化 → SP替代/异步IO/数据库事务
├── 线程优化 → 统一线程池/协程
└── 内存优化 → 减少GC/避免抖动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

性能优化是一个系统性工程,需要从监控、定位、修复、验证四个环节形成闭环。理解底层原理是做好性能优化的基础。

上次更新: 2026/06/10, 11:13:41
线程与并发编程
序列化与数据存储

← 线程与并发编程 序列化与数据存储→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式