目录
- 前言
- NotificationListenerService方法集
- NotificationListenerService接收流程
- 通知消息发送流程
- NotificationListenerService注册
- 总结
前言
在上一篇通知服务NotificationListenerService使用方法 中,我们已经介绍了如何使用NotificationListenerService来监听消息通知,在最后我们还模拟了如何实现微信自动抢红包功能。
那么NotificationListenerService是如何实现系统通知监听的呢?(本篇源码分析基于API 32)
NotificationListenerService方法集
NotificationLisenerService是Service的子类
public abstract class NotificationListenerService extends Service
除了Service的方法属性外,NotificationListenerService还为我们提供了收到通知、通知被移除、连接到通知管理器等方法,如下图所示。
一般业务中我们只关注有标签的那四个方法即可。
NotificationListenerService接收流程
既然NotificationListenerService是继承自Service的,我们先来看它的onBind方法,代码如下所示。
public IBinder onBind(Intent intent) { | |
if (mWrapper == null) { | |
mWrapper = new NotificationListenerWrapper(); | |
} | |
return mWrapper; | |
} |
在onBind方法中返回了一个NotificationListenerWrapper实例,NotificationListenerWrapper对象是定义在NotificationListenerService中的一个内部类。主要方法如下所示。
/** @hide */ | |
protected class NotificationListenerWrapper extends INotificationListener.Stub { | |
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; | |
} | |
if (sbn == null) { | |
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification"); | |
return; | |
} | |
try { | |
// convert icon metadata to legacy format for older clients | |
createLegacyIconExtras(sbn.getNotification()); | |
maybePopulateRemoteViews(sbn.getNotification()); | |
maybePopulatePeople(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.arg = sbn; | |
args.arg = 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(); | |
} | |
} | |
...省略onNotificationRemoved等方法 | |
} |
NotificationListenerWrapper继承自INotificationListener.Stub,当我们看到Stub这一关键字的时候,就应该知道这里是使用AIDL实现了跨进程通信。
在NotificationListenerWrapper的onNotificationPosted中通过代码
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, | |
args).sendToTarget(); |
将消息发送出去,handler接受后,又调用NotificationListernerService的onNotificationPosted方法,进而实现通知消息的监听。代码如下所示。
private final class MyHandler extends Handler { | |
public static final int MSG_ON_NOTIFICATION_POSTED =; | |
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.arg; | |
RankingMap rankingMap = (RankingMap) args.arg; | |
args.recycle(); | |
onNotificationPosted(sbn, rankingMap); | |
} break; | |
... | |
} | |
} | |
} |
那么,消息通知发送时,又是如何与NotificationListenerWrapper通信的呢?
通知消息发送流程
当客户端发送一个通知的时候,会调用如下所示的代码
notificationManager.notify(, notification)
notify又会调用notifyAsUser方法,代码如下所示
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(); | |
} | |
} |
紧接着又会走到INotificationManager的enqueueNotificationWithTag方法中,enqueueNotificationWithTag是声明在INotificationManager.aidl文件中的接口
/** {@hide} */ | |
interface INotificationManager | |
{ | |
void cancelAllNotifications(String pkg, int userId); | |
... | |
void cancelToast(String pkg, IBinder token); | |
void finishToken(String pkg, IBinder token); | |
void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, | |
in Notification notification, int userId); | |
... | |
} |
这个接口是在NotificationManagerService中实现的,接着我们转到NotificationManagerService中去查看,相关主要代码如下所示。
final IBinder mService = new INotificationManager.Stub() { | |
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); | |
} | |
} |
enqueueNotificationWithTag方法会走进enqueueNotificationInternal方法,在方法最后会通过Handler发送一个EnqueueNotificationRunnable,代码如下所示。
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) { | |
... | |
//构造StatusBarNotification,用于分发监听服务 | |
final StatusBarNotification n = new StatusBarNotification( | |
pkg, opPkg, id, tag, notificationUid, callingPid, notification, | |
user, null, System.currentTimeMillis()); | |
// setup local book-keeping | |
String channelId = notification.getChannelId(); | |
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) { | |
channelId = (new Notification.TvExtender(notification)).getChannelId(); | |
} | |
... | |
// 设置intent的白名点,是否盛典、是否后台启动等 | |
if (notification.allPendingIntents != null) { | |
final int intentCount = notification.allPendingIntents.size(); | |
if (intentCount >) { | |
final long duration = LocalServices.getService( | |
DeviceIdleInternal.class).getNotificationAllowlistDuration(); | |
for (int i =; 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)); | |
} | |
} | |
} | |
} | |
... | |
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground)); | |
} |
EnqueueNotificationRunnable源码如下所示。
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; | |
} | |
public void run() { | |
synchronized (mNotificationLock) { | |
... | |
//将通知加入队列 | |
mEnqueuedNotifications.add(r); | |
scheduleTimeoutLocked(r); | |
... | |
if (mAssistants.isEnabled()) { | |
mAssistants.onNotificationEnqueuedLocked(r); | |
mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), | |
DELAY_FOR_ASSISTANT_TIME); | |
} else { | |
mHandler.post(new PostNotificationRunnable(r.getKey())); | |
} | |
} | |
} | |
} |
在EnqueueNotificationRunnable最后又会发送一个PostNotificationRunable,
PostNotificationRunable源码如下所示。
protected class PostNotificationRunnable implements Runnable { | |
private final String key; | |
PostNotificationRunnable(String key) { | |
this.key = key; | |
} | |
public void run() { | |
synchronized (mNotificationLock) { | |
try { | |
... | |
//发送通知 | |
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() { | |
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 { | |
//... | |
} | |
} finally { | |
... | |
} | |
} | |
} | |
} |
从代码中可以看出,PostNotificationRunable类中会调用notifyPostedLocked方法,这里你可能会有疑问:这里分明判断notification.getSmallIcon()是否为null,不为null时才会进入notifyPostedLocked方法。为什么这里直接默认了呢?这是因为在Android5.0中规定smallIcon不可为null,且NotificationListenerService仅适用于5.0以上,所以这里是必然会执行到notifyPostedLocked方法的。
其方法源码如下所示。
private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, | |
boolean notifyAllListeners) { | |
try { | |
// Lazily initialized snapshots of the notification. | |
StatusBarNotification sbn = r.getSbn(); | |
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; | |
TrimCache trimCache = new TrimCache(sbn); | |
//循环通知每个ManagedServiceInfo对象 | |
for (final ManagedServiceInfo info : getServices()) { | |
... | |
mHandler.post(() -> notifyPosted(info, sbnToPost, update)); | |
} | |
} catch (Exception e) { | |
Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e); | |
} | |
} |
notifyPostedLocked方法最终会调用notifyPosted方法,我们再来看notifyPosted方法。
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); | |
} | |
} |
notifyPosted方法,最终会调用INotificationListerner的onNotificationPosted方法,这样就通知到了NotificationListenerService的onNotificationPosted方法。
上述方法的流程图如下图所示。
NotificationListenerService注册
在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); | |
} |
registerAsSystemService方法将NotificationListenerWrapper对象注册到NotificationManagerService中。如此就实现了对系统通知的监听。
总结
NotificationListenerService实现对系统通知的监听可以概括为三步:
- NotificationListenerService将 NotificationListenerWrapper注册到NotificationManagerService中。
- 当有通知被发送时 ,NotificationManagerService跨进程通知到每个NotificationListenerWrapper。
- NotificationListenerWrapper中信息由NotificationListenerService类中的Handler中处理,从而调用NotificationListenerService中对应的回调方法。