Android 性能优化全景图 —— 从入门到不踩坑

4 阅读9分钟

Android 性能优化全景图 —— 从入门到不踩坑

写在前面:性能优化不是玄学,是一门可以量化的手艺。本文适合初中高级开发者,从发现问题到解决问题,工具到手,干货管够。


一、为什么要性能优化?

用户说: "这App怎么这么卡?"、"手机烫得能煎蛋"、"刚打开就崩了"

老板说: "竞品比我们快,用户都跑了"

面试官说: "讲讲你做过的性能优化"

你说: "我...我代码写得挺规范的啊?"

😄 性能优化,是区分"能写代码"和"写好代码"的分水岭。下面我们进入正题。


二、发现问题篇 —— 性能不好,你得先知道

2.1 用户反馈的信号

现象可能原因关键词
卡顿、掉帧UI线程阻塞、过度绘制Choreographer、16ms
启动慢主线程初始化太多冷启动、懒加载
手机发热、耗电快后台任务过多、频繁网络请求Doze、JobScheduler
闪退、崩溃OOM、内存泄漏Memory Profiler、LeakCanary
包体积大资源冗余、未压缩ProGuard、资源压缩

2.2 监控手段

线上监控:

  • Crashlytics / Bugly —— 崩溃收集,必备
  • ANR监控 —— 捕获主线程卡死
  • 自定义性能埋点 —— 关键页面加载时间、接口耗时

线下调试:

  • Android Studio Profiler —— 官方全家桶
  • LeakCanary —— 内存泄漏自动检测
  • Perfetto —— 系统级性能分析

三、解决问题篇 —— 核心干货来了 🔥


3.1 内存优化

3.1.1 概念辨析
术语解释类比
内存泄漏对象不再使用,但无法被回收没关的水龙头,慢慢漏
内存溢出(OOM)申请内存超过可用空间水桶满了还往里倒
内存抖动频繁创建/销毁对象,GC压力山大心电图一样的内存曲线

关系: 内存泄漏 → 可用内存减少 → 最终可能OOM 💥

3.1.2 Memory Profiler 实战

打开方式: Android Studio → View → Tool Windows → Profiler

核心功能:

功能用途怎么看
Memory Chart实时内存曲线看锯齿状是否频繁(内存抖动)
Heap Dump堆内存快照分析对象数量、大小
Allocation Tracking对象分配追踪找到谁在疯狂new对象

实战步骤:

1. 启动App,打开Memory Profiler
2. 操作你怀疑有问题的功能模块
3. 点击"触发GC"(垃圾桶图标)
4. 观察:内存是否持续上涨不回落?
5. 如果是 → Dump heap,搜索可疑对象
6. 分析引用链:谁持有它不放?
3.1.3 LeakCanary 自动检测

集成方式:

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}

使用方式: 集成后自动生效,检测到泄漏会发通知!

通知示例:

LeakCanary
├─ Activity泄漏: MainActivity
├─ 泄漏大小: 1.2 MB
└─ 引用链: MainActivity → Handler → Runnable → ...

一句话: LeakCanary 是你的内存保镖,有问题它第一时间告诉你。

3.1.4 常见泄漏场景 & 修复
场景问题原因修复方案
Handler内存泄漏非静态内部类持有Activity引用改为静态内部类 + WeakReference
单例持有Context单例生命周期 > Activity使用Application Context
注册未取消EventBus/广播未unregisteronDestroy中取消注册
匿名内部类Runnable/Callback持有外部类引用改为静态类或弱引用
静态View/Activity静态变量一直持有避免静态持有,及时置空

Handler泄漏修复示例:

// ❌ 错误写法
private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 隐式持有外部Activity引用
        textView.setText("更新UI");
    }
};

// ✅ 正确写法
private static class SafeHandler extends Handler {
    private final WeakReference<MainActivity> activityRef;
    
    SafeHandler(MainActivity activity) {
        activityRef = new WeakReference<>(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        MainActivity activity = activityRef.get();
        if (activity != null && !activity.isFinishing()) {
            activity.textView.setText("更新UI");
        }
    }
}

3.2 启动速度优化

3.2.1 启动类型
类型定义用户体验
冷启动App进程不存在,从零开始最慢,白屏时间最长
温启动进程存在,Activity重建中等速度
热启动进程存在,Activity还在后台最快,瞬间恢复

优化重点: 冷启动,因为这是用户的第一印象。

3.2.2 优化策略

1. 延迟初始化(懒加载)

// ❌ 一股脑全在Application初始化
@Override
public void onCreate() {
    super.onCreate();
    initSDK1();
    initSDK2();
    initSDK3();
    initSDK4();
    initSDK5();
    // ... 用户已经等得不耐烦了
}

// ✅ 按需初始化
@Override
public void onCreate() {
    super.onCreate();
    // 只初始化必须的
    initMustSDK();
    
    // 其他的延迟到使用时再初始化
    // 或者放到子线程
    new Thread(() -> {
        initLazySDK();
    }).start();
}

2. 异步初始化 + 任务调度

使用 AppStartup 或自定义启动器,并行初始化多个SDK。

主线程: ────[核心任务]────[UI渲染]
                  ↓
子线程1: ──[SDK1]──[SDK2]────
子线程2: ──[SDK3]──────────

3. 优化Splash页面

  • 避免Splash页做耗时操作
  • 使用 windowBackground 提前展示主题色
  • 尽快跳转到主页面
3.2.3 启动时间测量

方式1:代码打点

// 在Application.onCreate开始
long startTime = System.currentTimeMillis();

// 在主页面onWindowFocusChanged
long endTime = System.currentTimeMillis();
Log.d("Startup", "启动耗时: " + (endTime - startTime) + "ms");

方式2:ADB 命令

adb shell am start -W 包名/主Activity
# 输出: ThisTime, TotalTime, WaitTime

方式3:标记完全绘制(API 19+)

// 在主页面数据加载完成后调用
ActivityCompat.reportFullyDrawn(this);
// 可在 Logcat 过滤 "FullyDrawn" 查看耗时

3.3 UI流畅度优化

3.3.1 16ms 定律

原理: 屏幕刷新率60fps → 每帧必须在 16.67ms 内完成绘制

超时后果: 掉帧 → 用户感觉卡顿

理想情况:
Frame1(16ms) → Frame2(16ms) → Frame3(16ms) → ...

卡顿情况:
Frame1(16ms) → Frame2(35ms!!!) → Frame3...
                    ↓
              掉帧了!用户看到了卡顿
3.3.2 常见卡顿原因
原因表现解决方案
主线程耗时操作IO、网络、复杂计算移到子线程
布局层级过深测量/布局耗时长扁平化布局、ConstraintLayout
过度绘制同一像素多次绘制减少背景、使用Hierarchy Viewer
频繁requestLayout触发重新测量布局批量更新、避免频繁调用
3.3.3 优化技巧

1. 布局优化

<!-- ❌ 嵌套太深 -->
<LinearLayout>
    <LinearLayout>
        <LinearLayout>
            <TextView />
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

<!-- ✅ 扁平化布局 -->
<ConstraintLayout>
    <TextView />
</ConstraintLayout>

2. 减少过度绘制

  • 开发者选项 → 调试GPU过度绘制
  • 颜色越深问题越严重:无色 < 蓝 < 绿 < 粉红 < 红色

优化方法:

  • 移除不必要的 background
  • 使用 clipToPaddingclipChildren
  • 自定义View时使用 canvas.clipRect()

3. 使用 Perfetto 分析(推荐)

方式1:Perfetto Web UI(推荐)

  • 访问 ui.perfetto.dev
  • 手机连接电脑,在开发者选项中开启"系统跟踪"
  • 录制trace后上传分析

方式2:命令行抓取

# 使用 adb 抓取
adb shell perfetto \
  --out /data/misc/perfetto-traces/trace \
  --time 5s \
  gfx view res

# 拉取到本地
adb pull /data/misc/perfetto-traces/trace

打开 HTML 或上传到 Perfetto UI,看哪些帧超时了(红色标记)。


3.4 电量优化

3.4.1 耗电大户
元凶场景建议
频繁网络请求轮询、心跳包批量请求、增量同步
GPS定位持续定位按需定位、降低精度
蓝牙扫描持续扫描附近设备按需扫描,设置超时
WakeLock阻止休眠及时release
后台服务常驻后台使用JobScheduler
3.4.2 优化方案

1. 使用 JobScheduler / WorkManager

// 系统会智能调度,批量执行后台任务
WorkManager workManager = WorkManager.getInstance(context);
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
    MyWorker.class,
    15, // 重复间隔
    TimeUnit.MINUTES
).build();
workManager.enqueue(workRequest);

2. 网络优化

  • 批量上传数据,减少请求次数
  • 使用 GZIP 压缩
  • 预加载 + 缓存

3. 监控电量消耗

adb shell dumpsys batterystats > battery.txt
# 分析各App耗电情况

3.5 包体积瘦身

3.5.1 瘦身策略
方案效果实施难度
ProGuard/R8 混淆去除无用代码、混淆命名
资源压缩移除无用资源
WebP格式图片图片体积减少50%+⭐⭐
动态下发非必须资源动态下载⭐⭐⭐
So动态加载按需加载native库⭐⭐⭐⭐
3.5.2 配置示例
android {
    buildTypes {
        release {
            // 开启混淆
            minifyEnabled true
            // 开启资源压缩
            shrinkResources true
            // 开启R8
            useProguard false
        }
    }
}
3.5.3 检查包体积
# 分析APK结构
./gradlew assembleRelease
# Android Studio → Build → Analyze APK

查看各模块占比:代码 / 资源 / So库,针对性优化。


3.6 网络优化

3.6.1 优化策略
策略说明收益
缓存OkHttp缓存、数据库缓存减少重复请求
请求合并多个接口合并成一个减少RTT
压缩GZIP / Protobuf减少传输量
预加载提前请求可能用到的数据用户体验提升
弱网适配超时重试、离线模式网络差时也能用
3.6.2 OkHttp 配置示例
OkHttpClient client = new OkHttpClient.Builder()
    // 连接超时
    .connectTimeout(15, TimeUnit.SECONDS)
    // 读取超时
    .readTimeout(20, TimeUnit.SECONDS)
    // 缓存
    .cache(new Cache(cacheDir, 10 * 1024 * 1024)) // 10MB缓存
    // GZIP(默认开启)
    .build();

四、工具箱速查表 📦

工具用途使用场景
CPU Profiler方法耗时分析找热点方法、优化性能瓶颈
Memory Profiler内存分析内存泄漏、内存抖动检测
Heap Dump堆内存快照分析对象分布、GC Root
LeakCanary自动泄漏检测开发阶段自动发现泄漏
Perfetto系统级性能分析UI卡顿、线程调度分析
Layout Inspector布局层级查看优化布局深度
GPU Overdraw过度绘制检测减少不必要的绘制
APK Analyzer包体积分析瘦身优化

五、面试高频题速记 🎯

Q1: 什么是内存泄漏?如何检测和避免?

内存泄漏:对象不再使用但无法被回收。 检测:Memory Profiler + LeakCanary 避免:注意Handler、单例、注册/反注册、静态变量等场景,使用弱引用。

Q2: 如何优化App启动速度?

  1. 延迟/懒加载非必须初始化
  2. 异步初始化 + 任务调度(AppStartup)
  3. 优化Splash页面
  4. 避免主线程IO/网络操作

Q3: UI卡顿怎么排查?

  1. 16ms定律,确认是否掉帧
  2. Perfetto分析哪一帧超时
  3. 检查主线程是否有耗时操作
  4. 检查布局层级、过度绘制
  5. 使用Choreographer监控帧率

Q4: 如何进行电量优化?

  1. 减少频繁网络请求,使用批量+缓存
  2. 避免长时间WakeLock
  3. 使用JobScheduler/WorkManager调度后台任务
  4. 按需使用定位,降低精度

Q5: 包体积瘦身有哪些方法?

  1. ProGuard/R8混淆
  2. shrinkResources资源压缩
  3. WebP图片
  4. 动态下发资源
  5. So动态加载

六、总结

性能优化是一门"度量"的艺术:

  1. 先发现问题 —— 监控 + 工具
  2. 定位原因 —— 分析 + 排查
  3. 制定方案 —— 策略 + 优先级
  4. 验证效果 —— 数据对比
  5. 持续迭代 —— 性能无止境

记住:没有度量的优化都是耍流氓 🔧


🐾 作者:不倒翁

📅 更新时间:2026-03-26

如果这篇文章对你有帮助,点个赞呗~ 你的支持是我持续输出的动力!


参考资料: