一、CPU 优化的核心原理
从 CPU 角度看,程序执行速度由三个因子决定:
程序执行时间 = 指令数 × 每条指令的平均时钟周期(CPI) × 时钟周期时间
优化任何一个因子都能提升速度:
| 优化方向 | 具体手段 |
|---|---|
| 减少指令数 | 更优算法、更简洁代码、多线程并发、预加载减少运行时指令 |
| 降低 CPI | 减少 IO 等待、优化缓存命中率、选择更高效的编程语言/编译器 |
| 降低时钟周期 | 避免 CPU 降频(控制发热)、利用高性能核心 |
关键认知 :当手机发热时,CPU 会通过降频来减少发热。因此,避免长时间高负荷使用 CPU,不仅是优化性能,也是防止性能倒退的重要手段。
二、CPU 性能分析工具
1. Perfetto / System Trace
Perfetto 是 Systrace 的继任者,是 Android 10+ 的系统级追踪工具,能查看 Frame Timeline、CPU 调度、Binder、I/O 等全链路信息。
使用方式:
bash
# 抓取系统 trace
adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 10s \
sched freq idle gfx view wm am ss
分析要点:
- 查看
Frames行,红色 F 表示丢帧 - 分析主线程和 RenderThread 的耗时分布
- 查看 CPU 核心调度情况
2. Android Studio CPU Profiler
支持三种模式:
| 模式 | 适用场景 | 性能开销 |
|---|---|---|
| System Trace | 系统级分析,查看线程调度、渲染流程 | 低 |
| Java Method Trace | 精确分析 Java 方法耗时 | 高(5-10倍) |
| C/C++ Function Trace | Native 代码分析 | 中 |
分析视图:
- Call Chart:查看调用关系,系统 API(黄)、应用代码(绿)、第三方(蓝)
- Flame Chart:聚合多次调用,识别热点函数
- Top Down:从顶层方法逐层展开,看耗时分布
- Bottom Up:从底层方法向上追溯调用方
优化时重点关注
Incl Cpu Time(含子函数总耗时)和Calls+Recur(调用次数)。
3. TraceView(埋点分析)
通过代码埋点精确追踪特定方法:
java
Debug.startMethodTracing("myapp");
// 需要分析的代码
Debug.stopMethodTracing();
生成的 .trace 文件可用 Android Studio 打开。适合精确分析特定函数,但运行时开销很大。
4. 芯片级工具(深度优化)
- Snapdragon Profiler:高通官方工具,支持 150+ 硬件性能计数器,实时监控 CPU/GPU/DSP
- ARM Performance Studio:含 Streamline(系统级 CPU/GPU 分析)、Frame Advisor 等
三、CPU 优化具体手段
1. 线程池优化
合理配置线程池是 CPU 优化的基础:
java
// CPU 密集型任务:线程数 = CPU核心数 + 1
int cpuCount = Runtime.getRuntime().availableProcessors();
ExecutorService cpuPool = Executors.newFixedThreadPool(cpuCount + 1);
// IO 密集型任务:线程数可以更多
ExecutorService ioPool = Executors.newFixedThreadPool(cpuCount * 2 + 1);
最佳实践:
- 合并网络库和图片库的线程池,避免创建过多线程
- 禁止直接使用
new Thread(),统一通过线程工厂管理 - 配合 Lint 检查线程使用规范
2. 减少 CPU 闲置 --- 预加载策略
大部分应用 CPU 不会持续高负载,利用闲置时间预加载数据/View,可减少核心场景的指令数。
检测 CPU 闲置的推荐方案 (Native 的 times 函数):
cpp
#include <sys/times.h>
#include <unistd.h>
bool isCpuIdle() {
struct tms buf;
clock_t utime, stime;
static clock_t lastUtime = 0, lastStime = 0;
times(&buf);
utime = buf.tms_utime;
stime = buf.tms_stime;
long ticksPerSec = sysconf(_SC_CLK_TCK);
double userUsage = (double)(utime - lastUtime) / ticksPerSec;
double sysUsage = (double)(stime - lastStime) / ticksPerSec;
lastUtime = utime;
lastStime = stime;
return (userUsage + sysUsage) < THRESHOLD; // 如 5%
}
注意:Android 8.0+ 不再支持读取
/proc/stat,因此推荐使用times函数方案。
3. 减少 CPU 等待 --- IO 任务分离
IO 操作由 DMA 执行,CPU 会等待或切换。将 IO 从主线程分离:
优化前:主线程 → IO读取 → 数据处理 → UI更新
优化后:主线程 → 默认数据展示 → 异步IO → IO完成 → UI更新
核心原则:将主流程拆得足够细,先执行不依赖 IO 的处理,数据到达后再刷新。
4. 锁优化
锁竞争会导致线程阻塞和 CPU 空转:
| 优化策略 | 说明 |
|---|---|
| 缩小锁粒度 | 只锁必要代码块,不要锁整个方法 |
| 减少锁持有时间 | 锁内不做耗时操作 |
| 使用读写锁 | 读多写少场景用 ReentrantReadWriteLock |
| 无锁数据结构 | 如 ConcurrentHashMap、AtomicInteger |
| 避免锁嵌套 | 防止死锁和优先级反转 |
5. 算法与数据结构优化
- 选择时间复杂度更低的算法
- 避免在循环中创建对象(减少 GC 压力)
- 使用
SparseArray替代HashMap<Integer, Object> - 字符串拼接用
StringBuilder,避免String.format(耗时高)
6. 减少 GC 压力
频繁 GC 会抢占 CPU 资源:
- 对象池复用(如
Message.obtain()) - 避免内存泄漏(使用 LeakCanary 检测)
- 大对象使用
BitmapFactory.Options进行采样加载 - 使用
ArrayMap/SparseArray替代HashMap
四、任务调度优化
1. 提升核心线程优先级
通过 Process.setThreadPriority() 调整线程优先级(范围 -20 到 19,-20 最高):
java
// 提升主线程优先级
Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_URGENT_DISPLAY);
// 提升渲染线程优先级
int renderTid = getRenderThreadTid(); // 遍历 /proc/pid/task 获取
if (renderTid != -1) {
Process.setThreadPriority(renderTid, -19);
}
原则:提高核心线程优先级,降低非核心线程优先级,两者配合才能高效提升速度。
2. 核心线程绑定 CPU 大核
现代手机 CPU 采用大小核架构,将关键线程绑定到大核可显著提升性能:
java
// 获取最大频率的 CPU 核心
private int getMaxFreqCPUIndex() {
int cores = Runtime.getRuntime().availableProcessors();
int maxFreq = -1, maxIndex = 0;
for (int i = 0; i < cores; i++) {
File file = new File("/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq");
// 读取频率,找出最大值
}
return maxIndex;
}
cpp
// Native 层绑定线程到指定核心
#include <sched.h>
extern "C" JNIEXPORT jint JNICALL
Java_bindMaxFreqCore(JNIEnv *env, jobject, jint cpuIndex, jint tid) {
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(cpuIndex, &mask);
return sched_setaffinity(tid, sizeof(mask), &mask); // 0 表示成功
}
适用场景:
- 渲染线程绑定大核,提升帧率稳定性
- 游戏主线程绑定大核,减少卡顿
- 启动阶段关键线程绑定大核,加速启动
3. Input 链路的系统级优化
对于极致流畅度要求,可在系统层面优化:
- irq 绑定大核:将输入中断处理绑定到高性能核心
- InputDispatcher 优先派发:对焦点应用优先派发事件
- 触屏固件提频:在触摸事件发生时临时提升 CPU 频率
- 不等 VSync 直接触发 :对于点击事件,直接触发
doFrame而非等待下一个 VSync
五、编译期优化
1. R8 Full Mode(AGP 8.0+ 默认启用)
R8 作为 ProGuard 的继任者,在 Full Mode 下可进行更深度的优化:
- 值假设优化
- 空值传播
- 死代码消除
- 内联优化
配置:
groovy
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
}
}
}
2. Baseline Profiles
通过提供预编译的 Profile,让 ART 在应用安装时就完成 AOT 编译,减少运行时 JIT 开销:
kotlin
// 使用 Macrobenchmark 生成 Baseline Profile
baselineProfile {
managedDevice = "pixel6Api31"
// 运行关键用户场景
}
效果 :启动时间可缩短 20%-60%。
3. 启动 Profile(Startup Profiles)
针对应用启动路径的专项优化,将启动链路中的方法提前编译为机器码。
六、度量与监控
1. 本地度量 --- Macrobenchmark
kotlin
@LargeTest
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startup() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric(), FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
}
}
2. 线上监控 --- Android Vitals
关注 Google Play 控制台中的核心指标:
- ANR 率:主线程阻塞超过 5 秒
- Slow frames:渲染超过 16ms 的帧占比
- Frozen frames:渲染超过 700ms 的帧
- Excessive wakeups:过度唤醒次数
3. 自定义 CPU 监控
java
// 读取 /proc/pid/stat 计算进程 CPU 使用率
private float getProcessCpuUsage() {
// 读取进程 utime + stime
// 读取 /proc/stat 获取系统总时间
// 计算 (进程时间差 / 系统时间差) * 100%
}
七、CPU 优化 Checklist
| 优先级 | 优化项 | 预期收益 |
|---|---|---|
| P0 | 启用 R8 Full Mode + Baseline Profiles | 启动提速 20%-60%,ANR 减少 |
| P0 | 规范线程使用,统一线程池管理 | 减少线程竞争和上下文切换 |
| P1 | 主线程 IO 分离 + 异步初始化 | 减少主线程阻塞,提升流畅度 |
| P1 | 锁粒度优化 + 无锁数据结构 | 减少线程等待,提升并发效率 |
| P1 | 核心线程(渲染/主线程)绑定大核 | 提升关键路径执行速度 |
| P2 | CPU 闲置时预加载 | 提升页面切换/二次启动速度 |
| P2 | 算法优化 + 对象池复用 | 减少指令数和 GC 压力 |
总结
Android CPU 优化的核心思路可以概括为:
- 先观测:用 Perfetto / CPU Profiler 定位瓶颈,找到热点函数和调度问题
- 再治理:从线程管理、锁优化、IO 分离、算法优化等层面减少 CPU 负担
- 深调度:通过优先级调整和绑核,让关键任务跑在最佳资源上
- 前置化:利用 R8、Baseline Profiles 在编译期就获得性能收益
- 防退化:用 Macrobenchmark 做 CI 回归,用 Android Vitals 监控线上质量
CPU 优化不是一次性工作,而是需要融入日常开发流程的持续实践。