19. 通知与消息推送:让用户随时感知你的存在
通知,是 Android 应用与用户沟通的「最后一公里」。做得好了,用户觉得贴心;做得差了,用户直接关掉通知权限,甚至卸载应用。我这些年做项目,踩过不少通知的坑,今天就把这些经验掰开揉碎讲给你听。
本章核心知识点
- Notification 通知构建:从零到一创建通知
- NotificationChannel:Android 8.0 后的必知概念
- Android 12+ 通知适配:新版本的新规矩
- FCM 集成:云端推送的标配方案
- 本地通知:不依赖网络也能发通知
19.1 Notification 通知构建:从零开始
通知说白了就是一个显示在系统栏的 UI 组件。Android 提供了 NotificationCompat.Builder 来帮我们构建,兼容性做得不错。
最基本的通知长这样:
// 创建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("新消息")
.setContentText("你有一条未读消息")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.notify(NOTIFICATION_ID, builder.build());
嗯,这里要注意 setAutoCancel(true)。我刚开始做的时候忘了加这个,用户点击通知后通知还在那挂着,被测试骂了好几次。加上之后,点击通知自动消失,体验好很多。
小技巧:NOTIFICATION_ID 最好用常量管理,同一个 ID 会覆盖之前的通知。如果你想展示多条通知,每个 ID 必须不同。
19.2 通知渠道:Android 8.0 的分水岭
Android 8.0(API 26)引入了 NotificationChannel。我个人觉得这是 Android 通知史上最重要的改动。为什么?因为它把通知的控制权还给了用户。
以前开发者想怎么发就怎么发,用户只能全部关掉或全部打开。现在每个渠道都可以单独控制——用户可以把「促销通知」关掉,但保留「订单通知」。
// 创建通知渠道(只需创建一次)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "订单通知";
String description = "包含订单状态变更的通知";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(
CHANNEL_ORDER, name, importance
);
channel.setDescription(description);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
这里有个坑:渠道一旦创建,重要性等级就不能改了。除非用户卸载重装。所以创建渠道前,想清楚这个渠道到底该多重要。
| 重要性等级 | 行为表现 | 适用场景 |
|---|---|---|
| IMPORTANCE_HIGH | 弹出横幅、有声音、显示在锁屏 | 即时消息、来电 |
| IMPORTANCE_DEFAULT | 有声音、显示在通知栏 | 订单通知、系统提醒 |
| IMPORTANCE_LOW | 无声音、显示在通知栏 | 订阅更新、内容推荐 |
| IMPORTANCE_MIN | 无声音、折叠在底部 | 后台任务完成提示 |
避坑指南:我曾经在一个项目中把所有通知都设为 HIGH 重要性,结果用户直接关掉了整个应用的通知权限。后来改成按场景分级,用户投诉少了 70%。别滥用 HIGH,用户不是傻子。
19.3 Android 12+ 通知适配:新版本的新规矩
Android 12 开始,通知权限变成了运行时权限。这意味着你需要像请求相机权限一样,主动请求通知权限。
具体来说,Android 13(API 33)引入了 POST_NOTIFICATIONS 权限。如果你的 targetSdk 是 33 及以上,必须在运行时请求这个权限。
// Android 13+ 需要运行时请求通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityResultLauncher<String> launcher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
// 发送通知
} else {
// 引导用户去设置开启
}
});
launcher.launch(Manifest.permission.POST_NOTIFICATIONS);
}
}
你想想看,如果用户拒绝了权限,你的通知就发不出去了。这时候怎么办?我建议在用户拒绝后,弹个 Dialog 引导他去系统设置里手动开启。别直接放弃,也别反复弹窗惹人烦。
Android 12 还有一个变化:通知的「快速设置」按钮可以一键关闭所有通知。所以你的通知渠道设计要合理,否则用户一键全关,你连解释的机会都没有。
19.4 FCM 集成:云端推送的标配
Firebase Cloud Messaging(FCM)是 Google 官方的推送方案。说白了就是你的服务器把消息发给 FCM 服务器,FCM 再推送到用户手机。省去了你自己维护长连接的麻烦。
集成步骤其实不复杂:
- 在 Firebase 控制台创建项目,下载
google-services.json - 在
build.gradle添加依赖 - 创建
FirebaseMessagingService子类 - 获取设备 Token 并上传到自己的服务器
// 1. 添加依赖(app/build.gradle)
implementation 'com.google.firebase:firebase-messaging:23.2.0'
// 2. 创建服务
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(String token) {
// 新 Token 生成时,上传到自己的服务器
sendTokenToServer(token);
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// 收到消息,构建通知
if (remoteMessage.getNotification() != null) {
showNotification(remoteMessage.getNotification());
}
// 处理数据消息
if (remoteMessage.getData().size() > 0) {
handleDataMessage(remoteMessage.getData());
}
}
}
这里有个细节:FCM 消息分两种——通知消息(系统自动展示)和数据消息(需要自己处理)。通知消息在应用后台时由系统直接展示,但应用前台时不会自动弹出。所以很多人会在 onMessageReceived 里自己构建通知,保证前后台行为一致。
个人经验:我习惯把 FCM 的 Token 存在 SharedPreferences 里,每次启动时检查是否变化。如果变了就更新服务器。这样能避免 Token 过期导致推送失败。
19.5 本地通知:不依赖网络也能发
本地通知就是应用自己调度的通知,不需要服务器参与。典型场景:闹钟提醒、定时任务完成、倒计时结束。
Android 提供了 AlarmManager 和 WorkManager 两种方式来实现定时通知。我个人更推荐 WorkManager,因为它能处理设备重启后的恢复。
// 使用 WorkManager 发送本地通知
public class NotificationWorker extends Worker {
public NotificationWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
// 构建并发送通知
NotificationManager manager = (NotificationManager)
getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(
getApplicationContext(), CHANNEL_ID
)
.setSmallIcon(R.drawable.ic_alarm)
.setContentTitle("提醒")
.setContentText("该喝水了!")
.build();
manager.notify(1001, notification);
return Result.success();
}
}
// 调度任务:1小时后执行
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(NotificationWorker.class)
.setInitialDelay(1, TimeUnit.HOURS)
.build();
WorkManager.getInstance(context).enqueue(workRequest);
嗯,这里要注意:Android 12 开始,精确闹钟需要权限。如果你的通知对时间要求很严格(比如吃药提醒),需要申请 SCHEDULE_EXACT_ALARM 权限。否则就用 WorkManager 的近似调度,系统会帮你省电。
避坑指南:我曾经用 AlarmManager 做本地通知,结果用户重启手机后所有通知都丢了。后来换成 WorkManager,它自动处理了设备重启后的重新调度。所以能用 WorkManager 就别用 AlarmManager。
19.6 通知的最佳实践
最后,分享几个我这些年总结的通知经验:
- 渠道数量控制在 3-5 个:太多用户会烦,太少不够精细
- 通知内容要精简:标题不超过 30 字,内容不超过 60 字
- 善用通知分组:同类型的通知折叠在一起,比如「3 条新消息」
- 不要滥用高优先级:只有真正紧急的消息才用 HIGH
- 测试所有 Android 版本:不同版本的通知行为差异很大
通知这件事,说白了就是「在合适的时间,用合适的方式,告诉用户合适的信息」。做得好,用户觉得贴心;做不好,用户直接关权限。希望今天的分享能帮你少走弯路。
公众号:蓝海资料掘金营,微信deep3321