21、应用层省电实践:减少网络请求、合并任务、使用缓存、避免频繁唤醒

各位同学,今天我们来聊聊应用层省电的四个核心招式。说实话,很多App耗电快,并不是底层驱动写得烂,而是应用层自己“作”出来的。我见过太多App,明明用户已经锁屏了,还在后台疯狂拉数据、反复唤醒CPU,电池能撑住才怪。

我个人习惯把应用层省电总结为四个关键词:少请求、合并做、用缓存、别乱醒。下面我们一个一个拆开讲。

21.1 减少网络请求:能不发就不发

网络请求是耗电大户。每次发起一次HTTP请求,手机都要经历:唤醒天线 -> 建立连接 -> 传输数据 -> 保持连接 -> 超时关闭。这个过程,少则几百毫秒,多则几秒,天线一直处于高功耗状态。

核心原则:能合并的请求不要分开发,能延迟的请求不要立即发,能取消的请求一定要取消。

避坑指南:我曾经接手过一个新闻客户端,每次用户滑动列表,都会触发10多个独立的网络请求去拉图片、拉评论数、拉点赞状态。后来我改成批量接口,一次请求带回所有数据,电量消耗直接降了40%。

具体做法:

  • 使用批量接口,替代多个单点请求
  • 设置合理的超时时间,避免长时间等待
  • 在Activity/Fragment销毁时,主动取消未完成的请求
  • 利用WorkManagerJobScheduler延迟非紧急请求
// 不好的做法:每个数据单独请求
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 知识体系总览

下面这张图,是我自己整理的“应用层省电四板斧”结构图。你可以把它当作一个检查清单,每次写代码时对照一下。

应用层省电实践 · 四板斧 省电核心策略 减少网络请求 批量接口 · 延迟非紧急 合并任务 攒批处理 · 统一周期 使用缓存 内存缓存 · 磁盘缓存 避免频繁唤醒 WorkManager · 弹性窗口 核心思想:能省则省,能合则合,能缓则缓,能睡则睡

21.6 写在最后

嗯,这四个招式其实并不复杂,但真正在项目中坚持做到位的人不多。我见过太多App,功能做得很花哨,但一装到手机上,半天就没电了。用户不会关心你用了什么架构、什么设计模式,他们只知道——这个App太费电,删了。

所以,从今天开始,写每一行代码前,先问自己三个问题:

  • 这个请求真的必须现在发吗?
  • 这个任务能不能跟其他任务一起做?
  • 这个数据能不能从缓存里拿?

养成这个习惯,你的App不仅省电,用户体验也会好很多。