SystemUI 开发之如何监听到通知的(五)
在 《SystemUI 开发之通知的实现逻辑(四)》一文中曾提到过 SystemUI 是通过 NotificationListenerService 来实现监听的,本文将回答以下问题:
NotificationManagerService与NotificationListenerService关系是什么?- 它们在 framework 中是如何工作的?
以下分析基于 Android 11 源码 cs.android.com/android/pla…
在 Android 系统中,通知系统是一个典型的生产者-消费者模型。NotificationManagerService (NMS) 负责管理与分发,而 NotificationListenerService (NLS) 负责监听与呈现。
0x00. NotificationManagerService 启动
Android 系统在启动时通过 SystemServer.startOtherServices() 方法通过SystemServiceManager 启动了 NotificationManagerService 。
/**
* Starts a miscellaneous grab bag of stuff that has yet to be refactored and organized.
*/
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
...
t.traceBegin("StartNotificationManager");
mSystemServiceManager.startService(NotificationManagerService.class);
SystemNotificationChannels.removeDeprecated(context);
SystemNotificationChannels.createAll(context);
notification = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
t.traceEnd();
...
}
SystemServiceManager.startService() 方法中通过反射获取到系统服务的实例,并执行了系统服务类的 onStart() 方法
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);
// Create the service.
if (!SystemService.class.isAssignableFrom(serviceClass)) {
throw new RuntimeException("Failed to create " + name
+ ": service must extend " + SystemService.class.getName());
}
final T service;
try {
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} ...
startService(service);
return service;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
}
public void startService(@NonNull final SystemService service) {
// Register it.
mServices.add(service);
// Start it.
long time = SystemClock.elapsedRealtime();
try {
// 执行服务的onStart方法
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + service.getClass().getName()
+ ": onStart threw an exception", ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
}
NotificationManagerService.onStart() 中将服务进行注册到 ServiceManager 。
public void onStart() {
...
// 将NMS注册到服务中心中进行管理
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
publishLocalService(NotificationManagerInternal.class, mInternalService);
}
至此就可以通过以下方式获取 NotificationManagerService 的对象了。
// 这个方法在内部会判断如果是同一个进程则会直接返回当前进程中的Stub对象,否则会返回Proxy对象
INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE))
NMS启动后,SystemServer 内部还紧接着通过 INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE))获取一个 Binder 代理。
目的:虽然在同进程,但使用代理是为了确保初始化时序的安全性,并方便将该接口注入到后续启动的 StatusBarManagerService 或 WindowManagerService 中,维持架构的解耦和一致性。
0x01. SystemUI 中是如何启动注册 NotificationListenerService 的?
与普通应用不同,SystemUI 的通知监听器采用的是动态注册机制,而非单纯依靠 Manifest 的静态声明。
- 初始化:SystemUI 启动时,通过 Dagger 注入并初始化
com.android.systemui.statusbar.NotificationListener类。 - 注册入口
StatusBar.start()
在 StatusBar.start() 中通过 mNotificationsController.initialize() 方法执行了
notificationListener.registerAsSystemService() 方法将 NotificationListenerService 注册到 NMS 中的。
NotificationListenerService.registerAsSystemService()
public void registerAsSystemService() {
try {
// 这个方法是 NotificationListenerService 的内部方法
registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
}
该方法是 NotificationListenerService 为系统组件提供的特权接口。它会调用 AIDL 接口 INotificationManager.registerListener,将 SystemUI 的代理对象(Stub)直接传递给 NMS。
@SystemApi
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
if (mWrapper == null) {
mWrapper = new NotificationListenerWrapper();
}
mSystemContext = context;
// 通过 NotificationManagerService 的 Binder 的客户端代理对象把NLS注册进去
INotificationManager noMan = getNotificationInterface();
mHandler = new MyHandler(context.getMainLooper());
mCurrentUser = currentUser;
// 注意这里把 mWrapper 这个Binder对象传给了 NMS,这个类实现了INotification.Stub接口,
// 此时NMS就可以拿到 SystemUI 中“句柄”
noMan.registerListener(mWrapper, componentName, currentUser);
}
// 获取NMS的代理对象
protected final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
return mNoMan;
}
// NMS 通过这个回调接口“告诉”SystemUI 通知的添加、删除、更新等操作
protected class NotificationListenerWrapper extends INotificationListener.Stub {
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
// 通知到达
//...代码省略
}
public void onNotificationRemoved(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update, NotificationStats stats, int reason) {
// 通知移除
//...代码省略
}
public void onNotificationRankingUpdate(NotificationRankingUpdate update)
throws RemoteException {
// 排序更新
//...代码省略
}
...
}
NotificationListenerWrapper 是一个 Binder 对象,它是 NotificationListenerService 内部实现了 Stub 定义的回调接口,它的作用是传递给NMS后,后续会有通过这个它来“告诉”SystemUI:“通知来了”。
0x02. NotificationListenerService 内部是如何与 NotificationManagerService 交互的?
实际上在 SystemUI 中 是使用 NotificationListener 类扩展 NotificationListenerService ,NMS 在上一步注册也拿到了NLS的 Binder 对象,而 NLS 也会获取 NMS 的代理对象,它们之间的关系如下
两者的交互建立在双向 Binder IPC 基础之上。
-
下行:数据感知(
NMS -> NLS)NLS内部持有一个NotificationListenerWrapper(实现INotificationListener.Stub)。当 NMS 侧有通知增删改时,会通过这个 Stub 回调 NLS 的onNotificationPosted()或onNotificationRemoved()。 -
上行:操作反馈(
NLS -> NMS)NLS通过getNotificationInterface()获取INotificationManager的 Binder 代理。当用户在 SystemUI 上滑动删除一条通知时,SystemUI 会通过该代理调用 NMS 的cancelNotificationFromListener -
数据媒介
RankingMap为了优化性能,
NMS不会频繁传递全量数据。它通过RankingMap告知NLS通知的优先级、分组、可见性等元数据的变更,NLS收到后在本地进行 UI 排序。
0x03. NotificationManagerService 是如何工作的?
NMS 运行在 system_server 进程中,是通知系统的中枢。
1. NMS 如何收到各个应用的通知?
应用进程调用 NotificationManager.notify(),最终通过 Binder 跨进程调用 NMS。
-
入口点:在
NMS中,最核心的入口是enqueueNotificationInternal。 -
封包转化:NMS 首先将应用传来的数据转化为
NotificationRecord对象。这是一个承载通知所有生命周期的“重型”对象。 -
流量控制(限流):
static final int MAX_PACKAGE_NOTIFICATIONS = 50; // 单个应用最多 50 条通知 static final float DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE = 5f; // 每秒最多入队 5 条NMS 会检查
mMaxPackageEnqueueRate,如果应用发送频率过快,会抛出异常或丢弃通知,防止恶意应用撑爆系统内存。 -
异步处理:NMS 接收到请求后,不会在 Binder 线程同步处理,而是通过
Handler发送一个EnqueueNotificationRunnable到工作线程,以保证系统服务的响应性。
2. NMS 是如何管理这些通知的?
管理的核心在于内存缓存和生命周期追踪。NMS 使用了一系列受锁保护的数据结构:
-
核心存储容器:
@GuardedBy("mNotificationLock") final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>(); // 当前显示的所有通知列表 @GuardedBy("mNotificationLock") final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>(); // 以 Key 索引,方便快速查找 @GuardedBy("mNotificationLock") final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>(); // 正在排队等待处理的通知 -
分类管理:
- Group(分组)管理:使用
mSummaryByGroupKey。如果应用设置了 GroupKey,NMS 会自动处理聚合逻辑,甚至在满足一定条件时(代码中的mAutoGroupAtCount)自动进行 “Auto-bundle” 归类。 - Archive(归档):代码中的
Archive类负责记录已消失的通知历史,供“通知历史”功能调用。它使用了轻量级拷贝sbn.cloneLight(),防止历史记录占用过多内存。
- Group(分组)管理:使用
-
状态维护:
NMS通过mNotificationLock对象同步所有操作,确保在多线程环境下(如:应用入队通知的同时,用户在侧滑删除通知)数据的一致性。
3. 如何给这些通知排序的?
排序是通过 RankingHelper 和专门的 RankingThread 实现。
RankingHelper 采用了双次排序法
public void sort(ArrayList<NotificationRecord> notificationList) {
final int N = notificationList.size();
// clear global sort keys
for (int i = N - 1; i >= 0; i--) {
notificationList.get(i).setGlobalSortKey(null);
}
// rank each record individually
// 第一次排序:先使用 mPreliminaryComparator 对所有原始通知进行一次基础排序。
Collections.sort(notificationList, mPreliminaryComparator);
synchronized (mProxyByGroupTmp) {
// record individual ranking result and nominate proxies for each group
for (int i = 0; i < N; i++) {
final NotificationRecord record = notificationList.get(i);
record.setAuthoritativeRank(i);
final String groupKey = record.getGroupKey();
NotificationRecord existingProxy = mProxyByGroupTmp.get(groupKey);
if (existingProxy == null) {
mProxyByGroupTmp.put(groupKey, record);
}
}
// assign global sort key:
// is_recently_intrusive:group_rank:is_group_summary:group_sort_key:rank
// 构造全局排序键
for (int i = 0; i < N; i++) {
final NotificationRecord record = notificationList.get(i);
NotificationRecord groupProxy = mProxyByGroupTmp.get(record.getGroupKey());
String groupSortKey = record.getNotification().getSortKey();
// We need to make sure the developer provided group sort key (gsk) is handled
// correctly:
// gsk="" < gsk=non-null-string < gsk=null
//
// We enforce this by using different prefixes for these three cases.
String groupSortKeyPortion;
if (groupSortKey == null) {
groupSortKeyPortion = "nsk";
} else if (groupSortKey.equals("")) {
groupSortKeyPortion = "esk";
} else {
groupSortKeyPortion = "gsk=" + groupSortKey;
}
boolean isGroupSummary = record.getNotification().isGroupSummary();
record.setGlobalSortKey(
String.format("crtcl=0x%04x:intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x",
record.getCriticality(),
record.isRecentlyIntrusive()
&& record.getImportance() > NotificationManager.IMPORTANCE_MIN
? '0' : '1',
groupProxy.getAuthoritativeRank(),
isGroupSummary ? '0' : '1',
groupSortKeyPortion,
record.getAuthoritativeRank()));
}
mProxyByGroupTmp.clear();
}
// Do a second ranking pass, using group proxies
// 第二次排序:终极排序
Collections.sort(notificationList, mFinalComparator);
}
-
RankingHelper的作用是排序逻辑的封装。它负责根据以下维度计算分数:
- Importance(重要性):如
IMPORTANCE_LOW、IMPORTANCE_MIN等。 - ZenMode(禅定模式/勿扰):
mInterruptionFilter决定了哪些通知该“闭嘴”。 - 用户偏好:用户是否手动置顶了某个频道。
- 会话属性:Android 11 强化的
mMsgPkgsAllowedAsConvos(允许作为会话的包名),对话类通知通常有更高的权重。
- Importance(重要性):如
-
异步排序机制:
排序是消耗 CPU 的操作。
NMS维护了一个名为 "ranker" 的HandlerThread。private final HandlerThread mRankingThread = new HandlerThread("ranker", Process.THREAD_PRIORITY_BACKGROUND);当通知状态改变时,会发送
MESSAGE_RANKING_SORT消息。排序完成后,通过MESSAGE_SEND_RANKING_UPDATE消息通知 SystemUI。 -
通知分发给 SystemUI:
排序结果封装在
NotificationRankingUpdate中,通过mListeners(通知监听器管理器)分发给 SystemUI 进程。
引用