26、通知的性能优化:通知的复用、避免频繁更新、批量操作、通知的内存管理

通知这东西,看起来简单,不就是发个弹窗嘛。但你要是做过日活千万级的App,就知道通知优化有多重要了。我见过不少App,通知发着发着就卡了,甚至直接OOM崩溃。说白了,通知管理不当,就是一颗定时炸弹。

今天咱们聊聊通知的性能优化。四个方向:复用、避免频繁更新、批量操作、内存管理。嗯,一个一个来。

通知的复用:别每次都新建

你想想看,每次发通知都创建一个新的Notification对象,这合理吗?不合理。尤其是那些持续更新的通知,比如音乐播放器、下载进度条、导航提示——这些场景下,通知的内容在变,但通知本身是同一个。

我个人习惯的做法是:使用相同的通知ID。只要ID相同,系统就会自动复用之前的通知,而不是创建新的。

// 错误示范:每次都新建
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
for (int i = 0; i < 10; i++) {
    Notification notification = buildNotification("进度:" + i + "%");
    manager.notify(i, notification); // 每次ID都不同,创建了10个通知
}

// 正确做法:复用同一个ID
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
int NOTIFICATION_ID = 1001;
for (int i = 0; i < 10; i++) {
    Notification notification = buildNotification("进度:" + i + "%");
    manager.notify(NOTIFICATION_ID, notification); // 同一个ID,复用通知
}

核心原则:同一个逻辑通知,永远使用同一个ID。不要为了更新内容就换ID。

我在项目中遇到过一个问题:音乐播放器的通知,每次切歌都换ID。结果通知栏里堆了一堆播放器通知,用户都懵了。后来改成固定ID,一切正常。

避免频繁更新:别让系统累死

有些场景下,通知更新的频率非常高。比如下载进度条,每秒更新几十次。你想想看,每次更新都要走一遍Binder跨进程通信,系统不累吗?

我的建议是:做节流。说白了,就是控制更新频率,不要每次变化都去调notify()。

// 节流更新:至少间隔200ms
private long lastUpdateTime = 0;
private static final long THROTTLE_MS = 200;

public void updateProgress(int progress) {
    long now = System.currentTimeMillis();
    if (now - lastUpdateTime < THROTTLE_MS) {
        return; // 更新太频繁,跳过
    }
    lastUpdateTime = now;
    
    Notification notification = buildProgressNotification(progress);
    notificationManager.notify(NOTIFICATION_ID, notification);
}

小技巧:对于进度条通知,我一般控制在每秒更新5次以内。人眼根本看不出每秒30帧和每秒5帧的区别,但系统负担能减少80%。

为什么会这样?因为每次notify()都会触发系统重新渲染通知栏。你更新得越快,系统UI线程就越忙,最终导致通知栏卡顿,甚至ANR。

批量操作:一次搞定别零碎

有时候你需要同时更新多个通知,或者取消多个通知。如果你一个一个来,每次都是一次跨进程调用。批量操作就是把这些调用合并成一次。

Android从API 14开始就支持了批量操作,但很多人不知道。嗯,这里要注意:使用cancelAll()而不是逐个cancel()

// 错误示范:逐个取消
for (int id : notificationIds) {
    notificationManager.cancel(id); // 每个ID一次跨进程调用
}

// 正确做法:批量取消
notificationManager.cancelAll(); // 一次调用,取消所有通知

// 或者使用NotificationManager的cancelAsPackage(需要权限)
// 但一般用cancelAll就够了

批量更新的场景呢?比如你一次性收到了10条消息,每条都要发通知。这时候不要循环调10次notify(),而是应该合并成一条通知(使用通知分组功能)。

// 使用通知分组:合并多条消息
Notification summary = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setGroup(GROUP_KEY_MESSAGES)
        .setGroupSummary(true)
        .setContentTitle("你有10条新消息")
        .setContentText("点击查看详情")
        .build();

notificationManager.notify(SUMMARY_ID, summary);

我曾经踩过的坑:有一次批量发送通知,循环里调了50次notify(),结果低端手机上直接卡死。后来改成分组通知,性能问题瞬间解决。

通知的内存管理:别让通知吃光内存

通知本身是跨进程的,Notification对象会被序列化后传给SystemServer。如果你在Notification里塞了太多东西,比如大图、长文本、自定义RemoteViews,那内存消耗就很可观了。

我见过最夸张的情况:有人在Notification的extras里塞了一个2MB的Bitmap。结果每次更新通知,都要序列化2MB的数据跨进程传输。你说系统能不卡吗?

通知内容 内存消耗(估算) 优化建议
纯文本通知 ~5KB 无需优化
带小图标(48x48) ~50KB 使用矢量图或压缩PNG
带大图(720x1280) ~500KB-2MB 压缩到合理尺寸,使用BitmapFactory.Options
自定义RemoteViews ~100KB+ 减少View层级,避免复杂布局

优化内存的几个实操建议:

  • 图片压缩:通知里的大图,一定要压缩。我一般控制在720px宽度以内,质量80%就够了。
  • 避免大对象:不要在Notification的extras里放Bitmap、大字符串、序列化对象。
  • 及时清理:不再需要的通知,记得cancel掉。别让通知栏里堆着几十条历史通知。
  • RemoteViews轻量化:自定义布局尽量简单,不要嵌套太多层级。
// 图片压缩示例
public Bitmap compressForNotification(Bitmap original) {
    int maxWidth = 720;
    int width = original.getWidth();
    int height = original.getHeight();
    
    if (width <= maxWidth) {
        return original;
    }
    
    float ratio = (float) maxWidth / width;
    int newWidth = maxWidth;
    int newHeight = (int) (height * ratio);
    
    return Bitmap.createScaledBitmap(original, newWidth, newHeight, true);
}

一句话总结:通知的内存管理,核心就是「少传数据、及时释放」。别把通知当成数据通道,它只是个UI组件。

知识体系总览

下面这张图,把通知性能优化的四个维度串起来了。你可以把它当作一个检查清单,每次做通知功能时对照一下。

通知性能优化知识体系 通知性能优化 通知复用 避免频繁更新 批量操作 内存管理 固定通知ID 避免重复创建 节流控制(200ms) 每秒更新≤5次 使用cancelAll() 通知分组合并 图片压缩 避免大对象 及时清理通知 核心原则:少创建、少更新、少传输、及时释放

这四个维度,说白了就是一句话:别让通知成为性能瓶颈。复用ID、控制频率、批量操作、管好内存——做到这四点,你的通知功能基本就稳了。

我做了这么多年Android,见过太多通知相关的性能问题。其实大部分问题都不是技术难题,而是习惯问题。养成好习惯,通知优化就是顺手的事。


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