本地通知的实现
Android 通知服务通过 NotificationManager
系统服务实现,允许应用向用户发送通知。以下是其实现的关键步骤:
1. 创建通知渠道(Android 8.0+)
从 Android 8.0(API 26)开始,通知必须关联到通知渠道。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"channel_id",
"Channel Name",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
2. 构建通知
使用 NotificationCompat.Builder
创建通知,设置标题、内容、图标等。
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id")
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("Title")
.setContentText("Content")
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
3. 发送通知
通过 NotificationManager
发送通知。
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(notificationId, builder.build());
4. 处理通知点击
使用 PendingIntent
定义通知点击后的行为。
Intent intent = new Intent(this, TargetActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id")
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("Title")
.setContentText("Content")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
5. 更新或取消通知
- 更新通知:使用相同的
notificationId
再次调用notify()
。 - 取消通知:调用
NotificationManager.cancel(notificationId)
。
6. 高级功能
- 进度条:使用
setProgress(max, progress, indeterminate)
显示进度。 - 大文本样式:使用
setStyle(new NotificationCompat.BigTextStyle().bigText(longText))
显示更多内容。 - 操作按钮:通过
addAction()
添加操作按钮。
7. 权限
在 AndroidManifest.xml
中声明通知权限:
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
Android 通知服务通过 NotificationManager
和 NotificationCompat.Builder
实现,支持通知渠道、点击处理、进度条等功能,帮助应用与用户有效交互。
远程通知的实现(FCM)
上面提到的通知是本地通知,即由应用在设备上直接创建和发送的通知。Android 还支持通过服务器向客户端发送通知,通常称为远程通知或推送通知。
通过服务器发送通知的实现方式
Android 通过 Firebase Cloud Messaging (FCM) 实现服务器向客户端发送通知。FCM 是 Google 提供的免费服务,支持跨平台消息推送。
以下是实现远程通知的关键步骤:
1. 集成 Firebase
- 在 Firebase 控制台 中创建一个项目。
- 将 Firebase 添加到 Android 应用:
- 在 Firebase 控制台中注册应用,下载
google-services.json
文件。 - 将
google-services.json
文件放到 Android 项目的app
目录下。 - 在
build.gradle
文件中添加 Firebase 依赖:// 项目级 build.gradle buildscript { dependencies { classpath 'com.google.gms:google-services:4.3.15' // 使用最新版本 } } // 应用级 build.gradle apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' dependencies { implementation 'com.google.firebase:firebase-messaging:23.1.0' // 使用最新版本 }
- 在 Firebase 控制台中注册应用,下载
2. 获取设备令牌
设备令牌(Token)是 FCM 用于标识设备的唯一 ID。应用启动时需要获取令牌并发送到服务器。
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.w("FCM", "Fetching FCM registration token failed", task.getException());
return;
}
// 获取 Token
String token = task.getResult();
Log.d("FCM", "FCM Token: " + token);
// 将 Token 发送到服务器
sendTokenToServer(token);
});
3. 服务器发送通知
服务器通过 FCM API 向客户端发送通知。以下是一个简单的 HTTP 请求示例(使用 FCM 的旧版 API):
请求 URL
https://fcm.googleapis.com/fcm/send
请求头
Authorization: key=<YOUR_SERVER_KEY>
Content-Type: application/json
请求体
{
"to": "<DEVICE_TOKEN>",
"notification": {
"title": "Title",
"body": "Body",
"icon": "icon_name",
"click_action": "TARGET_ACTIVITY"
},
"data": {
"key1": "value1",
"key2": "value2"
}
}
to
:目标设备的 Token。notification
:通知内容,会显示在系统通知栏。data
:自定义数据,可以在客户端处理。
4. 客户端处理通知
当客户端收到通知时,可以通过 FirebaseMessagingService
处理。
创建自定义服务
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// 检查消息是否包含通知负载
if (remoteMessage.getNotification() != null) {
String title = remoteMessage.getNotification().getTitle();
String body = remoteMessage.getNotification().getBody();
// 显示通知
sendNotification(title, body);
}
// 检查消息是否包含数据负载
if (remoteMessage.getData().size() > 0) {
Log.d("FCM", "Data payload: " + remoteMessage.getData());
}
}
private void sendNotification(String title, String body) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
NotificationManagerCompat manager = NotificationManagerCompat.from(this);
manager.notify(0, builder.build());
}
}
在 AndroidManifest.xml
中注册服务
<service
android:name=".MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service>
5. 处理通知点击
如果通知中包含 click_action
,可以在 AndroidManifest.xml
中为目标 Activity 添加 Intent Filter:
<activity android:name=".TargetActivity">
<intent-filter>
<action android:name="TARGET_ACTIVITY"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
FCM 是 Android 推送通知的标准解决方案,支持高可靠性和低延迟的消息传递。
FCM消息的类型
FCM收到通知的行为取决于消息的类型:
1. 通知消息(Notification Messages)
当服务器发送的消息中包含 notification
字段时,FCM 会自动处理并在状态栏显示通知,不需要客户端额外调用 sendNotification
。
示例消息格式
{
"to": "<DEVICE_TOKEN>",
"notification": {
"title": "Title",
"body": "Body",
"icon": "icon_name",
"click_action": "TARGET_ACTIVITY"
}
}
- 行为:
- 如果应用在前台,
onMessageReceived
会被调用,但通知不会自动显示,需要手动调用sendNotification
。 - 如果应用在后台或未运行,FCM 会自动在状态栏显示通知。
- 如果应用在前台,
2. 数据消息(Data Messages)
当服务器发送的消息中只包含 data
字段时,FCM 不会自动显示通知,需要客户端在 onMessageReceived
中手动处理并调用 sendNotification
。
示例消息格式
{
"to": "<DEVICE_TOKEN>",
"data": {
"title": "Title",
"body": "Body",
"key1": "value1",
"key2": "value2"
}
}
- 行为:
- 无论应用在前台还是后台,
onMessageReceived
都会被调用。 - 需要手动调用
sendNotification
来显示通知。
- 无论应用在前台还是后台,
3. 混合消息(Notification + Data Messages)
消息中同时包含 notification
和 data
字段时:
- 如果应用在前台,
onMessageReceived
会被调用,但通知不会自动显示,需要手动调用sendNotification
。 - 如果应用在后台或未运行,FCM 会自动显示通知,并将
data
字段传递给目标 Activity。
示例消息格式
{
"to": "<DEVICE_TOKEN>",
"notification": {
"title": "Title",
"body": "Body"
},
"data": {
"key1": "value1",
"key2": "value2"
}
}
为什么需要手动调用 sendNotification
?
- 应用在前台时:FCM 不会自动显示通知,以避免打扰用户。开发者可以在
onMessageReceived
中根据业务逻辑决定是否显示通知。 - 应用在后台或未启动时:FCM 会自动显示通知(仅对
notification
消息有效)。
FMC通知显示原理
FCM 依赖于 Google Play 服务运行,即使应用未启动,Google Play 服务仍然可以接收和处理 FCM 消息。FCM 接收到消息后,会将通知传递给系统的 NotificationManagerService。
NotificationManagerService 负责将通知存储到系统通知数据库中,并通知 System UI 显示通知。
当 NotificationManagerService 传递通知时,System UI 会根据通知的优先级决定是否弹出 Heads-up 通知窗口。
没有google play服务的手机是怎么弹出通知的?
国产手机厂商各家通常会提供自己的推送服务。另外也有一些集成的第三方推送聚合服务。
如何确保通知始终显示?
无论应用在前台还是后台,都可以通过以下方式确保通知显示:
修改 onMessageReceived
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
// 检查消息是否包含通知负载
if (remoteMessage.getNotification() != null) {
String title = remoteMessage.getNotification().getTitle();
String body = remoteMessage.getNotification().getBody();
sendNotification(title, body);
}
// 检查消息是否包含数据负载
if (remoteMessage.getData().size() > 0) {
Log.d("FCM", "Data payload: " + remoteMessage.getData());
// 如果需要,可以从 data 中提取标题和内容
String title = remoteMessage.getData().get("title");
String body = remoteMessage.getData().get("body");
sendNotification(title, body);
}
}
private void sendNotification(String title, String body) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "channel_id")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title)
.setContentText(body)
.setAutoCancel(true)
.setContentIntent(pendingIntent);
NotificationManagerCompat manager = NotificationManagerCompat.from(this);
manager.notify(0, builder.build());
}
总结
- 通知消息(
notification
):- 应用在前台:需要手动调用
sendNotification
。 - 应用在后台:FCM 自动显示通知。
- 应用在前台:需要手动调用
- 数据消息(
data
):- 无论应用在前台还是后台,都需要手动调用
sendNotification
。
- 无论应用在前台还是后台,都需要手动调用
- 最佳实践:
- 如果希望通知始终显示,可以在
onMessageReceived
中统一处理并调用sendNotification
。 - 如果希望减少代码重复,可以优先使用
notification
消息,并在前台时手动显示通知。
- 如果希望通知始终显示,可以在
通知服务的系统实现原理
Android 的通知服务是一个系统服务,其实现和管理涉及到多个系统组件。
1. 通知服务是系统服务
通知服务在 Android 中是通过 NotificationManager
提供的,而 NotificationManager
是一个系统服务,由 ServiceManager 管理。
-
注册过程:
- 在系统启动时,
NotificationManagerService
(简称 NMS)会被创建并注册到ServiceManager
中。 - 应用通过
Context.NOTIFICATION_SERVICE
获取NotificationManager
的实例,实际上是通过 Binder 机制与NotificationManagerService
交互。
- 在系统启动时,
-
源码路径:
NotificationManagerService
的实现在 AOSP 中的路径为:frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
2. 通知窗口的弹出由 System UI 负责
通知的显示和弹出是由 System UI 进程负责的。System UI 是 Android 系统的一个核心组件,负责管理状态栏、导航栏、通知栏等界面元素。
-
System UI 的职责:
- 当
NotificationManagerService
接收到通知时,会将通知传递给 System UI。 - System UI 负责将通知显示在状态栏、通知栏以及弹出通知窗口(Heads-up Notification)。
- 当
-
Heads-up 通知:
- 当收到重要通知时(如来电、闹钟等),System UI 会在屏幕顶部弹出 Heads-up 通知窗口。
- Heads-up 通知的显示逻辑由 System UI 中的
HeadsUpManager
负责。
-
源码路径:
- System UI 的源码在 AOSP 中的路径为:
frameworks/base/packages/SystemUI/
- 通知相关的实现主要在:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/
- System UI 的源码在 AOSP 中的路径为:
3. 通知服务的整体流程
以下是通知从发送到显示的完整流程:
(1)应用发送通知
- 应用通过
NotificationManager.notify()
发送通知。 NotificationManager
通过 Binder 调用NotificationManagerService
的enqueueNotification()
方法。
(2)NotificationManagerService 处理通知
NotificationManagerService
对通知进行权限检查、排序、分组等处理。- 将通知存储到系统的通知数据库中。
- 将通知传递给 System UI 进行显示。
(3)System UI 显示通知
- System UI 从
NotificationManagerService
获取通知数据。 - 根据通知的优先级和设置,决定是否显示为 Heads-up 通知。
- 在状态栏、通知栏或弹出窗口中显示通知。
(4)用户交互
- 当用户点击通知时,System UI 会通过
PendingIntent
启动目标 Activity 或执行其他操作。
4. 关键类和服务
NotificationManagerService
:- 负责管理所有通知的生命周期。
- 处理通知的存储、排序、分组等逻辑。
System UI
:- 负责通知的界面显示。
- 包括状态栏、通知栏、Heads-up 通知等。
HeadsUpManager
:- 负责管理 Heads-up 通知的显示和隐藏。
NotificationListenerService
:- 允许第三方应用监听通知事件(如通知的发布、更新、删除等)。
5. 源码示例
以下是通知服务相关的关键源码片段:
NotificationManagerService
注册
// 在 SystemServer.java 中注册 NotificationManagerService
private void startOtherServices() {
NotificationManagerService nms = new NotificationManagerService(context);
ServiceManager.addService(Context.NOTIFICATION_SERVICE, nms);
}
System UI 显示通知
// 在 System UI 中处理通知显示
public class NotificationPresenter {
public void updateNotification(StatusBarNotification sbn) {
// 更新通知到状态栏或通知栏
}
public void showHeadsUp(StatusBarNotification sbn) {
// 显示 Heads-up 通知
}
}
总结
- 通知服务:由
NotificationManagerService
实现,并通过ServiceManager
注册为系统服务。 - 通知显示:由 System UI 进程负责,包括状态栏、通知栏和 Heads-up 通知的显示。
- 整体流程:
- 应用通过
NotificationManager
发送通知。 NotificationManagerService
处理通知并存储。- System UI 从
NotificationManagerService
获取通知并显示。
- 应用通过
通过这种设计,Android 实现了通知的统一管理和高效显示。