笔者最近正在作一个项目,里面须要用到 Android Notification 机制来实现某些特定需求。我正好经过这个机会研究一下 Android Notification 相关的发送逻辑和接收逻辑,以及整理相关的笔记。我研究 Notification 机制的目的是解决如下我在使用过程当中所思考的问题:java
Notification,中文名翻译为通知,每一个 app 能够自定义通知的样式和内容等,它会显示在系统的通知栏等区域。用户能够打开抽屉式通知栏查看通知的详细信息。在实际生活中,Android Notification 机制有很普遍的应用,例如 IM app 的新消息通知,资讯 app 的新闻推送等等。android
本文的源码基于 Android 7.0。windows
通常来讲,若是咱们本身的 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 发送出去。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, 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 当中。框架
@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);
}
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);
}
checkCallerIsSystemOrSameApp(pkg);
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
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);
}
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
user);
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
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; // Allow updating existing notification
}
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
mUsageStats.registerOverCountQuota(pkg);
Slog.e(TAG, "Package has already posted " + count
+ " notifications. Not showing more. package=" + pkg);
return;
}
}
}
}
}
// Whitelist pending intents.
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);
}
}
}
}
// Sanitize inputs
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
Notification.PRIORITY_MAX);
// setup local book-keeping
final NotificationRecord r = new NotificationRecord(getContext(), n);
mHandler.post(new EnqueueNotificationRunnable(userId, r));
idOut[0] = id;
}
复制代码
这里代码比较多,但经过注释能够清晰地理清整个逻辑:异步
这里有一个点,就是 mHandler,涉及到切换线程,咱们先跟踪一下 mHandler 是在哪一个线程被建立。ide
mHandler 是 WorkerHandler 类的一个实例,在 NotificationManagerService#onStart() 方法中被建立,而 NotificationManagerService 是系统 Service,因此 EnqueueNotificationRunnable 的 run 方法会运行在 system_server 的主线程。oop
@Override
public void run() {
synchronized(mNotificationList) {
// 省略代码
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(n);
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
// notifications
Slog.e(TAG, "WARNING: In a future release this will crash the app: " + n.getPackageName());
}
buzzBeepBlinkLocked(r);
}
}
复制代码
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
// Lazily initialized snapshots of the notification.
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info: mServices) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
// This notification hasn't been and still isn't visible -> ignore.
if (!oldSbnVisible && !sbnVisible) {
continue;
}
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
// This notification became invisible -> remove the old one.
if (oldSbnVisible && !sbnVisible) {
final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
mHandler.post(new Runnable() {
@Override
public void run() {
notifyRemoved(info, oldSbnLightClone, update);
}
});
continue;
}
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) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
复制代码
调用到最后会执行 listener.onNotificationPosted() 方法。经过全局搜索得知,listener 类型是 NotificationListenerService.NotificationListenerWrapper 的代理对象。源码分析
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;
}
try {
// convert icon metadata to legacy format for older clients
createLegacyIconExtras(sbn.getNotification());
maybePopulateRemoteViews(sbn.getNotification());
} catch (IllegalArgumentException e) {
// warn and drop corrupt notification
Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + sbn.getPackageName());
sbn = null;
}
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
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 {
// still pass along the ranking map, it may contain other information
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, mRankingMap).sendToTarget();
}
}
}
复制代码
这里在一开始会从 sbnHolder 中获取到 sbn 对象,sbn 隶属于 StatusBarNotificationHolder 类,继承于 IStatusBarNotificationHolder.Stub 对象。注意到这里捕获了一个 RemoteException,猜想涉及到跨进程调用,但咱们不知道这段代码是在哪一个进程中执行的,因此在这里暂停跟踪代码。
笔者以前是经过向系统发送通知的方式跟踪源码,发现走不通。故我的尝试从另外一个角度入手,即系统接收咱们发过来的通知并显示到通知栏这个方式入手跟踪代码。
系统显示 Notification 的过程,猜想是在 PhoneStatusBar.java 中,由于系统启动的过程当中,会启动 SystemUI 进程,初始化整个 Android 显示的界面,包括系统通知栏。
public void start() {
// 省略代码
// Set up the initial notification state.
try {
mNotificationListener.registerAsSystemService(mContext,
new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
// 省略代码
}
复制代码
这段代码中,会调用 NotificationListenerService#registerAsSystemService() 方法,涉及到咱们以前跟踪代码的类。咱们继续跟进去看一下。
public void registerAsSystemService(Context context, ComponentName componentName, int currentUser) throws RemoteException {
if (mWrapper == null) {
mWrapper = new NotificationListenerWrapper();
}
mSystemContext = context;
INotificationManager noMan = getNotificationInterface();
mHandler = new MyHandler(context.getMainLooper());
mCurrentUser = currentUser;
noMan.registerListener(mWrapper, componentName, currentUser);
}
复制代码
这里会初始化一个 NotificationListenerWrapper 和 mHandler。因为这是在 SystemUI 进程中去调用此方法将 NotificationListenerService 注册为系统服务,因此在前面分析的那里:NotificationListenerService.NotificationListenerWrapper#onNotificationPosted(),这段代码是运行在 SystemUI 进程,而 mHandler 则是运行在 SystemUI 主线程上的 Handler。因此,onNotificationPosted() 是运行在 SystemUI 进程中,它经过 sbn 从 system_server 进程中获取到 sbn 对象。下一步是经过 mHandler 处理消息,查看 NotificationListenerService.MyHandler#handleMessage() 方法,得知当 message.what 为 MSG_ON_NOTIFICATION_POSTED 时,调用的是 onNotificationPosted() 方法。
可是,NotificationListenerService 是一个抽象类,onNotificationPosted() 为空方法,真正的实现是它的实例类。
观察到以前 BaseStatusBar#start() 中,是调用了 mNotificationListener.registerAsSystemService() 方法。那么,mNotificationListener 是在哪里进行初始化呢?
private final NotificationListenerService mNotificationListener = new NotificationListenerService() {
// 省略代码
@Override
public void onNotificationPosted(final StatusBarNotification sbn, final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null) {
mHandler.post(new Runnable() {
@Override
public void run() {
processForRemoteInput(sbn.getNotification());
String key = sbn.getKey();
mKeysKeptForRemoteInput.remove(key);
boolean isUpdate = mNotificationData.get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since we're not going to show them
// anyway. This is true also when the summary is canceled,
// because children are automatically canceled by NoMan in that case.
if (!ENABLE_CHILD_NOTIFICATIONS && mGroupManager.isChildInGroupWithSummary(sbn)) {
if (DEBUG) {
Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
}
// Remove existing notification to avoid stale data.
if (isUpdate) {
removeNotification(key, rankingMap);
} else {
mNotificationData.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap, null /* oldEntry */ );
}
}
});
}
}
// 省略代码
}
复制代码
@Override
public void addNotification(StatusBarNotification notification, RankingMap ranking, Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
mNotificationData.updateRanking(ranking);
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
boolean isHeadsUped = shouldPeek(shadeEntry);
if (isHeadsUped) {
mHeadsUpManager.showNotification(shadeEntry);
// Mark as seen immediately
setNotificationShown(notification);
}
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(notification.getKey())) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + notification.getKey());
}
} else if (mNotificationData.getImportance(notification.getKey()) < NotificationListenerService.Ranking.IMPORTANCE_MAX) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: not important enough: " + notification.getKey());
}
} else {
// Stop screensaver if the notification has a full-screen intent.
// (like an incoming phone call)
awakenDreams();
// not immersive & a full-screen alert should be shown
if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, notification.getKey());
notification.getNotification().fullScreenIntent.send();
shadeEntry.notifyFullScreenIntentLaunched();
MetricsLogger.count(mContext, "note_fullscreen", 1);
} catch (PendingIntent.CanceledException e) {}
}
}
// !!!
addNotificationViews(shadeEntry, ranking);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
复制代码
在这个方法中,最关键的方法是最后的 addNotificationViews() 方法。调用这个方法以后,你建立的 Notification 才会被添加到系统通知栏上。
跟踪完整个过程当中,以前提到的问题也能够一一解决了:
A:首先,咱们在 app 进程建立 Notification 实例,经过跨进程调用,传递到 system_server 进程的 NotificationManagerService 中进行处理,通过两次异步调用,最后传递给在 NotificationManagerService 中已经注册的 NotificationListenerWrapper。而 android 系统在初始化 systemui 进程的时候,会往 NotificationManagerService 中注册监听器(这里指的就是 NotificationListenerWrapper)。这种实现方法就是基于咱们熟悉的一种设计模式:监听者模式。
A:上面提到,因为初始化的时候已经往 NotificationManagerService 注册监听器,因此系统 SystemUI 进程会接收到 Notification 实例以后通过进一步解析,而后构造出 Notification Views 并最终显示在系统通知栏上。
A:经过上面的流程,我我的认为能够经过 Xposed 等框架去 hook 其中几个重要的方法去捕获 Notification 实例,例如 hook NotificationManager#notify() 方法去获取 Notification 实例。
这篇文章会同步到个人我的日志,若有问题,请你们踊跃提出,谢谢你们!