前言:最近在做 基于Android6.0的SystemUI开发,其中有一个功能决定通过系统的Notification通道来实现,在网上找了些资料,都没找到自己想要的东西,所以就只能自己慢慢看源码,现在把自己看懂的部分整理一下,有不对或者不详尽的地方,希望大家指正和补充。PS:此文需要配合安卓6.0SystemUI源码食用。
在安卓app中调用调用NotificationManager.notify(int id,Notification notification)方法可以发出和更新Notification跨进程在SystemUI的通知栏中进行显示,调用NotificationManager.cancel(int id)可以取消通知。我这里关注的是,整个流程走到SystemUI之后,里面是怎么对通知进行管理和显示的,确切的说是Notification中的自定义RemoteView部分,因为我需要利用这部分来实现我修改后的SystemUI的某个功能。
下面先上类图和添加新通知的时序图,更新通知和移除通知一来比添加新通知简单二来很多流程会和添加新通知殊途同归,就不贴了
SystemUI中关于Notification的类图
SystemUI中添加新通知的时序图
新Notification的添加
不管是发出一个新的通知还是对已经存在的通知进行更新,调用的都是NotificationManager.notify(int id,Notification notification)。最后走到SystemUI的时候首先调用BaseStatusBar中的成员变量mNotificationListener的onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap)方法。
其中BaseStatusBar是一个抽象类,是所有状态栏的基类,位于SystemUI工程下,包路径为com.android.systemui.statusbar。它的成员变量mNotificationListener是一个NotificationListenerService类的对象,该类不在SystemUI工程下,我们暂不关注。
来看mNotificationListener对onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap)方法的实现。
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() {
String key = sbn.getKey();
boolean isUpdate = mNotificationData.get(key) != null;
.
.
.
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap, null /* oldEntry */);
}
}
});
}
}
首先来看方法中的两个参数:1.StatusBarNotification sbn;2.RankingMap rankingMap。
StatusBarNotification点进去看,发现其实是由Notification组装而成,里面比较重要的属性有String pkg,int id,String key,Notification notification,保存着通知的内容,发出通知的报名信息,以及id等。StatusBarNotification 具体的组装生成过程不是在SystemUI包中进行,暂不关注。
RankingMap则是NotificationListenerService的一个静态内部类,里面保存着所有Notification相关的信息,具体的我们先往下看。
在onNotificationPosted()方法中,先对传过来的sbn进行空判断,不为空,则用mHandler发一个新的runnable来处理。
String key = sbn.getKey();
boolean isUpdate = mNotificationData.get(key) != null;
先拿到sbn里面key属性,再根据这个key去mNotificationData取对象。
mNotificationData是BaseStatusBar的一个protected成员变量,可被子类继承,自己本身的类是NotificationData,位于SystemUI工程下的com.android.systemui.statusbar。它的get(key)方法如下:
public Entry get(String key) {
return mEntries.get(key);
}
返回了一个Entry对象mEntries.get(key),我们来看看这个Entry是什么。Entry是NotificationData的一个内部类。其中包含的几个重要的属性的属性:
public String key;
public StatusBarNotification notification;
public StatusBarIconView icon;
public ExpandableNotificationRow row; // the outer expanded view
显然,我们的Notification先被封装成StatusBarNotification传到SystemUI,然后在SystemUI中又再一次被封装进这个Entry中。
然后我们再看看这个mEntries,这是NotificationData的一个成员变量:
private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
暂时没看到这个map的数据是在哪里添加的。
我们回到onNotificationPosted方法,跳过一部分代码,直接来到重点。
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap, null /* oldEntry */);
}
如果mNotificationData能通过sbn的key拿到的Entry不为空,说明这个通知已经存在了,isUpdate为true走更新流程,否则走添加流程。到此,onNotificationPosted方法就结束了。
我们先来看添加流程addNotification(sbn, rankingMap, null /* oldEntry */)。
这在BaseStatusBar中是一个抽象方法,具体逻辑有它的子类去实现,由于我们主要关注的是手机,所以去到PhoneStatusBar,位于com.android.systemui.statusbar.phone。
在PhoneStatusBar中,这个方法是被这么实现的
public void addNotification(StatusBarNotification notification, RankingMap ranking,
Entry oldEntry) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
.
.
.
addNotificationViews(shadeEntry, ranking);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
}
首先通过传来的StatusBarNotification notification封装构造出一个Entry对象(注意,这的notification是StabusBarNotification不是Notification,也就是onNotificationPosted传入的sbn)
Entry shadeEntry = createNotificationViews(notification);
跟过去看createNotificationViews(notification)方法,这里又跳回了状态栏的基类BaseStatusBar。
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
final StatusBarIconView iconView = createIcon(sbn);
if (iconView == null) {
return null;
}
// Construct the expanded view.
Noti