Android Notifications简介
Android Notifications,顾名思义就是通知,在谷歌开发文档中是这样描述的:“Notifications are glanceable, time-sensitive information and actions sent to the user.” 即:通知是发送给用户的可浏览、时间敏感的信息和操作。通知是指 Android 在您应用的界面之外显示的消息,旨在向用户提供提醒、来自他人的通信信息或您应用中的其他实时信息。用户可以点按通知来打开应用,或直接从通知中执行操作。 在设备上的外观
通知可以在不同的位置以不同的格式显示,例如,状态栏中的图标、抽屉式通知栏中比较详细的条目、应用图标上的标志,以及在配对的穿戴式设备上自动显示。
状态栏和抽屉式通知栏
发出通知后,通知会先以图标的形式显示在状态栏中 用户可以在状态栏向下滑动以打开抽屉式通知栏,并在其中查看更多详情及对通知执行操作。 用户可以向下拖动抽屉式通知栏中的某条通知以查看展开后的视图,其中会显示更多内容以及操作按钮(如果有)。
在应用或用户关闭通知之前,通知会一直显示在抽屉式通知栏中。
提醒式通知
从 Android 5.0 开始,通知可以短暂地显示在浮动窗口中,称之为提醒式通知。这种行为通常适用于用户应立即知晓的重要通知,而且仅在设备未锁定时才会显示。 提醒式通知会在应用发出通知后立即出现,稍后便会消失,但仍照常显示在抽屉式通知栏中。
例如,以下情况可能会触发提醒式通知:
用户的 Activity 处于全屏模式(应用使用 fullScreenIntent)。 通知的优先级很高,且在搭载 Android 7.1(API 级别 25)及更低版本的设备上使用铃声或振动。 在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知渠道的重要程度比较高。
锁定屏幕
从 Android 5.0 开始,通知可以显示在锁定屏幕上。
您可以采用编程方式设置您的应用在安全锁定屏幕上所发布通知的详情可见等级,甚至可以设置通知是否显示在锁定屏幕上。
用户可以通过系统设置来选择锁定屏幕通知的详情可见等级,包括选择停用所有锁定屏幕通知。从 Android 8.0开始,用户可以选择停用或启用各个通知渠道的锁定屏幕通知。
通知剖析 通知的设计由系统模板决定,您的应用只需要定义模板中各个部分的内容即可。通知的部分详情仅在展开后的视图中显示。
上图展示了通知最常见的部分,具体如下所示:
- 小图标:必须提供,通过 setSmallIcon() 进行设置。 应用名称:由系统提供。 时间戳:由系统提供,但您可以使用
- setWhen() 替换它或者使用 setShowWhen(false) 隐藏它。
- 大图标:可选内容(通常仅用于联系人照片,请勿将其用于应用图标),通过
- setLargeIcon() 进行设置。 标题:可选内容,通过
- setContentTitle() 进行设置。 文本:可选内容,通过 setContentText() 进行设置。
通知操作 尽管并非强制要求,但每个通知都应在被点按时打开相应的应用 Activity。除了这种默认的通知操作之外,您还可以添加可在通知中完成与应用相关任务的操作按钮(通常不需要打开 Activity),如下图所示。
从 Android 7.0(API 级别 24)开始,您还可以添加直接在通知中回复消息或输入其他文字的操作。
从 Android 10(API 级别 29)开始,平台可以自动生成操作按钮,此类按钮包含基于 intent 的建议操作。
展开式通知 默认情况下,通知的文字内容会被截断以放在一行。如果您需要长一些的通知,可以通过应用其他模板启用更大的展开式文本区域,如下图所示。 通知更新和分组 为了使用户在您提供后续更新时不会遭遇多个通知或多余的通知轰炸,您不妨考虑更新现有通知(而不是发出新通知),或者考虑使用收件箱样式的通知来显示会话更新。
不过,如有必要发出多个通知,则应将这些孤立的通知分为一组(可在搭载 Android 7.0 及更高版本的设备上这样做)。借助通知组,您可以以一条摘要的形式在抽屉式通知栏中将多个通知收拢成一条通知消息。用户便可以展开通知以查看每个通知的详情。
用户可以逐级展开通知组以及其中的每条通知以查看详情。 通知渠道 从 Android 8.0(API 级别 26)开始,必须为所有通知分配渠道,否则通知将不会显示。通过将通知归类到不同的渠道中,用户可以停用您应用的特定通知渠道(而非停用您的所有通知),还可以控制每个渠道的视觉和听觉选项,所有这些操作都在 Android 系统设置中完成(如图 11 所示)。用户还可以长按通知以更改所关联渠道的行为。
在搭载 Android 7.1(API 级别 25)及更低版本的设备上,用户仅可以按应用来管理通知(在搭载 Android 7.1 及更低版本的设备上,每个应用其实只有一个渠道)。
一个应用可以有多个通知渠道(每个渠道对应于该应用发出的每类通知)。应用还可以创建通知渠道来响应您应用的用户做出的选择。例如,您可以为用户在短信应用中创建的每个会话组设置单独的通知渠道。
在搭载 Android 8.0 及更高版本的设备上,渠道还可用于指定通知的重要程度等级。因此,发布到同一通知渠道的所有通知的行为都相同。
通知的重要程度 Android 利用通知的重要程度来决定通知应在多大程度上干扰用户(视觉上和听觉上)。通知的重要程度越高,干扰程度就越高。
在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知的重要程度由通知发布到的渠道的 importance 决定。用户可以在系统设置中更改通知渠道的重要程度(图 12)。 在搭载 Android 7.1(API 级别 25)及更低版本的设备上,每条通知的重要程度均由通知的 priority 决定。
可能的重要程度等级如下所示:
紧急:发出提示音,并以提醒式通知的形式显示。 高:发出提示音。 中:无提示音。 低:无提示音,且不会在状态栏中显示。 无论重要程度如何,所有通知都会在非干扰性的系统界面位置显示,例如,显示在抽屉式通知栏中,以及在启动器图标上作为标志显示
以上只是简单的展示了notification的一些特性,至于具体怎么使用在谷歌开发文档中有着详细的描述,这里就不再赘述。
Notification框架
Notification的框架总体上可任意分为三部分::系统服务端NotificationManagerService,通知显示端SystemUI,还有创建和更新通知的App端。 NotificationManager服务的注册过程
在系统开机时,SystemServer会拉起NotificationManagerService,并把这个服务注册到SystemServiceManager中,由SystemServiceManager进行管理(大多数的系统服务都是这样的),之后需要通知服务的应用可以通过Binder机制,通过SystemServiceManager获得NotificationManagerService的一个动态代理,实现对NotificationManagerService的调用。 ,但是绑定到ServiceManager中Context.NOTIFICATION_ SERVICE的服务类是NotificationManager,所有开发者通过Context.getSystemService(Context.NOTIFICATION_SERVICE)获取回来的服务类不是NotificationManagerServiced服务对象,而是NotificationManager对象,需要再通过NotificationManager对象中的getService()方法,获取SystemServiceManager系统服务管理对象中保存的INotificationManager.Stub()对象。这样NotificationManager就能通过INotificationManager.Stub()对象和NotificationManagerService服务对象进行远程通信了。
NotificationManagerServiced.java
public void onStart() {
SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
....
publishBinderService(Context.NOTIFICATION_SERVICE, mService, false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
publishLocalService(NotificationManagerInternal.class, mInternalService);
....
}
1.从简单使用中看如何获取NotificationManagerServiced服务
如下,notify将通知发出之后,会通过跨进程调用,获得NotificationManager的服务代理对象,
fun realSendNotification() {
var manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val hangIntent = Intent(this, MainActivity::class.java)
val hangPendingIntent =
PendingIntent.getActivity(this, 1001, hangIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val CHANNEL_ID = "your_custom_id"
val CHANNEL_NAME = "your_custom_name"
var notification = NotificationCompat . Builder (this, CHANNEL_ID)
.setContentTitle(mBinding?.vm?.notificationText.toString())
.setContentText("点我返回应用")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(hangPendingIntent)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.img))
.setAutoCancel(true)
.build()
val notificationChannel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel(
CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW
)
} else {
TODO("VERSION.SDK_INT < O")
}
manager.createNotificationChannel(notificationChannel)
manager.notify(1000, notification);
}
NotificationManagerService服务(截取部分代码) 以下代码为NotificationManagerService 类的部分代码,一些分析具体见注释
public class NotificationManagerService extends SystemService {
@Override
public void onStart() {
mListeners = new NotificationListeners();
publishBinderService(Context.NOTIFICATION_SERVICE, mService);
publishLocalService(NotificationManagerInternal.class, mInternalService);
}
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
@Override
public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
String tag, int id, Notification notification, int[] idReceived, int userId) {
enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
idReceived, userId);
}
@Override
public void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,
int userId) {
checkCallerIsSystem();
synchronized (mNotificationList) {
int i = indexOfNotificationLocked(pkg, null, notificationId, userId);
if (i < 0) {
return;
}
NotificationRecord r = mNotificationList.get(i);
StatusBarNotification sbn = r.sbn;
sbn.getNotification().flags =
(r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);
mRankingHelper.sort(mNotificationList);
mListeners.notifyPostedLocked(sbn, sbn );
}
}
};
private final IBinder mService = new INotificationManager.Stub() {
@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);
}
@Override
public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId, REASON_NOMAN_CANCEL,
null);
}
@Override
public void cancelAllNotifications(String pkg, int userId) {
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);
cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,
REASON_NOMAN_CANCEL_ALL, null);
}
@Override
public void registerListener(final INotificationListener listener,
final ComponentName component, final int userid) {
enforceSystemOrSystemUI("INotificationManager.registerListener");
mListeners.registerService(listener, component, userid);
}
@Override
public void unregisterListener(INotificationListener listener, int userid) {
mListeners.unregisterService(listener, userid);
}
};
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
mListeners.notifyPostedLocked(n, oldSbn);
buzzBeepBlinkLocked(r);
}
}
});
idOut[0] = id;
}
public class NotificationListeners extends ManagedServices {
public NotificationListeners() {
super(getContext(), mHandler, mNotificationList, mUserProfiles);
}
@Override
protected IInterface asInterface(IBinder binder) {
return INotificationListener.Stub.asInterface(binder);
}
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
StatusBarNotification sbnClone = null;
StatusBarNotification sbnCloneLight = null;
for (final ManagedServiceInfo info : mServices) {
if (trim == TRIM_LIGHT && sbnCloneLight == null) {
sbnCloneLight = sbn.cloneLight();
} else if (trim == TRIM_FULL) {
sbnClone = sbn.clone();
}
final StatusBarNotification sbnToPost =
(trim == TRIM_FULL) ? sbnClone : sbnCloneLight;
mHandler.post(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, update);
}
});
}
}
public void notifyRemovedLocked(StatusBarNotification sbn) {
final StatusBarNotification sbnLight = sbn.cloneLight();
for (final ManagedServiceInfo info : mServices) {
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
mHandler.post(new Runnable() {
@Override
public void run() {
notifyRemoved(info, sbnLight, update);
}
});
}
}
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener)info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
NotificationRankingUpdate rankingUpdate) {
if (!info.enabledAndUserMatches(sbn.getUserId())) {
return;
}
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationRemoved(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
}
}
/android/frameworks/base/services/core/java/com/android/server/notification/ManagedServices.java
abstract protected IInterface asInterface(IBinder binder);
protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
abstract public class ManagedServices {
abstract protected void onServiceAdded(ManagedServiceInfo info);
protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
public void unregisterService(IInterface service, int userid) {
checkNotNull(service);
unregisterServiceImpl(service, userid);
}
public void registerService(IInterface service, ComponentName component, int userid) {
checkNotNull(service);
ManagedServiceInfo info = registerServiceImpl(service, component, userid);
if (info != null) {
onServiceAdded(info);
}
}
private ManagedServiceInfo registerServiceImpl(final IInterface service,
final ComponentName component, final int userid) {
synchronized (mMutex) {
try {
ManagedServiceInfo info = newServiceInfo(service, component, userid,
true , null, Build.VERSION_CODES.LOLLIPOP);
service.asBinder().linkToDeath(info, 0);
mServices.add(info);
return info;
} catch (RemoteException e) {
}
}
return null;
}
private void unregisterServiceImpl(IInterface service, int userid) {
ManagedServiceInfo info = removeServiceImpl(service, userid);
if (info != null && info.connection != null) {
mContext.unbindService(info.connection);
}
}
}
SystemUI服务与NotificationManagerService的绑定
SystemUI的启动不是本文的重点,这篇文章将跳过SystemUI服务的启动过程,直接看看SystemUI服务与NotificationManagerService是如何绑定的。
SystemUI进程在初始化过程中,会创建一个NotificationListenerService服务类,服务对象中创建一个INotificationListener(这个在上文中也有描述)对象并通过远程过程调用把这个INotificationListener对象注册到NotificationManagerService服务对象的服务管理类子类NotificationListeners对象mListeners中
NotificationManagerService类只是管理Notification的逻辑,显示端是在SystemUI进程中实现的,管理端和显示端处于不同的进程,他们之间的通过binder完成的,,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。所以我们先来看下NotificationManagerService服务和SystemUI进程通信的服务接口文件
在上面的代码中展示了notificaition的简单使用,显然这个notificaition的创建采用了建造者模式,即通过.setxxx(),来定义相关属性,最后通过.build()构造出一个notification。最后在notification处将这个通知发送出去。
流程分析 下面从源码中分析流程
notify()最后通过 service.enqueueNotificationWithTag()将通知传给了NotificationManagerService,具体分析见代码注释。这里的是 service 是 INotificationManager 接口。如果熟 悉 AIDL 等系统相关运行机制的话,就可以看出这里是代理类调用了代理接口的方法,实际方法实现是在 NotificationManagerService 当中。
NotificationManager.java
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, mContext.getUser());
}
...
@UnsupportedAppUsage
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
try {
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
fixNotification(notification), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
service.enqueueNotificationWithTag已经在NotificationManagerService中了,属于NotificationManagerService进程了,在NotificationManagerService中经过一些列调用会走到enqueueNotificationInternal(如下)
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int incomingUserId, boolean postSilently) {
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
final UserHandle user = UserHandle.of(userId);
final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
if (notificationUid == INVALID_UID) {
throw new SecurityException("Caller " + opPkg + ":" + callingUid
+ " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
}
checkRestrictedCategories(notification);
try {
fixNotification(notification, pkg, tag, id, userId);
} catch (Exception e) {
if (notification.isForegroundService()) {
throw new SecurityException("Invalid FGS notification", e);
}
Slog.e(TAG, "Cannot fix notification", e);
return;
}
final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
notification, tag, id, pkg, userId);
if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {
if (!isNotificationShownInternal(pkg, tag, id, userId)) {
reportForegroundServiceUpdate(false, notification, id, pkg, userId);
return;
}
}
mUsageStats.registerEnqueuedByApp(pkg);
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
String channelId = notification.getChannelId();
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
String shortcutId = n.getShortcutId();
final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
pkg, notificationUid, channelId, shortcutId,
true , false );
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
+ ", channelId=" + channelId
+ ", id=" + id
+ ", tag=" + tag
+ ", opPkg=" + opPkg
+ ", callingUid=" + callingUid
+ ", userId=" + userId
+ ", incomingUserId=" + incomingUserId
+ ", notificationUid=" + notificationUid
+ ", notification=" + notification;
Slog.e(TAG, noChannelStr);
boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
== NotificationManager.IMPORTANCE_NONE;
if (!appNotificationsOff) {
doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
"Failed to post notification on channel \"" + channelId + "\"\n" +
"See log for more details");
}
return;
}
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
r.setPostSilently(postSilently);
r.setFlagBubbleRemoved(false);
r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
final boolean fgServiceShown = channel.isFgServiceShown();
if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
|| !fgServiceShown)
&& (r.getImportance() == IMPORTANCE_MIN
|| r.getImportance() == IMPORTANCE_NONE)) {
if (TextUtils.isEmpty(channelId)
|| NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
r.setSystemImportance(IMPORTANCE_LOW);
} else {
channel.setImportance(IMPORTANCE_LOW);
r.setSystemImportance(IMPORTANCE_LOW);
if (!fgServiceShown) {
channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
channel.setFgServiceShown(true);
}
mPreferencesHelper.updateNotificationChannel(
pkg, notificationUid, channel, false);
r.updateNotificationChannel(channel);
}
} else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
channel.setFgServiceShown(true);
r.updateNotificationChannel(channel);
}
}
ShortcutInfo info = mShortcutHelper != null
? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user)
: null;
if (notification.getShortcutId() != null && info == null) {
Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");
}
r.setShortcutInfo(info);
r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));
r.userDemotedAppFromConvoSpace(
mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.getSbn().getOverrideGroupKey() != null)) {
return;
}
if (info != null) {
mShortcutHelper.cacheShortcut(info, user);
}
if (notification.allPendingIntents != null) {
final int intentCount = notification.allPendingIntents.size();
if (intentCount > 0) {
final long duration = LocalServices.getService(
DeviceIdleInternal.class).getNotificationAllowlistDuration();
for (int i = 0; i < intentCount; i++) {
PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
if (pendingIntent != null) {
mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),
ALLOWLIST_TOKEN, duration,
TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
REASON_NOTIFICATION_SERVICE,
"NotificationManagerService");
mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
| FLAG_SERVICE_SENDER));
}
}
}
}
final long token = Binder.clearCallingIdentity();
boolean isAppForeground;
try {
isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
} finally {
Binder.restoreCallingIdentity(token);
}
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
}
首先检查通知发起者是系统进程或者是查看发起者发送的是否是同个 app 的通知信息,否则抛出异常; 除了系统的通知和已注册的监听器允许入队列外,其他 app 的通知都会限制通知数上限和通知频率上限; 将 notification 的 PendingIntent 加入到白名单; 将之前的 notification 进一步封装为 StatusBarNotification 和 NotificationRecord,最后封装到一个异步线程 EnqueueNotificationRunnable 中。
StatusBarNotification以key作为其唯一标识,定义如下: private String key() { String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid; if (overrideGroupKey != null && getNotification().isGroupSummary()) { sbnKey = sbnKey + "|" + overrideGroupKey; } return sbnKey; } 例如:0|com.example.mi.xiaomiapp|11223344|null|10159,源码定义如下所示:
因为是通过 mHandler.post将消息发送到主线程,所以 EnqueueNotificationRunnable继承自Runnable接口,执行的主要任务在他的run方法中。
protected class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;
private final boolean isAppForeground;
EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {
this.userId = userId;
this.r = r;
this.isAppForeground = foreground;
}
@Override
public void run() {
synchronized (mNotificationLock) {
final Long snoozeAt =
mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
r.getUser().getIdentifier(),
r.getSbn().getPackageName(), r.getSbn().getKey());
final long currentTime = System.currentTimeMillis();
if (snoozeAt.longValue() > currentTime) {
(new SnoozeNotificationRunnable(r.getSbn().getKey(),
snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);
return;
}
final String contextId =
mSnoozeHelper.getSnoozeContextForUnpostedNotification(
r.getUser().getIdentifier(),
r.getSbn().getPackageName(), r.getSbn().getKey());
if (contextId != null) {
(new SnoozeNotificationRunnable(r.getSbn().getKey(),
0, contextId)).snoozeLocked(r);
return;
}
mEnqueuedNotifications.add(r);
scheduleTimeoutLocked(r);
final StatusBarNotification n = r.getSbn();
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
NotificationRecord old = mNotificationsByKey.get(n.getKey());
if (old != null) {
r.copyRankingInformation(old);
}
final int callingUid = n.getUid();
final int callingPid = n.getInitialPid();
final Notification notification = n.getNotification();
final String pkg = n.getPackageName();
final int id = n.getId();
final String tag = n.getTag();
updateNotificationBubbleFlags(r, isAppForeground);
handleGroupedNotificationLocked(r, old, callingUid, callingPid);
if (n.isGroup() && notification.isGroupChild()) {
mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
}
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
if (old != null) {
enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
}
EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
pkg, id, tag, userId, notification.toString(),
enqueueStatus);
}
if (mAssistants.isEnabled()) {
mAssistants.onNotificationEnqueuedLocked(r);
mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
DELAY_FOR_ASSISTANT_TIME);
} else {
mHandler.post(new PostNotificationRunnable(r.getKey()));
}
}
}
}
protected class PostNotificationRunnable implements Runnable {
private final String key;
PostNotificationRunnable(String key) {
this.key = key;
}
@Override
public void run() {
synchronized (mNotificationLock) {
try {
NotificationRecord r = null;
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
if (Objects.equals(key, enqueued.getKey())) {
r = enqueued;
break;
}
}
if (r == null) {
Slog.i(TAG, "Cannot find enqueued record for key: " + key);
return;
}
if (isBlocked(r)) {
Slog.i(TAG, "notification blocked by assistant request");
return;
}
final boolean isPackageSuspended =
isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());
r.setHidden(isPackageSuspended);
if (isPackageSuspended) {
mUsageStats.registerSuspendedByAdmin(r);
}
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.getSbn();
final Notification notification = n.getNotification();
if (old == null || old.getSbn().getInstanceId() == null) {
n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
} else {
n.setInstanceId(old.getSbn().getInstanceId());
}
int index = indexOfNotificationLocked(n.getKey());
if (index < 0) {
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
r.setInterruptive(isVisuallyInterruptive(null, r));
} else {
old = mNotificationList.get(index);
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
notification.flags |=
old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
r.isUpdate = true;
final boolean isInterruptive = isVisuallyInterruptive(old, r);
r.setTextChanged(isInterruptive);
r.setInterruptive(isInterruptive);
}
mNotificationsByKey.put(n.getKey(), r);
if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= FLAG_ONGOING_EVENT
| FLAG_NO_CLEAR;
}
mRankingHelper.extractSignals(r);
mRankingHelper.sort(mNotificationList);
final int position = mRankingHelper.indexOf(mNotificationList, r);
int buzzBeepBlinkLoggingCode = 0;
if (!r.isHidden()) {
buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r);
}
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
mListeners.notifyPostedLocked(r, old);
if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
&& !isCritical(r)) {
mHandler.post(new Runnable() {
@Override
public void run() {
mGroupHelper.onNotificationPosted(
n, hasAutoGroupSummaryLocked(n));
}
});
} else if (oldSbn != null) {
final NotificationRecord finalRecord = r;
mHandler.post(() -> mGroupHelper.onNotificationUpdated(
finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));
}
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(r,
NotificationListenerService.REASON_ERROR, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
mGroupHelper.onNotificationRemoved(n);
}
});
}
Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+ n.getPackageName());
}
if (mShortcutHelper != null) {
mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
false ,
mHandler);
}
maybeRecordInterruptionLocked(r);
maybeRegisterMessageSent(r);
maybeReportForegroundServiceUpdate(r, true);
mNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,
buzzBeepBlinkLoggingCode, getGroupInstanceId(r.getSbn().getGroupKey()));
} finally {
int N = mEnqueuedNotifications.size();
for (int i = 0; i < N; i++) {
final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
if (Objects.equals(key, enqueued.getKey())) {
mEnqueuedNotifications.remove(i);
break;
}
}
}
}
}
}
以上代码会 notification ranking service,有新的 notification 进 来,然后对所有 notification 进行重新排序; 然后到最后会调用 mListeners.notifyPostedLocked() 方法。这里 mListeners 是 NotificationListeners 类的一个实例。
public class NotificationListeners extends ManagedServices {
@GuardedBy("mNotificationLock")
public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
notifyPostedLocked(r, old, true);
}
@GuardedBy("mNotificationLock")
private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
boolean notifyAllListeners) {
try {
StatusBarNotification sbn = r.getSbn();
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, r. getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
if (!oldSbnVisible && !sbnVisible) {
continue;
}
if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
continue;
}
if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
continue;
}
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
if (oldSbnVisible && !sbnVisible) {
final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
mHandler.post(() -> notifyRemoved(
info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
continue;
}
final int targetUserId = (info.userid == UserHandle.USER_ALL)
? UserHandle.USER_SYSTEM : info.userid;
updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(() -> notifyPosted(info, sbnToPost, update));
}
} catch (Exception e) {
Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
}
}
StatusBarNotification会发送给上述中的所有注册了NotificationListenerService的client,包括system UI等。上述的ManagedServiceInfo可以有system UI,手机管家以及用户自己实现NLS的任意APP。 mHandler.post(() -> notifyPosted(info, sbnToPost, update))最后会执行 notifyPosted(info, sbnToPost, update);
NotificationManagerService.java
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
}
}
之后就会交给systemUI去对通知进行处理(systemUI的处理过程还在分析)
|