众所周知,notification是在状态栏上显示的可以定制声音,震动,Led灯,单击跳转,显示内容的通知。通常应用中要发送一个notification都是通过以下方式:
- NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- ……//创建notification
- manager.notify(1, notification);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
……//创建notification
manager.notify(1, notification);
我们可以在创建notification时指定notification的声音,震动,Led灯,单击跳转,显示内容,通过NotificationManager发送出去,最终在状态栏上显示,在显示过程中对声音,震动,Led灯做同步处理。
先来看看NotificationManager发送通知后的显示时序图:
从时序图上看出,同android所有的Manager类一样,最终都是通过调用Framework中的Service类实现所要完成的操作。下面详细描述每一步所完成的操作:
1,在调用NotificationManager.notify后,NotificationManager通过内部的NotificationManagerService代理处理后续的发送任务,这里调用的是NotificationManagerService.enqueueNotification.
2,NotificationManagerService.enqueueNotification中反复调用了自己的几个方法,最终调用的是enqueueNotificationInternal发送,这个方法里完成了所有对notification发送的处理,也包括了对notification的更新。此类在Frameworks的services目录下面,方法如下:
- public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
- String tag, int id, int priority, Notification notification, int[] idOut)
- {
- checkIncomingCall(pkg);
- // Limit the number of notifications that any given package except the android
- // package can enqueue. Prevents DOS attacks and deals with leaks.
- if (!"android".equals(pkg)) {
- synchronized (mNotificationList) {
- int count = 0;
- final int N = mNotificationList.size();
- for (int i=0; i<N; i++) {
- final NotificationRecord r = mNotificationList.get(i);
- if (r.pkg.equals(pkg)) {
- count++;
- if (count >= MAX_PACKAGE_NOTIFICATIONS) {
- Slog.e(TAG, "Package has already posted " + count
- + " notifications. Not showing more. package=" + pkg);
- return;
- }
- }
- }
- }
- }
- // This conditional is a dirty hack to limit the logging done on
- // behalf of the download manager without affecting other apps.
- if (!pkg.equals("com.android.providers.downloads")
- || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
- EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag,
- notification.toString());
- }
- if (pkg == null || notification == null) {
- throw new IllegalArgumentException("null not allowed: pkg=" + pkg
- + " id=" + id + " notification=" + notification);
- }
- if (notification.icon != 0) {
- if (notification.contentView == null) {
- throw new IllegalArgumentException("contentView required: pkg=" + pkg
- + " id=" + id + " notification=" + notification);
- }
- }
- synchronized (mNotificationList) {
- NotificationRecord r = new NotificationRecord(pkg, tag, id,
- callingUid, callingPid,
- priority,
- notification);
- NotificationRecord old = null;
- int index = indexOfNotificationLocked(pkg, tag, id);
- if (index < 0) {
- mNotificationList.add(r);
- } else {
- old = mNotificationList.remove(index);
- mNotificationList.add(index, r);
- // Make sure we don't lose the foreground service state.
- if (old != null) {
- notification.flags |=
- old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
- }
- }
- // Ensure if this is a foreground service that the proper additional
- // flags are set.
- if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
- notification.flags |= Notification.FLAG_ONGOING_EVENT
- | Notification.FLAG_NO_CLEAR;
- }
- if (notification.icon != 0) {
- StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
- r.uid, r.initialPid, notification);
- n.priority = r.priority;
- if (old != null && old.statusBarKey != null) {
- r.statusBarKey = old.statusBarKey;
- long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.updateNotification(r.statusBarKey, n);
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- } else {
- long identity = Binder.clearCallingIdentity();
- try {
- r.statusBarKey = mStatusBar.addNotification(n);
- mAttentionLight.pulse();
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- sendAccessibilityEvent(notification, pkg);
- } else {
- Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
- if (old != null && old.statusBarKey != null) {
- long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.removeNotification(old.statusBarKey);
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
- // If we're not supposed to beep, vibrate, etc. then don't.
- if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
- && (!(old != null
- && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
- && mSystemReady) {
- final AudioManager audioManager = (AudioManager) mContext
- .getSystemService(Context.AUDIO_SERVICE);
- // sound
- final boolean useDefaultSound =
- (notification.defaults & Notification.DEFAULT_SOUND) != 0;
- if (useDefaultSound || notification.sound != null) {
- Uri uri;
- if (useDefaultSound) {
- uri = Settings.System.DEFAULT_NOTIFICATION_URI;
- } else {
- uri = notification.sound;
- }
- boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
- int audioStreamType;
- if (notification.audioStreamType >= 0) {
- audioStreamType = notification.audioStreamType;
- } else {
- audioStreamType = DEFAULT_STREAM_TYPE;
- }
- mSoundNotification = r;
- // do not play notifications if stream volume is 0
- // (typically because ringer mode is silent).
- if (audioManager.getStreamVolume(audioStreamType) != 0) {
- long identity = Binder.clearCallingIdentity();
- try {
- mSound.play(mContext, uri, looping, audioStreamType);
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
- // vibrate
- final boolean useDefaultVibrate =
- (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
- if ((useDefaultVibrate || notification.vibrate != null)
- && audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
- mVibrateNotification = r;
- mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
- : notification.vibrate,
- ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
- }
- }
- // this option doesn't shut off the lights
- // light
- // the most recent thing gets the light
- mLights.remove(old);
- if (mLedNotification == old) {
- mLedNotification = null;
- }
- //Slog.i(TAG, "notification.lights="
- // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
- if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
- mLights.add(r);
- updateLightsLocked();
- } else {
- if (old != null
- && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
- updateLightsLocked();
- }
- }
- }
- idOut[0] = id;
- }
public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid,
String tag, int id, int priority, Notification notification, int[] idOut)
{
checkIncomingCall(pkg);
// Limit the number of notifications that any given package except the android
// package can enqueue. Prevents DOS attacks and deals with leaks.
if (!"android".equals(pkg)) {
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
for (int i=0; i<N; i++) {
final NotificationRecord r = mNotificationList.get(i);
if (r.pkg.equals(pkg)) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " notifications. Not showing more. package=" + pkg);
return;
}
}
}
}
}
// This conditional is a dirty hack to limit the logging done on
// behalf of the download manager without affecting other apps.
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag,
notification.toString());
}
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
if (notification.icon != 0) {
if (notification.contentView == null) {
throw new IllegalArgumentException("contentView required: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
}
synchronized (mNotificationList) {
NotificationRecord r = new NotificationRecord(pkg, tag, id,
callingUid, callingPid,
priority,
notification);
NotificationRecord old = null;
int index = indexOfNotificationLocked(pkg, tag, id);
if (index < 0) {
mNotificationList.add(r);
} else {
old = mNotificationList.remove(index);
mNotificationList.add(index, r);
// Make sure we don't lose the foreground service state.
if (old != null) {
notification.flags |=
old.notification.flags&Notification.FLAG_FOREGROUND_SERVICE;
}
}
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR;
}
if (notification.icon != 0) {
StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
r.uid, r.initialPid, notification);
n.priority = r.priority;
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
}
finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
mAttentionLight.pulse();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
sendAccessibilityEvent(notification, pkg);
} else {
Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// If we're not supposed to beep, vibrate, etc. then don't.
if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
&& (!(old != null
&& (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
&& mSystemReady) {
final AudioManager audioManager = (AudioManager) mContext
.getSystemService(Context.AUDIO_SERVICE);
// sound
final boolean useDefaultSound =
(notification.defaults & Notification.DEFAULT_SOUND) != 0;
if (useDefaultSound || notification.sound != null) {
Uri uri;
if (useDefaultSound) {
uri = Settings.System.DEFAULT_NOTIFICATION_URI;
} else {
uri = notification.sound;
}
boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0;
int audioStreamType;
if (notification.audioStreamType >= 0) {
audioStreamType = notification.audioStreamType;
} else {
audioStreamType = DEFAULT_STREAM_TYPE;
}
mSoundNotification = r;
// do not play notifications if stream volume is 0
// (typically because ringer mode is silent).
if (audioManager.getStreamVolume(audioStreamType) != 0) {
long identity = Binder.clearCallingIdentity();
try {
mSound.play(mContext, uri, looping, audioStreamType);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
// vibrate
final boolean useDefaultVibrate =
(notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
if ((useDefaultVibrate || notification.vibrate != null)
&& audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION)) {
mVibrateNotification = r;
mVibrator.vibrate(useDefaultVibrate ? DEFAULT_VIBRATE_PATTERN
: notification.vibrate,
((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1);
}
}
// this option doesn't shut off the lights
// light
// the most recent thing gets the light
mLights.remove(old);
if (mLedNotification == old) {
mLedNotification = null;
}
//Slog.i(TAG, "notification.lights="
// + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0));
if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0) {
mLights.add(r);
updateLightsLocked();
} else {
if (old != null
&& ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) {
updateLightsLocked();
}
}
}
idOut[0] = id;
}
3,首先是对Notification的属性做了验证,同一个应用发送notification数量不能超过MAX_PACKAGE_NOTIFICATIONS的值99,其余的验证包括notification不为空,contentView 不为空的验证,条件都满足后先创建一个NotificationRecord
- NotificationRecord r = new NotificationRecord(pkg, tag, id,
- callingUid, callingPid,
- priority,
- notification);
NotificationRecord r = new NotificationRecord(pkg, tag, id,
callingUid, callingPid,
priority,
notification);
通过
indexOfNotificationLocked(pkg, tag, id)获取是否已经发送过此notification,如果有发送过,就获取oldNtificationRecord,后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n);如果是新发送的notification就走新增流程r.statusBarKey = mStatusBar.addNotification(n); 如果notification的icon为空,并且存在旧的NotificationRecord,就调用mStatusBar.removeNotification(old.statusBarKey)取消这个notification,代码如下:
- if (notification.icon != 0) {
- StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
- r.uid, r.initialPid, notification);
- n.priority = r.priority;
- if (old != null && old.statusBarKey != null) {
- r.statusBarKey = old.statusBarKey;
- long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.updateNotification(r.statusBarKey, n);
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- } else {
- long identity = Binder.clearCallingIdentity();
- try {
- r.statusBarKey = mStatusBar.addNotification(n);
- mAttentionLight.pulse();
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- sendAccessibilityEvent(notification, pkg);
- } else {
- Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
- if (old != null && old.statusBarKey != null) {
- long identity = Binder.clearCallingIdentity();
- try {
- mStatusBar.removeNotification(old.statusBarKey);
- }
- finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
if (notification.icon != 0) {
StatusBarNotification n = new StatusBarNotification(pkg, id, tag,
r.uid, r.initialPid, notification);
n.priority = r.priority;
if (old != null && old.statusBarKey != null) {
r.statusBarKey = old.statusBarKey;
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.updateNotification(r.statusBarKey, n);
}
finally {
Binder.restoreCallingIdentity(identity);
}
} else {
long identity = Binder.clearCallingIdentity();
try {
r.statusBarKey = mStatusBar.addNotification(n);
mAttentionLight.pulse();
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
sendAccessibilityEvent(notification, pkg);
} else {
Slog.e(TAG, "Ignoring notification with icon==0: " + notification);
if (old != null && old.statusBarKey != null) {
long identity = Binder.clearCallingIdentity();
try {
mStatusBar.removeNotification(old.statusBarKey);
}
finally {
Binder.restoreCallingIdentity(identity);
}
}
}
这里我们走notification的新增流程
mStatusBar.addNotification(n),此mStatusBar是系统启动时创建NotificationManagerService中传的StatusBarManagerService。在上一篇文章中SystemUI的启动过程中说过StatusBarManagerService中的对statusBar的操作都是通过IStatusBar的接口实现类StatusBar完成的,所以addNotification最终是在StatusBar的子类PhoneStatusBar中完成。
4,PhoneStatusBar在SystemUI的包里面,在它的addNotification方法中完成了所有Notification在状态栏上的显示操作。对PhoneStatusBar.addNotification的调用是采用Handle的方式,此Handle的Message成功发送后,StatusBarManagerService的addNotification就返回了, 然后再返回到NotificationManagerService的调用处,接着处理后续的声音播放NotificationPlayer.play、震动Vibrator.vibrator、Led指示灯updateLightsLocked,这些都是和PhoneStatusBar的addNotification同时在进行,当Notification在显示的时候,声音,震动,Led的处理就同步完成了。
5,在PhoneStatusBar.addNotification中调用addNotificationView将notification的icon添加到statusBar,还有notification在ExpandedView(状态栏拉开的那个界面)中显示的View也是在此方法中创建。
接着调用tick方法,显示Notification的tick提示,就是notification显示时在状态栏上滚动的那个效果。
至此,Notification的显示就完成了。StatusBar上除了Notification的显示还有StatusIcon(状态图标),状态图标的显示比较简单。系统的状态图标都是在SystemUI中控制显示,通过注册监听,接收系统广播控制图标的显示。SystemUI在启动时系统设置的状态图标都会加入到状态栏的view中,但不会设置显示,当相应事件发生后,注册的监听或广播就会设置相应图标显示。要显示的状态图标都需要现在/frameworks/base/core/values/config.xml中设置名称,否则系统无法显示状态图标。