21、应用层省电实践:减少网络请求、合并任务、使用缓存、避免频繁唤醒
各位同学,今天我们来聊聊应用层省电的四个核心招式。说实话,很多App耗电快,并不是底层驱动写得烂,而是应用层自己“作”出来的。我见过太多App,明明用户已经锁屏了,还在后台疯狂拉数据、反复唤醒CPU,电池能撑住才怪。
我个人习惯把应用层省电总结为四个关键词:少请求、合并做、用缓存、别乱醒。下面我们一个一个拆开讲。
21.1 减少网络请求:能不发就不发
网络请求是耗电大户。每次发起一次HTTP请求,手机都要经历:唤醒天线 -> 建立连接 -> 传输数据 -> 保持连接 -> 超时关闭。这个过程,少则几百毫秒,多则几秒,天线一直处于高功耗状态。
核心原则:能合并的请求不要分开发,能延迟的请求不要立即发,能取消的请求一定要取消。
避坑指南:我曾经接手过一个新闻客户端,每次用户滑动列表,都会触发10多个独立的网络请求去拉图片、拉评论数、拉点赞状态。后来我改成批量接口,一次请求带回所有数据,电量消耗直接降了40%。
具体做法:
- 使用批量接口,替代多个单点请求
- 设置合理的超时时间,避免长时间等待
- 在Activity/Fragment销毁时,主动取消未完成的请求
- 利用
WorkManager或JobScheduler延迟非紧急请求
// 不好的做法:每个数据单独请求
api.fetchUserInfo(userId);
api.fetchUserPosts(userId);
api.fetchUserFriends(userId);
// 好的做法:合并成一个批量请求
api.fetchUserProfile(userId, new String[]{"info", "posts", "friends"});
21.2 合并任务:把零碎活儿攒起来干
你想想看,CPU和网络模块都有一个特点:启动瞬间功耗极高,但持续运行时平均功耗反而低。就像开车一样,频繁起步刹车最费油,匀速巡航反而省油。
所以,我们要把零碎的小任务合并成一个大任务,集中处理。这就是所谓的“任务合并”策略。
| 场景 | 错误做法 | 正确做法 |
|---|---|---|
| 日志上传 | 每产生一条日志就上传一次 | 攒够10条或每隔5分钟批量上传 |
| 位置上报 | 每秒上报一次GPS坐标 | 每30秒上报一次,或只在位置变化超过100米时上报 |
| 数据同步 | 每个模块各自同步 | 统一由SyncAdapter管理,合并同步周期 |
我的经验:在做一个运动App时,我们原本把步数、心率、GPS轨迹分别用三个定时器上传。后来改成统一用一个定时器,每5分钟检查一次所有待上传数据,打包发送。电池续航从8小时提升到了12小时。
21.3 使用缓存:内存换电量,划算
缓存,说白了就是用存储空间换网络请求。你想想看,从内存里读数据,功耗几乎可以忽略不计。但从网络拉数据,功耗是前者的几百倍。
所以,能缓存的数据,坚决不要重复请求。
常见的缓存策略:
- 内存缓存:使用
LruCache,适合图片、JSON等频繁访问的数据 - 磁盘缓存:使用
DiskLruCache或Room数据库,适合需要持久化的数据 - 网络缓存:利用HTTP缓存头(Cache-Control、ETag),减少304响应
// 使用LruCache做内存缓存
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> memoryCache = new LruCache<>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
// 读取时先查缓存
Bitmap bitmap = memoryCache.get(key);
if (bitmap == null) {
bitmap = loadFromNetwork(key);
memoryCache.put(key, bitmap);
}
注意:缓存不是越多越好。缓存太大,会导致内存紧张,反而触发GC,增加CPU开销。我建议内存缓存控制在可用内存的1/8以内,磁盘缓存控制在10MB以内。
21.4 避免频繁唤醒:让手机多睡会儿
这是最容易被忽视的一点。很多开发者为了“实时性”,动不动就申请WakeLock、设置AlarmManager、使用Handler循环。结果手机刚想睡,就被你叫醒了。
为什么会这样?因为Android的深度睡眠(Doze模式)下,CPU和网络都处于极低功耗状态。一旦被唤醒,所有模块都要重新上电,这个过程本身就很耗电。
核心原则:能不唤醒就不唤醒,必须唤醒就尽量集中唤醒。
具体做法:
- 使用
WorkManager替代AlarmManager,系统会帮你合并唤醒时机 - 避免使用
WakeLock,除非万不得已(比如播放音乐、下载大文件) - 使用
JobScheduler设置setPeriodic()时,加上setFlex()弹性窗口 - 利用
PowerManager.isDeviceIdleMode()判断是否处于Doze模式,主动降低频率
// 不好的做法:每15分钟唤醒一次
AlarmManager alarm = (AlarmManager) getSystemService(ALARM_SERVICE);
alarm.setRepeating(AlarmManager.RTC_WAKEUP, triggerAt, 15 * 60 * 1000, pendingIntent);
// 好的做法:使用WorkManager,让系统决定何时执行
PeriodicWorkRequest workRequest =
new PeriodicWorkRequest.Builder(MyWorker.class, 15, TimeUnit.MINUTES)
.setFlexInterval(5, TimeUnit.MINUTES) // 允许5分钟的弹性窗口
.build();
WorkManager.getInstance(this).enqueue(workRequest);
21.5 知识体系总览
下面这张图,是我自己整理的“应用层省电四板斧”结构图。你可以把它当作一个检查清单,每次写代码时对照一下。
21.6 写在最后
嗯,这四个招式其实并不复杂,但真正在项目中坚持做到位的人不多。我见过太多App,功能做得很花哨,但一装到手机上,半天就没电了。用户不会关心你用了什么架构、什么设计模式,他们只知道——这个App太费电,删了。
所以,从今天开始,写每一行代码前,先问自己三个问题:
- 这个请求真的必须现在发吗?
- 这个任务能不能跟其他任务一起做?
- 这个数据能不能从缓存里拿?
养成这个习惯,你的App不仅省电,用户体验也会好很多。