本文预计阅读时间为20分钟
本文需要解决的问题
笔者最近正在做一个项目,里面需要用到 Android Notification 机制来实现某些特定需求。我正好通过这个机会研究一下 Android Notification 相关的发送逻辑和接收逻辑,以及整理相关的笔记。我研究 Notification 机制的目的是解决以下我在使用过程中所思考的问题:
- 我们创建的 Notification 实例最终以什么样的方式发送给系统?
- 系统是如何接收到 Notification 实例并显示的?
- 我们是否能拦截其他 app 的 Notification 并获取其中的信息?
什么是 Android Notification 机制?
Notification,中文名翻译为通知,每个 app 可以自定义通知的样式和内容等,它会显示在系统的通知栏等区域。用户可以打开抽屉式通知栏查看通知的详细信息。在实际生活中,Android Notification 机制有很广泛的应用,例如 IM app 的新消息通知,资讯 app 的新闻推送等等。
源码分析
本文的源码基于 Android 7.0。
Notification 的发送逻辑
一般来说,如果我们自己的 app 想发送一条新的 Notification,可以参照以下代码:
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.notification_icon)
.setWhen(System.currentTimeMillis())
.setContentTitle("Test Notification Title")
.setContentText("Test Notification Content!");
Intent resultIntent = new Intent(this, ResultActivity.class);
PendingIntent contentIntent =
PendingIntent.getActivity(
this,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify(mId, mBuilder.build());
可以看到,我们通过 NotificationCompat.Builder 新建了一个 Notification 对象,最后通过 NotificationManager#notify() 方法将 Notification 发送出去。
NotificationManager#notify()
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
// 省略部分注释
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
/**
* @hide
*/
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (notification.getSmallIcon() == null) {
throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+ notification);
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
try {
// !!!
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, idOut, user.getIdentifier());
if (localLOGV && id != idOut[0]) {
Log.v(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我们可以看到,到最后会调用 service.enqueueNotificationWithTag() 方法,这里的是 service 是 INotificationManager 接口。如果熟悉 AIDL 等系统相关运行机制的话,就可以看出这里是代理类调用了代理接口的方法,实际方法实现是在 NotificationManagerService 当中。
NotificationManagerService#enqueueNotificationWithTag()
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId);