深入理解 Android 通知服务:从本地通知到 FCM 推送的实现与实现原理

本地通知的实现

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 通知服务通过 NotificationManagerNotificationCompat.Builder 实现,支持通知渠道、点击处理、进度条等功能,帮助应用与用户有效交互。

远程通知的实现(FCM)

上面提到的通知是本地通知,即由应用在设备上直接创建和发送的通知。Android 还支持通过服务器向客户端发送通知,通常称为远程通知推送通知

通过服务器发送通知的实现方式

Android 通过 Firebase Cloud Messaging (FCM) 实现服务器向客户端发送通知。FCM 是 Google 提供的免费服务,支持跨平台消息推送。

以下是实现远程通知的关键步骤:


1. 集成 Firebase

  1. Firebase 控制台 中创建一个项目。
  2. 将 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' // 使用最新版本
      }
      

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)

消息中同时包含 notificationdata 字段时:

  • 如果应用在前台,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/
      

3. 通知服务的整体流程

以下是通知从发送到显示的完整流程:

(1)应用发送通知
  • 应用通过 NotificationManager.notify() 发送通知。
  • NotificationManager 通过 Binder 调用 NotificationManagerServiceenqueueNotification() 方法。
(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 通知的显示。
  • 整体流程
    1. 应用通过 NotificationManager 发送通知。
    2. NotificationManagerService 处理通知并存储。
    3. System UI 从 NotificationManagerService 获取通知并显示。

通过这种设计,Android 实现了通知的统一管理和高效显示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值