A service that receives calls from the system when new notifications are posted or removed, or their ranking changed.
官方文档有这么一句话,告诉我们NotificationListenerService可以用来监听到通知的发送以及移除和排名位置变化。那么如果我们注册了这个服务,当系统任何一条通知到来或者被移除掉,我们都能通过这个service来监听到,甚至可以做一些管理工作。
[TOC]
如何使用?
既然是系统提供的服务,那么申请权限以及在配置文件中声明是基本的套路。在AndroidManifest中需要加入以下代码:1
2
3
4
5
6
7<service android:name=".NotificationListener"
android:label="@string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
这样就声明了我可以拥有处理notification的能力了。但是系统怎么知道你能处理呢?显然,你得向系统申请权限,系统会记录下一些信息,当通知到来或有被移除的时候,会通知所有有权限的应用这条新来的通知对象。
那么当你申请权限的时候,系统到底做了啥事儿呢?
原理分析
当你首次使用的时候,需要去设置页面开启一个权限,这个页面叫做Notification access,会列出所有申请了这个权限的应用供你选择,勾选了这个应用后,那么对应的上面注册的NotificationListener就会被启动起来。
当我们使用NotificationListenerService的时候,我们只需要继承自它,暴露给我们两个抽象方法:
onNotificationPosted(StatusBarNotification sbn)
onNotificationRemoved(StatusBarNotification sbn)
当系统收到一条通知的时候,onNotificationPosted会回调,当从通知栏移除一条通知时,onNotificationPosted会回调。
注意到StatusBarNotification这个对象,是系统对notification对象的重新包装,携带了一些基本信息,我们可以拿着这个对象干很多有意思的事情,稍后介绍。
从实现上来看,NotificationListenerService继承自系统的Service,那么这个服务是何时,何地,被谁启动的呢?
这里我们不妨猜想一下:
- 1.如果我们已经在设置中勾选了应用,必然会触发系统会更新一些设置,如果你有一些android开发经验,可能知道这种更新往往是通过contenprovider更新了一张系统数据表,告诉系统我有这个权限了;
- 2.如果已经勾选过,重新开机系统在准备的时候,应该会知道有谁已经有了这个权限,到时候要为这个应用服务;
- 3.如果卸载了有权限的应用,应该会告诉系统从权限列表移除数据,显然这里肯定有谁在监听一个广播;
那么,到底是谁在做这件事儿呢?我们先来分析一下NotificationListenerService的实现。
查看SDK源码,当绑定服务后,返回的IBinder对象如下:
1 | @Override |
注意到返回的这个binder对象其实是INotificationListener.Stub,可以看到onNotificationPosted方法是会被INotificationListener对象调用的,一定有一个客户端得到这个INotificationListener对象,当通知来的时候回调到NotificationListenerService.this.onNotificationPosted(sbn)方法,就是我们继承NotificationListenerService后需要实现的方法。注意到源码中还有一个方法:1
2
3
4
5
6
7private final INotificationManager getNotificationInterface() {
if (mNoMan == null) {
mNoMan = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
return mNoMan;
}
这个mNoman是一个INotificationManager对象,并且是通过ServiceManager注册的,有经验的同学应该知道了,类似这种都能在系统中找到对应的服务,所有NotificationManagerService就呼之欲出了,我们来分析看看它的源码。
NotificationmanagerService中的相关源码分析
前面我们猜想了可能启动NotificationListenerService的时机,事实上在NotificationmanagerService源码中我们都能找到具体实现。
- 开机启动
在系统开机的时候,NotificationmanagerService在初始化后,会调用到systemReady()方法,如下所示:1
2
3
4
5
6
7
8void systemReady() {
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
// no beeping until we're basically done booting
mSystemReady = true;
// make sure our listener services are properly bound
rebindListenerServices();
}
首先会拿到audioservice对象,因为有些通知可以携带声音信息,然后调用rebindListenerServices这个关键方法。
1 | void rebindListenerServices() { |
可以看到,首先从secure表中查处哪些应用注册了ENABLED_NOTIFICATION_LISTENERS服务,添加到toAdd列表中,然后把当前已经注册的mListeners全部移除掉,调用 unregisterListenerService(component, info.userid)移除之后,重新注册toAdd列表中的服务,调用registerListenerService(component, currentUser)。可以猜到这两个方法就是去bind或者unbind我们自己实现的NotificationListenerService了。
通过secure表查询到的字段,我们可以通过RE浏览器看到里面的数据,在com.android.provider.setting中找到secure表。
接下来在registerListenerservice中有如下实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30....
try {
if (DBG) Slog.v(TAG, "binding: " + intent);
if (!mContext.bindServiceAsUser(intent,
new ServiceConnection() {
INotificationListener mListener;
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mNotificationList) {
mServicesBinding.remove(servicesBindingTag);
try {
mListener = INotificationListener.Stub.asInterface(service);
NotificationListenerInfo info = new NotificationListenerInfo(
mListener, name, userid, this);
service.linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
// already dead
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Slog.v(TAG, "notification listener connection lost: " + name);
}
},
Context.BIND_AUTO_CREATE,
new UserHandle(userid)))
...
这部分代码大家应该比较熟悉了,跟平时用AIDL服务一样,我们注册的NotificationListenerService服务在这里通过INotificationListener.Stub.asInterface(service);
转换成需要的INotificationListener对象,将它加入到mListeners集合中,然后就可以通过这个对象回调到我们自己注册的服务代码中了。
用户在设置中开启权限的时候
这种情况下,由于NotificationManagerService中使用ContentObserver
监听了SettingProvider
的变化,当secure数据表发生变化,也会调用rebindListenerServices重新绑定服务。如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34class SettingsObserver extends ContentObserver {
private final Uri NOTIFICATION_LIGHT_PULSE_URI
= Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
= Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
SettingsObserver(Handler handler) {
super(handler);
}
void observe() {
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
false, this, UserHandle.USER_ALL);
resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
false, this, UserHandle.USER_ALL);
update(null);
}
@Override public void onChange(boolean selfChange, Uri uri) {
update(uri);
}
public void update(Uri uri) {
ContentResolver resolver = mContext.getContentResolver();
if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
boolean pulseEnabled = Settings.System.getInt(resolver,
Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
if (mNotificationPulseEnabled != pulseEnabled) {
mNotificationPulseEnabled = pulseEnabled;
updateNotificationPulse();
}
}
if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
rebindListenerServices();
}
}
}通过广播监听
当监听到注册了NotificationListenerService的应用被卸载时,同样会调用rebindListenerServices重新绑定服务,之后的流程跟前面分析一样。这部分代码较长,由于篇幅原因就不贴出来了,大家可以自行查看源码。
上面就是NotificationListenerService的启动情况。既然服务已经启动了,那么当有通知到来或者删除的时候,怎么告诉注册的这些应用呢?前面分析到,注册的服务都会被添加到一个mListeners的集合中,我们猜想当通知到来的时候,NotificationListenerService肯定会遍历整个集合,然后调用listener的onNotificationPosted(sbnf)方法。平时我们在发送通知的时候,是通过NotificationManager对象调用notify发送,其实最终都是通过NotificationManagerService来完成的,我们看源码,在NotificationManager中:1
2
3
4
5
6
7
8
9
10public void notify(String tag, int id, Notification notification)
{
...
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
notification, idOut, UserHandle.myUserId());
...
} catch (RemoteException e) {
}
}
这里的service对象显然是NotificationManagerService,调用其enqueueNotificationWithTag发送一条通知,接下来辗转调用到notifyPostedLocked(),这里就符合了我们上面的猜想:1
2
3
4
5
6
7
8
9
10
11private void notifyPostedLocked(NotificationRecord n) {
// make a copy in case changes are made to the underlying Notification object
final StatusBarNotification sbn = n.sbn.clone();
for (final NotificationListenerInfo info : mListeners) {
mHandler.post(new Runnable() {
@Override
public void run() {
info.notifyPostedIfUserMatch(sbn);
}});
}
}
可以看到这里遍历mListeners集合,调用notifyPostedIfUserMatch方法,到这里我们可以知道离我们想要看到的方法已经不远了。1
2
3
4
5
6
7
8
9
10public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
if (!enabledAndUserMatches(sbn)) {
return;
}
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
终于,我们熟悉的方法看到了,这不就是我们自己注册服务中的方法吗?这样整个调用链就清晰了。
这里我们可以发现一个有意思的地方,如果我们注册了这个服务,当系统有通知到来的时候,会回调这个服务。如果有些应用有常驻进程,当由于某些原因常驻进程挂掉了,这是不是有一定机会系统帮我们唤醒了进程呢?
另外的删除通知类似,这里就不再分析了。
现在总结一下整个流程:
从整体上来看,NotificationListenerService相当于一个本地代理,真正起到控制作用的是NotificationManagerService。通过三种方式注册到NotificationManagerService中的listeners会保存在一个集合中,当通知到来的时候,会遍历整个集合回调listener中的方法。
取消通知
NotificationListenerService还提供了一个方法,可以取消到收到的通知,如果用户使用了这个服务,可以对收到的通知取消,那么在通知栏上我们就看不到这条通知了。这对于有洁癖的用户来说,这也许是一个好方法。
- cancelNotification(String pkg, String tag, int id)
- cancelAllNotifications()
- cancelNotification(String key) 5.0
看名字知道这两个方法的作用。5.0以后,第一个方法过期了,所以这个方法取消不了5.0版本以后发送的通知,第三个方法是5.0提供的方法。