基于Android 10 分析系统通知管理服务(NMS)
相关源码文件如下:
frameworks/base/core/java/android/app/
- NotificationManager.java
- Notification.java
- NotificationChannel.java
frameworks/base/core/java/android/service/notification/
- NotificationListenerService.java
frameworks/base/services/core/java/com/android/server/notification/
- NotificationManagerService.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/
- NotificationListener.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/
- NotificationEntryManager.java
一. 概述
Android应用除了WMS和AMS的管理服务之外,还有通知管理服务(NMS)也是非常重要的,通知是应用界面之外向用户显示的界面。
1.1 通知使用示例
1.1.1 创建Channel
val nm: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel("123", "TestChannel", NotificationManager.IMPORTANCE_HIGH)
nm.createNotificationChannel(channel)
}
1.1.2 创建通知
val notification: Notification = NotificationCompat.Builder(this, "123")
.setContentText("ContentText")
.setTicker("Ticker")
.setGroupSummary(true)
.setContentTitle("Hello")
.setContentInfo("ContentInfo")
.setSmallIcon(R.mipmap.ic_launcher)
.build()
1.1.3 发送通知
val nm: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(notifyId, notification)
1.1.4 取消通知
val nm: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.cancel(notifyId)
nm.cancelAll();
1.2 架构图
1.2.1 核心类图
1.2.2 通知处理流程图
二. 通知发送原理分析
由于分析是基于调用栈分析,所以建议读者按章节顺序阅读。
2.1 NM.notify
源码文件: /frameworks/base/core/java/android/app/NotificationManager.java
运行在三方App进程中
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());
}
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();
}
}
private Notification fixNotification(Notification notification) {
String pkg = mContext.getPackageName();
Notification.addFieldsFromContext(mContext, notification);
...
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);
}
}
notification.reduceImageSizes(mContext);
ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
boolean isLowRam = am.isLowRamDevice();
return Builder.maybeCloneStrippedForDelivery(notification, isLowRam, mContext);
}
在App进程中调用NotificationManager类的notify()方法,最终会通过Binder调用system_server进程中的NotifcationManagerService(简称NMS),执行enqueueNotificationWithTag方法。
2.2 NMS.enqueueNotificationWithTag
源码文件:/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
运行在system_server进程中
final IBinder mService = new INotificationManager.Stub() {
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, 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 incomingUserId) {
...
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);
...
try {
fixNotification(notification, pkg, userId);
} catch (NameNotFoundException e) {
Slog.e(TAG, "Cannot create a context for sending app", e);
return;
}
...
String channelId = notification.getChannelId();
final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
notificationUid, channelId, false );
if (channel == null) {
...
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 StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
...
}
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.sbn.getOverrideGroupKey() != null)) {
return;
}
...
mHandler.post(new EnqueueNotificationRunnable(userId, r));
}
这个过程主要是创建NotificationRecord对象保存Notification信息,最后通过Handler提交给EnqueueNotificationRunnable处理。
- key规则是“UserHandle#getIdentifier()|pkg|notifyId|tag|uid”
- mHandler是WorkerHandler的实例对象,在NMS启动后初始化。
2.3 EnqueueNotificationRunnable
源码文件:/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
运行在system_server进程中
protected 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 (mNotificationLock) {
mEnqueuedNotifications.add(r);
scheduleTimeoutLocked(r);
final StatusBarNotification n = r.sbn;
...
flagNotificationForBubbles(r, pkg, callingUid, old);
handleGroupedNotificationLocked(r, old, callingUid, callingPid);
...
if (mAssistants.isEnabled()) {
mAssistants.onNotificationEnqueuedLocked(r);
mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
DELAY_FOR_ASSISTANT_TIME);
} else {
mHandler.post(new PostNotificationRunnable(r.getKey()));
}
}
...
}
}
- 首先将NotificationRecord放入队列中待处理。
- 处理气泡通知和分组通知
- 最后通过Handler提交给PostNotificationRunnable处理,并且传递了key值,而非NotificationRecord对象。
2.4 PostNotificationRunnable
源码文件:/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
运行在system_server进程中
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;
}
...
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.sbn;
final Notification notification = n.getNotification();
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;
r.setTextChanged(isVisuallyInterruptive(old, r));
}
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);
if (!r.isHidden()) {
buzzBeepBlinkLocked(r);
}
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : 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 (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(r,
NotificationListenerService.REASON_ERROR, r.getStats());
mHandler.post(new Runnable() {
@Override
public void run() {
mGroupHelper.onNotificationRemoved(n);
}
});
}
}
maybeRecordInterruptionLocked(r);
} 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;
}
}
}
}
}
}
- 通过mEnqueuedNotifications 列表找出本次通知信息NotificationRecord。
- 通过mNotificationsByKey列表找出上一个通知信息。
- 通过mNotificationList列表找出当前key的通知信息,且确保前台服务状态不丢失。
- 前台服务单独添加两个Flag (FLAG_ONGOING_EVENT | FLAG_NO_CLEAR) 。
- 没有小图标的通知不显示
- 最后通知观察者
- 此mListeners为NotificationListeners对象,是NMS内部类并且在NMS初始化创建。
2.5 NotificationListeners.notifyPostedLocked
源码文件:/frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
运行在system_server进程中
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) {
StatusBarNotification sbn = r.sbn;
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
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(new Runnable() {
@Override
public void run() {
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(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, 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) {
Slog.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
}
- 此处的listener来自与ManagedServiceInfo的service成员变量。
- listener数据类型是NotificationListenerWrapper的代理对象
- sbnHolder的数据类型是StatusBarNotificationHolder,继承于IStatusBarNotificationHolder.Stub对象。
三. SysetmUI
3.1 NotificationListenerWrapper.onNotificationPosted
源码文件:/frameworks/base/core/java/android/service/notification/NotificationListenerService.java
运行在com.android.systemui进程中
protected class NotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
return;
}
synchronized (mLock) {
applyUpdateLocked(update);
if (sbn != null) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
} else {
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
mRankingMap).sendToTarget();
}
}
}
}
- 通过Binder向 system_server 读取sbn对象。
- 提交给Handler处理,此Handler运行在systemui进程的主线程中。
3.2 MyHandler
源码路径: /frameworks/base/core/java/android/service/notification/NotificationListenerService.java
运行在com.android.systemui进程中
private final class MyHandler extends Handler {
public static final int MSG_ON_NOTIFICATION_POSTED = 1;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
public void handleMessage(Message msg) {
if (!isConnected) {
return;
}
switch (msg.what) {
case MSG_ON_NOTIFICATION_POSTED: {
SomeArgs args = (SomeArgs) msg.obj;
StatusBarNotification sbn = (StatusBarNotification) args.arg1;
RankingMap rankingMap = (RankingMap) args.arg2;
args.recycle();
onNotificationPosted(sbn, rankingMap);
} break;
}
}
}
- 最后调用NotificationListenerService实例对象的onNotificationPosted()
- MyHandler为NotificationListenerService内部类
3.3 NotificationListener.onNotificationPosted
源码文件:/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
运行在com.android.systemui进程中
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
private final NotificationRemoteInputManager mRemoteInputManager =
Dependency.get(NotificationRemoteInputManager.class);
private final NotificationEntryManager mEntryManager =
Dependency.get(NotificationEntryManager.class);
private final NotificationGroupManager mGroupManager =
Dependency.get(NotificationGroupManager.class);
private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
private final Context mContext;
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
if (!ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
if (isUpdate) {
mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
} else {
mEntryManager.getNotificationData()
.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap);
} else {
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
}
- 最终通过 isUpdate 来区分是更新还是新增通知。
3.4 NotificationEntryManager.addNotification
源码文件:/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
运行在com.android.systemui进程中
public class NotificationEntryManager implements
Dumpable,
NotificationContentInflater.InflationCallback,
NotificationUpdateHandler,
VisualStabilityManager.Callback {
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
if (DEBUG) {
Log.d(TAG, "addNotification key=" + key);
}
mNotificationData.updateRanking(rankingMap);
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
NotificationEntry entry = new NotificationEntry(notification, ranking);
Dependency.get(LeakDetector.class).trackInstance(entry);
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
abortExistingInflation(key);
mPendingNotifications.put(key, entry);
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPendingEntryAdded(entry);
}
}
@Override
public void addNotification(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
}
四. 小结
源码分析中共涉及三个进程,分别是三方app进程、system_server进程、SystermUI进程,进程之间通过Binder通信。
持续更新…
|