首页 > 其他分享 >深入理解通知服务NotificationListenerService原理

深入理解通知服务NotificationListenerService原理

时间:2022-11-20 15:37:10浏览次数:71  
标签:String notification final 深入 原理 null sbn NotificationListenerService


前言

在上一篇​​通知服务NotificationListenerService使用方法​​ 中,我们已经介绍了如何使用NotificationListenerService来监听消息通知,在最后我们还模拟了如何实现微信自动抢红包功能。

那么NotificationListenerService是如何实现系统通知监听的呢?(本篇源码分析基于API 32)

NotificationListenerService方法集

NotificationLisenerService是Service的子类

public abstract class NotificationListenerService extends Service

除了Service的方法属性外,NotificationListenerService还为我们提供了收到通知、通知被移除、连接到通知管理器等方法,如下图所示。

深入理解通知服务NotificationListenerService原理_ide

一般业务中我们只关注有标签的那四个方法即可。

NotificationListenerService接收流程

既然NotificationListenerService是继承自Service的,我们先来看它的onBind方法,代码如下所示。

@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new NotificationListenerWrapper();
}
return mWrapper;
}

在onBind方法中返回了一个NotificationListenerWrapper实例,NotificationListenerWrapper对象是定义在NotificationListenerService中的一个内部类。主要方法如下所示。

/** @hide */
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;
}
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.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();
}
}
...省略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 = 1;

@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;

...
}
}
}

 那么,消息通知发送时,又是如何与NotificationListenerWrapper通信的呢?

通知消息发送流程

当客户端发送一个通知的时候,会调用如下所示的代码

notificationManager.notify(1, 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
{
@UnsupportedAppUsage
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中去查看,相关主要代码如下所示。

@VisibleForTesting
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);
}
}

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 > 0) {
final long duration = LocalServices.getService(
DeviceIdleInternal.class).getNotificationAllowlistDuration();
for (int i = 0; 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;
}

@Override
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;
}

@Override
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() {
@Override
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原理_通知_02

NotificationListenerService注册

在NotificationListenerService中通过registerAsSystemService方法注册服务,代码如下所示。

@SystemApi
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中对应的回调方法。

标签:String,notification,final,深入,原理,null,sbn,NotificationListenerService
From: https://blog.51cto.com/u_15477127/5871513

相关文章

  • 关于编辑器QScintilla(Scintilla)词法分析器工作原理的分析(实现注释区分)
    入门,首先看我这两篇博客:关于QScintilla库的入门大全正式开始,先来看看词法分析器和编辑器的关系:(注意:如果自己重写一个词法分析器,那么用的是继承另一个类 QsciLexerCustom ......
  • Qt基础之四:Qt信号与槽机制原理及优缺点
    信号和插槽用于对象之间的通信。信号和槽机制是Qt的核心特性,可能也是与其他框架提供的特性最大不同的部分。Qt的元对象系统使信号和槽成为可能。一.简介在GUI编程中,当我们改......
  • 深入解析CSS读书笔记·第1、2章
    层叠、优先级和继承层叠及优先级CSS本质是在声明规则,而层叠是为了解决规则之间冲突,优先级解决冲突的工具。层叠优先级的判断顺序是:来源->选择器->顺序。来源指样式的来......
  • Base64 编码原理 && 实现
    在众多的商用APP中,混杂着多种算法去实现接口的参数的加密,以及各种位置的鉴权操作。即使厂商把一系列算法做了混合的使用,我们也不用去惧怕,接下来的几章中,笔者将会带领大家把......
  • 【Spring系列】- Bean生命周期底层原理
    Bean生命周期底层原理......
  • Kubernetes(k8s)CNI(Calico)网络模型原理
    目录一、概述二、Calico架构和核心组件三、什么是BGP?三、Calico两种网络模式1)IPIP模式2)BGP模式四、安装Calico插件1)通过helm安装Calico2)通过yaml文件安装3)k8sflannel......
  • 常用外设原理介绍
    LEDLED灯,又称LED发光二极管,原理图如下所示LED发光二极管有P、N两极,具有普通二极管的单向导电性,当P极电压高于N极电压,且达到一定阈值之后(一般为0.7V,与其材质有关),二极管开始导......
  • Java——集合——Collection集合——Iterator接口介绍&迭代器的代码实现&迭代器的实现
                                                       ......
  • 深入理解Java引用类型和值调用
    目录引言数据类型分类基本类型引用类型引用分类数据存在哪基本类型与引用类型的区别值传递引用对象传递之例外nullReference引言  什么是数据类型?在计算机科学和计算......
  • 路由原理hash和history
    hash和history都是运用于前后端项目分离的要清楚两者的区别以及两者各自的使用场景,还有各自的使用特点和优缺点hash是由#后面拼接的真实url路径history是h5新增的特性......