不积跬步无以至千里
最近被提了一个关于通知栏上通知排序的bug,之前就想过我们的通知栏上的顺序是在哪进行排序的?其实为什么呢?因为浸提那应用的同事提了一个设置Setpriority(int value)的方法,结果设置优先级较大了,还是没有排到通知队列的前边,很纳闷,今天就看一下咋回事。
其实通知由NotificationManager创建,然后通过IPC传到了NotificationManagerService里面,如图
NotificationManager.java的notify方法
其中核心实现是在调用notifyAsUser方法中,如下图:
如图可知,其中的调用的就是NotificationManagerService中的enqueueNotificationWithTag方法。
其中创建的逻辑咱们就不深究了,咱们看一下关于frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java,它是通知逻辑的真正实现者。
如下图,
看见了binder就看到了对应着实现者的实现逻辑,而我们的关于通知的处理逻辑是在这个内名内部类中,然后我们看下在NotificationManager中调用的方法,enqueueNotificationWithTag(),如下图
其内部实现方法实则为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[] idOut, int incomingUserId) {
-
if (DBG) {
-
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
-
+ " notification=" + notification);
-
}
-
/// M:仅仅是过滤一些特殊的通知流,正常的可以忽略{
-
boolean foundTarget = false;
-
if (pkg != null && pkg.contains(".stub") && notification != null) {
-
String contentTitle = notification.extras != null ?
-
notification.extras.getString(Notification.EXTRA_TITLE) : " ";
-
if (contentTitle != null && contentTitle.startsWith("notify#")) {
-
foundTarget = true;
-
Slog.d(TAG, "enqueueNotification, found notification, callingUid: " + callingUid
-
+ ", callingPid: " + callingPid + ", pkg: " + pkg
-
+ ", id: " + id + ", tag: " + tag);
-
}
-
}
-
/// @}
-
//检测是否为Phone进程或者系统进程,是否为同一个uid发送,
-
checkCallerIsSystemOrSameApp(pkg);
-
//是否为系统通知
-
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
-
//是否为NotificationListenerService监听
-
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
-
-
final int userId = ActivityManager.handleIncomingUser(callingPid,
-
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
-
final UserHandle user = new UserHandle(userId);
-
-
// Fix the notification as best we can.
-
try {
-
final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser(
-
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
-
(userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
-
Notification.addFieldsFromContext(ai, userId, notification);
-
} catch (NameNotFoundException e) {
-
Slog.e(TAG, "Cannot create a context for sending app", e);
-
return;
-
}
-
-
mUsageStats.registerEnqueuedByApp(pkg);
-
-
-
if (pkg == null || notification == null) {
-
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
-
+ " id=" + id + " notification=" + notification);
-
}
-
//创建一个StatusBarNotification对象
-
final StatusBarNotification n = new StatusBarNotification(
-
pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
-
user);
-
//这里系统限制了每个应用的发送通知的数量,来阻止DOS攻击防止泄露,这块Toast也有这块的处理会对一个应用发送的条数做处理
-
if (!isSystemNotification && !isNotificationFromListener) {
-
synchronized (mNotificationList) {
-
//判断是否为一个新的通知还是更新
-
if(mNotificationsByKey.get(n.getKey()) != null) {
-
// this is an update, rate limit updates only
-
final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
-
if (appEnqueueRate > mMaxPackageEnqueueRate) {
-
mUsageStats.registerOverRateQuota(pkg);
-
final long now = SystemClock.elapsedRealtime();
-
if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
-
Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
-
+ ". Shedding events. package=" + pkg);
-
mLastOverRateLogTime = now;
-
}
-
return;
-
}
-
}
-
-
int count = 0;
-
final int N = mNotificationList.size();
-
for (int i=0; i
<N; i++) {
-
final NotificationRecord r = mNotificationList.get(i);
-
if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
-
if (r.sbn.getId() == id && TextUtils.equals(r.sbn.getTag(), tag)) {
-
break; //如果是已存在的通知,是更新操作直接跳出
-
}
-
count++;
-
//当一个应用的通知数量大于限制数量值时报错,单应用最大数量为50
-
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
-
mUsageStats.registerOverCountQuota(pkg);
-
Slog.e(TAG, "Package has already posted " + count
-
+ " notifications. Not showing more. package=" + pkg);
-
return;
-
}
-
}
-
}
-
}
-
}
-
-
// 白名单的延迟意图
-
if (notification.allPendingIntents != null) {
-
final int intentCount = notification.allPendingIntents.size();
-
if (intentCount > 0) {
-
final ActivityManagerInternal am = LocalServices
-
.getService(ActivityManagerInternal.class);
-
final long duration = LocalServices.getService(
-
DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
-
for (int i = 0; i
< intentCount; i++) {
-
PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
-
if (pendingIntent != null) {
-
am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration);
-
}
-
}
-
}
-
}
-
-
// 过滤容错处理是否为优先级输入有问题
-
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
-
Notification.PRIORITY_MAX);
-
-
// 创建NotificationRecord
-
final NotificationRecord r = new NotificationRecord(getContext(), n);
-
//创建Runnable,后边操作在这个Runnable中
-
mHandler.post(new EnqueueNotificationRunnable(userId, r));
-
-
idOut[0] = id;
-
-
/// 过滤前边处理的特殊的通知,普通的通知不用考虑 @{
-
if (foundTarget) {
-
try {
-
Thread.sleep(1000);
-
} catch (InterruptedException exception) {
-
// ignore it.
-
}
-
}
-
/// @}
-
}
总结上边代码,主要有个过滤的特殊通知处理,创建了StatusBarNotification、NotificationRecord对象,并防止DOS攻击对单个应用的通知条数做了限制。
在这里想简单说一下NotificationRecord,因为他这里面有关于对排序有关priority属性的转变
NotificationRecord.java
这是NotificationRecord的构造方法其中后边用的比较多的是mRankingTimeMs和mImportance,而其中mImportance就是对Notification中priority的转变处理,接下来看一下方法
defaultImportance()
这里就是对Notification的priority的转换,还有一些情况比如flags和HIGH_PRIORITY为IMPORTANCE_MAX,通知fullScreenIntent不为空则也设置为IMPORTANCE_MAX
这样操作逻辑又到了这个EnqueueNotificationRunnable中,接着看一下这个内部类
-
private class EnqueueNotificationRunnable implements Runnable {
-
private final NotificationRecord r;
-
private final int userId;
-
-
EnqueueNotificationRunnable(int userId, NotificationRecord r) {
-
this.userId = userId;
-
this.r = r;
-
};
-
-
@Override
-
public void run() {
-
-
synchronized (mNotificationList) {
-
final StatusBarNotification n = r.sbn;
-
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
-
//根据key值获取Old NotificationRecord
-
NotificationRecord old = mNotificationsByKey.get(n.getKey());
-
if (old != null) {
-
// 获取ranking 信息从old NotificationRecord
-
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();
-
final boolean isSystemNotification = isUidSystem(callingUid) ||
-
("android".equals(pkg));
-
-
// Handle grouped notifications and bail out early if we
-
// can to avoid extracting signals.
-
handleGroupedNotificationLocked(r, old, callingUid, callingPid);
-
-
// This conditional is a dirty hack to limit the logging done on
-
// behalf of the download manager without affecting other apps.
-
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);
-
}
-
-
mRankingHelper.extractSignals(r);
-
-
final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
-
-
// blocked apps
-
if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
-
|| !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
-
if (!isSystemNotification) {
-
if (isPackageSuspended) {
-
Slog.e(TAG, "Suppressing notification from package due to package "
-
+ "suspended by administrator.");
-
mUsageStats.registerSuspendedByAdmin(r);
-
} else {
-
Slog.e(TAG, "Suppressing notification from package by user request.");
-
mUsageStats.registerBlocked(r);
-
}
-
return;
-
}
-
}
-
-
// tell the ranker service about the notification
-
if (mRankerServices.isEnabled()) {
-
mRankerServices.onNotificationEnqueued(r);
-
// TODO delay the code below here for 100ms or until there is an answer
-
}
-
-
-
int index = indexOfNotificationLocked(n.getKey());
-
if (index
< 0) {
-
mNotificationList.add(r);
-
mUsageStats.registerPostedByApp(r);
-
} else {
-
old = mNotificationList.get(index);
-
mNotificationList.set(index, r);
-
mUsageStats.registerUpdatedByApp(r, old);
-
// Make sure we don't lose the foreground service state.
-
notification.flags |=
-
old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
-
r.isUpdate = true;
-
}
-
-
mNotificationsByKey.put(n.getKey(), r);
-
-
// Ensure if this is a foreground service that the proper additional
-
// flags are set.
-
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-
notification.flags |= Notification.FLAG_ONGOING_EVENT
-
| Notification.FLAG_NO_CLEAR;
-
}
-
-
applyZenModeLocked(r);
-
//******排序操作*********//
-
mRankingHelper.sort(mNotificationList);
如上代码,我们这句代码实际为咱们文章的重心,对这些通知的一个排序操作
mRankingHelper.sort(mNotificationList)
代码如下RankingHelper.java 的sort方法
其实主要的是其中排序操作为如下代码,后边为对设置setGroup属性的分组处理。
Collections.sort(notificationList,mPreliminaryComparator);
其中是对通知数据列表设置一个比较器即mPreliminaryComparator进行排序操作,因此我们查看一下这个比较器
NotificationComparator.java
根据这个类可知他会对通知的属性mImportance属性进行比较(就是前边NotificationRecord通过Notification的priority的属性转变来的属性值),然后就是关于PackagePriority进行对比,然后就是通知设置的属性priority,然后就是ContactAffinity属性就行对比,然后就是对mRankingTimeMs属性进行比较这个是对设置的时间Notification的setWhen设置的值,层层比较,前边属性最重要,如果相等比对下面的属性,最后比较时间。
好了就这些