性能优化(下):启动优化、网络优化、电量优化与渲染优化

好,咱们接着聊性能优化。上一章我把内存和布局优化讲得差不多了,这一章咱们聚焦在另外四个关键点上:启动、网络、电量和渲染。说实话,这几个点在实际项目中踩坑的概率非常高。我当年刚带项目时,就因为在启动优化上偷了懒,结果被用户骂“打开慢得像老年机”。嗯,从那以后,我再也不敢小看任何一项优化了。

一、启动优化:冷启动 vs 热启动

先搞清楚概念。冷启动,指的是进程从零开始创建,Application 和 Activity 都要重新走一遍生命周期。热启动呢?Activity 被回收了但进程还在,只需要重新创建 Activity 就行。说白了,冷启动是“从头再来”,热启动是“半路接手”。

我个人习惯把冷启动的耗时拆成三个阶段:

  • Application 初始化:onCreate 里干了多少脏活累活?
  • Activity 创建:onCreate、onStart、onResume 里有没有阻塞主线程?
  • 首帧渲染:第一帧画面什么时候交给屏幕?

你想想看,很多团队一上来就往 Application 里塞 SDK 初始化,什么推送、统计、地图、IM……全堆在一起。结果呢?冷启动直接奔着两三秒去了。我建议的做法是:能懒加载就懒加载,能异步就异步

核心原则:Application.onCreate 里只做最核心的初始化(如崩溃收集、性能监控),其余全部推迟到使用时再初始化。

举个例子,我之前接手过一个项目,Application 里初始化了 12 个 SDK。我改成按需加载后,冷启动时间从 2.8 秒降到了 1.1 秒。怎么做的?用 IdleHandler 或者 WorkManager 延迟执行。

// 利用 IdleHandler 在主线程空闲时执行非关键初始化
Looper.myQueue().addIdleHandler(() -> {
    // 初始化非关键 SDK
    initNonCriticalSDKs();
    return false; // 只执行一次
});

小技巧:启动页不要用纯白或纯黑背景,用一张和 App 主题色一致的启动图(windowBackground),用户感知上会觉得“秒开”。

二、网络优化:缓存与压缩

网络优化,说白了就是两件事:少发请求少传数据

先聊缓存。我见过不少团队,每次打开页面都重新请求接口,哪怕数据根本没变。这其实很浪费流量和电量。我个人习惯用 OkHttp 的缓存策略,配合服务端返回的 Cache-Control 头,能省掉大量重复请求。

// OkHttp 缓存配置示例
int cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDir, cacheSize);

OkHttpClient client = new OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor(new CacheInterceptor()) // 自定义缓存拦截器
    .build();

再聊压缩。图片是流量大户。我建议所有图片都走 WebP 格式,体积比 PNG 小 30% 以上。接口返回的 JSON 数据,可以开启 Gzip 压缩。OkHttp 默认支持 Gzip,但记得确认服务端也开了。

注意:缓存不是万能的。对于实时性要求高的数据(如订单状态、聊天消息),必须走实时请求,不能依赖缓存。我曾经因为缓存策略没配好,导致用户看到的是半小时前的订单状态,差点被投诉到应用商店下架。

三、电量优化:JobScheduler 与 WorkManager

电量优化,很多开发者容易忽略。但你想,用户手机电量就那么多,你的 App 在后台偷偷跑网络、频繁唤醒 CPU,用户不卸载你卸载谁?

Android 官方推荐用 WorkManager 来管理后台任务。它内部会根据系统版本自动选择 JobScheduler(API 21+)或 AlarmManager,而且能自动处理 Doze 模式和 App Standby。

// WorkManager 示例:延迟执行后台同步任务
OneTimeWorkRequest syncWork = new OneTimeWorkRequest.Builder(SyncWorker.class)
    .setInitialDelay(10, TimeUnit.MINUTES)
    .setConstraints(new Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .setRequiresBatteryNotLow(true)
        .build())
    .build();

WorkManager.getInstance(context).enqueue(syncWork);

我个人习惯把非紧急的后台任务(如日志上传、数据预加载)都交给 WorkManager。它会在合适的时机(比如充电时、WiFi 下)才执行,对电量非常友好。

避坑指南:我曾经在项目里直接用 AlarmManager 做定时任务,结果在 Android 8.0 以上频繁被系统限制。后来全部迁移到 WorkManager,问题迎刃而解。记住:能用 WorkManager 就别自己造轮子

四、渲染优化:Profile GPU 工具

渲染优化,核心目标是保证 60fps,也就是每帧 16.6ms 内完成绘制。如果超了,用户就会感觉到卡顿。

Android 提供了 Profile GPU Rendering 工具,可以在开发者选项里打开。它会用柱状图显示每帧的绘制耗时,绿色代表正常,红色代表超时。

我一般这样分析:

  • 蓝色柱太高:说明 View 的 onDraw 里做了太多绘制操作,比如重复绘制、复杂 Path。
  • 红色柱太高:说明 GPU 处理时间过长,通常是过度绘制(Overdraw)导致的。
  • 黄色柱太高:说明 CPU 在等待 GPU 完成上一帧,可能是布局层级太深。

你想想看,如果一帧里红色柱占了 20ms,那这帧肯定掉帧了。我遇到过一个案例,一个列表页的 item 布局嵌套了 7 层,每层都有背景色,结果过度绘制严重。我把它压到 3 层,去掉不必要的背景,帧率直接从 40fps 升到了 58fps。

小技巧:GPU Overdraw 工具(开发者选项里也有)查看过度绘制区域。蓝色最好,红色要警惕。我一般要求团队把红色区域控制在屏幕面积的 10% 以内。

五、Systrace 工具使用

Systrace 是性能优化的“核武器”。它能记录系统级和 App 级的耗时事件,精确到微秒级别。我个人习惯在排查卡顿、启动慢、ANR 问题时,第一个想到的就是 Systrace。

使用方法很简单:

  1. 打开开发者选项,开启 System Tracing
  2. 复现问题场景,然后停止录制。
  3. 用 Chrome 浏览器打开生成的 .trace 文件。

在 Systrace 里,你会看到一条条彩色长条,每个长条代表一个线程或进程的活动。重点关注:

  • 主线程(UI Thread):有没有长时间占用?有没有被 Binder 调用阻塞?
  • SurfaceFlinger:帧率是否稳定?有没有掉帧?
  • CPU 频率:有没有降频?降频会导致性能下降。

我记得有一次,用户反馈某个页面滑动卡顿。我用 Systrace 一抓,发现主线程每隔 2 秒就执行一次 200ms 的磁盘写入操作。原来是某个第三方 SDK 在后台频繁写日志。我找到 SDK 厂商,让他们改成异步写入,卡顿就消失了。

注意:Systrace 在 Android 10 以上已经被 Perfetto 取代,但原理是一样的。如果你用的是新设备,建议直接学 Perfetto。不过老项目还是 Systrace 更通用。

知识体系总览

下面这张图,我把本章的核心知识点串起来了。你可以把它当作一个检查清单,做优化时对照着看。

性能优化(下) 启动优化 冷启动 / 热启动 Application 瘦身 IdleHandler 延迟加载 网络优化 OkHttp 缓存策略 WebP / Gzip 压缩 减少请求次数 电量优化 WorkManager 后台任务 JobScheduler 调度 Doze 模式适配 渲染优化 Profile GPU 分析 过度绘制检测 16.6ms 帧率保障 核心工具:Systrace 优化原则总结 • 启动:减少主线程阻塞,延迟非关键初始化 • 网络:能缓存就缓存,能压缩就压缩 • 电量:用 WorkManager 替代自建后台任务 • 渲染:用 Profile GPU 和 Systrace 定位瓶颈

好了,这一章的内容就到这里。性能优化没有银弹,关键是要养成用工具说话的习惯。下次遇到卡顿、耗电、启动慢,别靠猜,先上 Systrace 和 Profile GPU 看看数据。数据会告诉你问题在哪。


公众号:蓝海资料掘金营,微信deep3321