Android(安卓)NotificationManager简读
今天发觉系统状态栏不能够显示通知条了,但是通知还是有声音之类的,只是不能够显示了,是突然不可以显示了.所以突然看了一下源代码.
应用程序中发送通知Notification都需要获取通知服务:
NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
看来是通过NotificationManager管理器获取的,根据Android的代理模式,就会对应NotificationManagerService,所以大致梳理一下.
源代码路径都在:
~\base\services\core\java\com\android\server\notification
思路差不多如下:
一般从NotificationManager开始:为什么从notify开始呢?因为每次要发送通知都是通过最后notify方法的.
public void notify(int id, Notification notification) { notify(null, id, notification); }
然后进一步:
public void notify(String tag, int id, Notification notification) { int[] idOut = new int[1]; INotificationManager service = getService(); String pkg = mContext.getPackageName(); if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut, UserHandle.myUserId()); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); } } catch (RemoteException e) { } }
其中程序中的service即NofiticationManagerService,然后再看看:这个服务都是系统SystemServer中的run启动的.
service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut, UserHandle.myUserId());
...
// Notifications // ============================================================================ public void enqueueNotificationWithTag(String pkg, String tag, int id, Notification notification, int[] idOut, int userId) { enqueueNotificationInternal(pkg, Binder.getCallingUid(), Binder.getCallingPid(), tag, id, notification, idOut, userId); }
...
// Not exposed via Binder; for system use only (otherwise malicious apps could spoof the // uid/pid of another application) public void enqueueNotificationInternal(String pkg, int callingUid, int callingPid, String tag, int id, Notification notification, int[] idOut, int userId) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); } checkCallerIsSystemOrSameApp(pkg); final boolean isSystemNotification = ("android".equals(pkg)); userId = ActivityManager.handleIncomingUser(callingPid, callingUid, userId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); // Limit the number of notifications that any given package except the android // package can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification) { 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, userId, 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); } } // === Scoring === // 0. Sanitize inputs notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX); // Migrate notification flags to scores if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { if (notification.priority < Notification.PRIORITY_MAX) notification.priority = Notification.PRIORITY_MAX; } else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH; } // 1. initial score: buckets of 10, around the app int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] // 2. Consult external heuristics (TBD) // 3. Apply local rules // blocked apps if (ENABLE_BLOCKED_NOTIFICATIONS && !isSystemNotification && !areNotificationsEnabledForPackageInt(pkg)) { score = JUNK_SCORE; Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request."); } if (DBG) { Slog.v(TAG, "Assigned score=" + score + " to " + notification); } if (score < SCORE_DISPLAY_THRESHOLD) { // Notification will be blocked because the score is too low. return; } // Should this notification make noise, vibe, or use the LED? final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); synchronized (mNotificationList) { NotificationRecord r = new NotificationRecord(pkg, tag, id, callingUid, callingPid, userId, score, notification); NotificationRecord old = null; int index = indexOfNotificationLocked(pkg, tag, id, userId); 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; } final int currentUser; final long token = Binder.clearCallingIdentity(); try { currentUser = ActivityManager.getCurrentUser(); } finally { Binder.restoreCallingIdentity(token); } if (notification.icon != 0) { final StatusBarNotification n = new StatusBarNotification( pkg, id, tag, r.uid, r.initialPid, score, notification, user); 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); if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && canInterrupt) { mAttentionLight.pulse(); } } finally { Binder.restoreCallingIdentity(identity); } } // Send accessibility events only for the current user. if (currentUser == userId) { 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 )) && (r.userId == UserHandle.USER_ALL || (r.userId == userId && r.userId == currentUser)) && canInterrupt && mSystemReady) { final AudioManager audioManager = (AudioManager) mContext .getSystemService(Context.AUDIO_SERVICE); // sound final boolean useDefaultSound = (notification.defaults & Notification.DEFAULT_SOUND) != 0; Uri soundUri = null; boolean hasValidSound = false; if (useDefaultSound) { soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; // check to see if the default notification sound is silent ContentResolver resolver = mContext.getContentResolver(); hasValidSound = Settings.System.getString(resolver, Settings.System.NOTIFICATION_SOUND) != null; } else if (notification.sound != null) { soundUri = notification.sound; hasValidSound = (soundUri != null); } if (hasValidSound) { 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) or if speech recognition is active. if ((audioManager.getStreamVolume(audioStreamType) != 0) && !audioManager.isSpeechRecognitionActive()) { final long identity = Binder.clearCallingIdentity(); try { final IRingtonePlayer player = mAudioService.getRingtonePlayer(); if (player != null) { player.playAsync(soundUri, user, looping, audioStreamType); } } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(identity); } } } // vibrate // Does the notification want to specify its own vibration? final boolean hasCustomVibrate = notification.vibrate != null; // new in 4.2: if there was supposed to be a sound and we're in vibrate mode, // and no other vibration is specified, we fall back to vibration final boolean convertSoundToVibration = !hasCustomVibrate && hasValidSound && (audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE); // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) && !(audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT)) { mVibrateNotification = r; if (useDefaultVibrate || convertSoundToVibration) { // Escalate privileges so we can use the vibrator even if the notifying app // does not have the VIBRATE permission. long identity = Binder.clearCallingIdentity(); try { mVibrator.vibrate(useDefaultVibrate ? mDefaultVibrationPattern : mFallbackVibrationPattern, ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1); } finally { Binder.restoreCallingIdentity(identity); } } else if (notification.vibrate.length > 1) { // If you want your own vibration pattern, you need the VIBRATE permission mVibrator.vibrate(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 && canInterrupt) { mLights.add(r); updateLightsLocked(); } else { if (old != null && ((old.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0)) { updateLightsLocked(); } } } idOut[0] = id
这一段程序就有点又臭又长,但是其实很简单.
synchronized (mNotificationList) { NotificationRecord r = new NotificationRecord(pkg, tag, id, callingUid, callingPid, userId, score, notification); NotificationRecord old = null; int index = indexOfNotificationLocked(pkg, tag, id, userId); 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; } }
上面的通过一个NotificationRecord结构体记录所有的通知信息并且保存,有点类似ActivityRecord等,这个***Record在Android系统里还是比较常见的.其中里面mNotificationList就是ArrayList其实看到下面的就差不多了:
if (notification.icon != 0) { final StatusBarNotification n = new StatusBarNotification( pkg, id, tag, r.uid, r.initialPid, score, notification, user); 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); if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && canInterrupt) { mAttentionLight.pulse(); } } finally { Binder.restoreCallingIdentity(identity); } }
其他后面都是处理这个通知是否需要一个通知音播放,或者闪灯等一些视觉听觉方面的操作.
上面程序,其中StatusBarNotification是一个Parcel的数据结构体.
mStatusBar是StatusBarManagerService对象,
如果这个通知是第一次生成:
r.statusBarKey = mStatusBar.addNotification(n); if ((n.notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && canInterrupt) { mAttentionLight.pulse(); }
如果不是第一次了,那么就更新一次:
mStatusBar.updateNotification(r.statusBarKey, n);
他就会添加,看StatusBarManagerService类中:
// ================================================================================ // Callbacks for NotificationManagerService. // ================================================================================ public IBinder addNotification(StatusBarNotification notification) { synchronized (mNotifications) { IBinder key = new Binder(); mNotifications.put(key, notification); if (mBar != null) { try { mBar.addNotification(key, notification); } catch (RemoteException ex) { } } return key; } }
其实前面看到parcelable就需要联想到Binder这个提供储存的"代理商".
其中:
mBar.addNotification(key, notification);
这个mBar是一个IStatusBar对象,这个地方addNotification就是有CommandQueue类完成的:
public void addNotification(IBinder key, StatusBarNotification notification) { synchronized (mList) { NotificationQueueEntry ne = new NotificationQueueEntry(); ne.key = key; ne.notification = notification; mHandler.obtainMessage(MSG_ADD_NOTIFICATION, 0, 0, ne).sendToTarget(); } }
防止堵塞,发送一个Handler事件,处理这个MSG_ADD_NOTIFICATION事件如下:
case MSG_ADD_NOTIFICATION: { final NotificationQueueEntry ne = (NotificationQueueEntry)msg.obj; mCallbacks.addNotification(ne.key, ne.notification); break; }
其中:
mCallbacks.addNotification(ne.key, ne.notification);
是一个回调.
上面的通过StatusBarManagerService这个服务进程进行添加,更新和保存管理通知信息,在SystemUI工程中使用如下BastStatusBar.java类:
mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE));
// Connect in to the status bar manager service StatusBarIconList iconList = new StatusBarIconList(); ArrayList notificationKeys = new ArrayList(); ArrayList notifications = new ArrayList(); mCommandQueue = new CommandQueue(this, iconList); int[] switches = new int[7]; ArrayList binders = new ArrayList(); try { mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications, switches, binders); } catch (RemoteException ex) { // If the system process isn't there we're doomed anyway. }
首先连接上面的服务,然后从服务中获取所有的通知List信息. 后面显示到状态栏上面:
protected StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification)
通过它生产通知View,然后被状态面板添加进去.
下面还有几个细节需要注意:
<1> : 由何APP发出的通知,是有记录在案的,即通知同时附带了发送通知APP的所有信息,比如报名,UID之类的等,通知分为系统通知和普通应用通知.
<2> : 5.0版本以后的新版本,通知是有优先级的(当然mNotificationList这个NotificationRecord集合在手,也可以人为在老版本自己定义优先级规则,通过上面获取的APP信息判断哪些APP的通知可以在何种情况下被添加,然后同步到状态栏).
<3> : 5.0版本以后的新版本,通知需要考虑更多条件,程序里面用Conditions,以及很多手机已经在用的ZenMode,即勿扰模式,这里面约束条件还是挺多的(比如通知来了不亮屏,通知来了不播放提示音,即使设置了,等等),有兴趣的可以自行下载N版本的源代码去看一看.
至于里面涉及的SystemUI状态栏工程,在我个人看来就是APP Service+WindowManager链接各种系统Service撑起来的.
更多相关文章
- tcping测试服务器TCP端口
- android之socket编程实例一
- Android面试题总结加强版(二)
- Android(安卓)7.0修改系统时间
- Inflater
- Android开机自启动+屏蔽系统功能
- android Unterminated string at character 44 of
- Android(安卓)Service的生命周期2
- 32位机器Ubuntu系统编译android 内核注意修改点