本文共 18413 字,大约阅读时间需要 61 分钟。
众所周知,notification是在状态栏上显示的可以定制声音,震动,Led灯,单击跳转,显示内容的通知。通常应用中要发送一个notification都是通过以下方式:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); ……//创建notificationmanager.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= 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);通过 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); } } }这里我们走notification的新增流程 mStatusBar.addNotification(n),此mStatusBar是系统启动时创建NotificationManagerService中传的StatusBarManagerService。在上一篇文章中中说过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中设置名称,否则系统无法显示状态图标。
转载地址:http://zgfsi.baihongyu.com/