20、性能优化:启动优化、内存与CPU、渲染性能
车载系统最怕什么?
我做了这么多年,最怕的就是用户坐进车里,中控屏还在转圈圈。你想想看,车都挂D挡了,导航还没出来,这体验得多糟糕。
性能优化,说白了就是跟时间赛跑。今天咱们聊聊启动优化、内存与CPU、还有渲染性能这三块硬骨头。
核心观点:车载Android的性能优化,不是单纯的代码级优化,而是系统级的资源调度艺术。启动速度、内存占用、渲染流畅度,三者互相影响,必须统筹考虑。
启动优化:从按下电源到看到主界面
启动优化是我个人最看重的一块。为什么?因为这是用户对系统的第一印象。
Boot Animation 的优化
Boot Animation 说白了就是开机动画。很多人觉得这玩意儿就是个面子工程,其实不然。它直接影响用户感知到的启动时间。
我记得有一次,客户抱怨某款车型开机要15秒。我一看,Boot Animation 用了1080P的MP4视频,解码就占了3秒。后来换成纯PNG序列帧,压缩到720P,启动时间直接砍掉2秒。
优化要点:
- 降低分辨率:车载屏幕分辨率高,但Boot Animation没必要用全分辨率。720P足够。
- 减少帧数:15fps就够了,30fps纯属浪费。
- 使用硬件加速:SurfaceFlinger支持硬件合成,别用软件渲染。
- 提前结束:SystemServer启动完成后,立即结束动画,别等它播完。
小技巧:我曾经在某个项目里,把Boot Animation的最后一帧做成静态图,然后提前结束动画,直接显示静态图。用户感觉启动变快了,其实只是心理作用,但体验确实好了。
SystemServer 的启动优化
SystemServer是Android系统的核心。它启动慢了,整个系统都得等着。
我见过最夸张的情况,SystemServer里有个第三方服务,启动时去连云端服务器,超时30秒。整个系统卡在那,Boot Animation转了一圈又一圈。
优化策略:
- 并行化启动:把不依赖的服务放到后台线程启动。比如蓝牙、WiFi这些,没必要在主线程里等。
- 懒加载:有些服务不是一启动就要用的。比如打印服务、NFC服务,完全可以等用户用到时再初始化。
- 减少IO操作:SystemServer启动时,别去读大文件。我习惯把配置文件做成二进制格式,解析速度快很多。
- 预加载类:把常用的Java类提前加载到内存,避免运行时ClassLoader加载。
// 示例:SystemServer中并行启动服务的简化代码
private void startBootstrapServices() {
// 核心服务必须串行
Installer installer = new Installer();
installer.start();
// 非核心服务可以并行
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> startBluetoothService());
executor.submit(() -> startWifiService());
executor.submit(() -> startLocationService());
executor.shutdown();
}
注意:并行启动不是越多线程越好。车载系统资源有限,线程太多反而导致CPU争抢。我一般控制在4-6个线程。
内存与CPU优化
车载系统的硬件资源,说白了就是「省着用」。不像手机可以随便换,车机一用就是好几年,内存泄漏积累下来,系统会越来越卡。
内存优化
我遇到过最头疼的问题,是某个地图应用内存泄漏。每次导航完,内存涨个50MB,跑几次就OOM了。
排查方法:
- 使用dumpsys meminfo:看每个进程的内存占用。
- 抓取hprof文件:用MAT分析泄漏点。
- 监控Native内存:车载系统很多用C++写的,Java堆没问题不代表Native没问题。
优化手段:
- 减少Bitmap内存:车载UI很多用大图,一张1920x720的图就是5MB。我建议用WebP格式,体积小一半。
- 使用对象池:频繁创建的对象,比如消息体、事件对象,用池子复用。
- 及时释放资源:Surface、MediaCodec这些,用完必须release。
// 对象池示例
public class MessagePool {
private static final int POOL_SIZE = 50;
private final Queue<Message> pool = new LinkedList<>();
public Message obtain() {
Message msg = pool.poll();
return msg != null ? msg : new Message();
}
public void recycle(Message msg) {
if (pool.size() < POOL_SIZE) {
pool.offer(msg);
}
}
}
CPU优化
CPU优化,说白了就是「别让CPU空转,也别让它忙死」。
我见过有些开发者在车载系统里用轮询,每隔100ms查一次状态。这简直是CPU杀手。正确的做法是用回调或者事件驱动。
优化要点:
- 降低CPU频率:系统空闲时,主动降频。车载系统不像手机需要瞬时爆发,稳定更重要。
- 绑定CPU核心:关键线程绑定到大核,后台线程绑定到小核。
- 减少唤醒:AlarmManager别设太频繁,能合并就合并。
避坑指南:我曾经在某个项目里,把所有的后台任务都放到了同一个线程池。结果一个任务卡住了,整个后台都停了。后来我改成按优先级分线程池,关键任务单独一个池,普通任务一个池。
渲染性能
渲染性能,说白了就是「画面流畅不流畅」。车载系统对帧率要求很高,60fps是底线,低于30fps用户就能感觉到卡顿。
SurfaceFlinger 优化
SurfaceFlinger是Android的合成器。所有应用的画面都要经过它合成后输出到屏幕。
优化方向:
- 减少Layer数量:每多一个Layer,SurfaceFlinger就要多一次合成。我建议把静态UI元素合并到一个Layer。
- 使用硬件合成:HWC(Hardware Composer)比GPU合成快得多。确保你的UI能用HWC。
- 避免Overdraw:一个像素被画了多次,就是Overdraw。用开发者选项里的「显示过度绘制」来排查。
GPU 合成优化
有些场景下,HWC不支持,只能走GPU合成。这时候就要注意了。
我遇到过一个问题:导航地图的3D渲染,每帧都要重新合成,GPU负载直接飙到80%。后来我把地图的静态部分缓存成纹理,只更新动态部分,GPU负载降到30%。
优化技巧:
- 减少纹理上传:纹理上传到GPU很慢。尽量复用纹理。
- 使用VBO:顶点数据用VBO缓存,别每帧都传。
- 降低分辨率:有些场景不需要全分辨率渲染,比如倒车影像,720P就够了。
帧率监控
没有监控,就没有优化。我习惯在系统里内置帧率监控工具。
// 帧率监控的简单实现
public class FpsMonitor {
private long lastFrameTime = 0;
private int frameCount = 0;
private float fps = 0;
public void onFrame() {
frameCount++;
long now = System.nanoTime();
if (now - lastFrameTime > 1_000_000_000L) {
fps = frameCount;
frameCount = 0;
lastFrameTime = now;
Log.d("FpsMonitor", "Current FPS: " + fps);
}
}
}
个人经验:我习惯在车载系统的SystemUI里加一个FPS显示开关。开发阶段打开,方便调试。发布时关掉,不影响用户体验。
总结
性能优化这件事,没有银弹。启动优化、内存与CPU、渲染性能,这三块是互相影响的。内存占用高了,GC频繁,CPU就忙,渲染就卡。CPU负载高了,启动就慢。
我个人的习惯是:先做启动优化,因为这是用户第一印象。然后做内存优化,因为内存泄漏是慢性病。最后做渲染优化,因为画面流畅度是用户持续体验的关键。
嗯,今天就聊到这。记住一句话:性能优化不是一次性的工作,而是贯穿整个开发周期的习惯。