Android N NotificationManagerService源码分析
来源:互联网 发布:中级程序员报考条件 编辑:程序博客网 时间:2024/06/01 08:34
关于NotificationManagerService的使用推荐参考下面资料,写的不错:
Android4.4 NotificationManagerService使用详解与原理分析(一)、(二):点击打开链接
这里主要分析framework层的源码。
相关文件主要有:
frameworks\base\core\java\android\app\NotificationManager.java;
frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java;
frameworks\base\services\core\java\com\android\server\statusbar\StatusBarManagerService.java;
第三方应用发起通知时,调用的是NotificationManager的notify方法,我们就从这里入手。
notify方法表示发送一个显示在状态栏上的通知,代码如下:
/** * Post a notification to be shown in the status bar. If a notification with * the same id has already been posted by your application and has not yet been canceled, it * will be replaced by the updated information. * * @param id An identifier for this notification unique within your * application. * @param notification A {@link Notification} object describing what to show the user. Must not * be null. */ public void notify(int id, Notification notification) { notify(null, id, notification); }
如果你的第三方应用发送了一个相同id的通知,并且没有被取消,它将被更新的信息所取代。
/** * Post a notification to be shown in the status bar. If a notification with * the same tag and id has already been posted by your application and has not yet been * canceled, it will be replaced by the updated information. * * @param tag A string identifier for this notification. May be {@code null}. * @param id An identifier for this notification. The pair (tag, id) must be unique * within your application. * @param notification A {@link Notification} object describing what to * show the user. Must not be null. */ public void notify(String tag, int id, Notification notification) { notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); }如果你的第三方应用发送了一个相同tag和id的通知,并且没有被取消,它将被更新的信息所取代。
/** * @hide */ public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { int[] idOut = new int[1]; INotificationManager service = getService();//获取IBinder对象(NotificationManagerService) String pkg = mContext.getPackageName(); // Fix the notification as best we can. Notification.addFieldsFromContext(mContext, notification);//"android.appinfo"、"android.originatingUserId" if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } } fixLegacySmallIcon(notification, pkg); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { if (notification.getSmallIcon() == null) { throw new IllegalArgumentException("Invalid notification (no valid small icon): " + notification); } } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); final Notification copy = Builder.maybeCloneStrippedForDelivery(notification); try { service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, copy, idOut, user.getIdentifier()); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
- 我们看下Notification的addFieldsFromContext方法做了哪些事情:
/** * @hide */ public static void addFieldsFromContext(Context context, Notification notification) { addFieldsFromContext(context.getApplicationInfo(), context.getUserId(), notification); } /** * @hide */ public static void addFieldsFromContext(ApplicationInfo ai, int userId, Notification notification) { notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, ai);//"android.appinfo" notification.extras.putInt(EXTRA_ORIGINATING_USERID, userId);//"android.originatingUserId" }
主要是保存了当前应用对应的ApplicationInfo对象和对应的userId。在Android4.4中Notification新增了extras字段,用来保存系统通知的一些具体信息,定义如下:
/** * Additional semantic data to be carried around with this Notification. * <p> * The extras keys defined here are intended to capture the original inputs to {@link Builder} * APIs, and are intended to be used by * {@link android.service.notification.NotificationListenerService} implementations to extract * detailed information from notification objects. */ public Bundle extras = new Bundle();addFieldsFromContext方法主要实现将当前应用的ApplicationInfo对象保存到“android.appinfo”字段,将当前的userId保存到“android.originatingUserId”字段中。
- fixLegacySmalIcon,notify函数会判断notification是否有small icon,如果没有设icon或small icon,用notify方法时会抛出异常。fixLegacySmallIcon方法如下:
private void fixLegacySmallIcon(Notification n, String pkg) { if (n.getSmallIcon() == null && n.icon != 0) { n.setSmallIcon(Icon.createWithResource(pkg, n.icon)); } }
完成上面的操作后,调用通知管理类NotificationManagerService的enqueueNotificationWithTag方法,
@Override public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int[] idOut, int userId) throws RemoteException { enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(), tag, id, notification, idOut, userId); }
我们看到在enqueueNotificationWithTag方法中调用了enqueueNotificationInternal方法,这里就是通知处理的核心了。
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int[] idOut, int incomingUserId) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); } checkCallerIsSystemOrSameApp(pkg); // 校验UID final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg); final int userId = ActivityManager.handleIncomingUser(callingPid, callingUid, incomingUserId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); // Fix the notification as best we can. try { final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId); Notification.addFieldsFromContext(ai, userId, notification); } catch (NameNotFoundException e) { Slog.e(TAG, "Cannot create a context for sending app", e); return; } mUsageStats.registerEnqueuedByApp(pkg); // Limit the number of notifications that any given package except the android // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. // 这里会做一个限制,除了系统级别的应用之外,其他应用的notification数量会做限制,用来防止DOS攻击导致的泄露 if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationList) { final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg); if (appEnqueueRate > mMaxPackageEnqueueRate) { mUsageStats.registerOverRateQuota(pkg); final long now = SystemClock.elapsedRealtime(); if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) { Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate + ". Shedding events. package=" + pkg); mLastOverRateLogTime = now; } return; } int count = 0; final int N = mNotificationList.size(); for (int i=0; i<N; i++) { final NotificationRecord r = mNotificationList.get(i); if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) { if (r.sbn.getId() == id && TextUtils.equals(r.sbn.getTag(), tag)) { break; // Allow updating existing notification } count++; if (count >= MAX_PACKAGE_NOTIFICATIONS) {// 同一个应用发送notification数量不能超过50 mUsageStats.registerOverCountQuota(pkg); Slog.e(TAG, "Package has already posted " + count + " notifications. Not showing more. package=" + pkg); return; } } } } } if (pkg == null || notification == null) {//通知不能为空 throw new IllegalArgumentException("null not allowed: pkg=" + pkg + " id=" + id + " notification=" + notification); } // Whitelist pending intents. if (notification.allPendingIntents != null) { final int intentCount = notification.allPendingIntents.size(); if (intentCount > 0) { final ActivityManagerInternal am = LocalServices .getService(ActivityManagerInternal.class); final long duration = LocalServices.getService( DeviceIdleController.LocalService.class).getNotificationWhitelistDuration(); for (int i = 0; i < intentCount; i++) { PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i); if (pendingIntent != null) { am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(), duration); } } } } // Sanitize inputs notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX); // setup local book-keeping // 验证完条件后,将前面传递进来的Notification封装成一个StatusBarNotification对象, final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, callingUid, callingPid, 0, notification, user); // 封装NotificationRecord对象 final NotificationRecord r = new NotificationRecord(getContext(), n); mHandler.post(new EnqueueNotificationRunnable(userId, r)); idOut[0] = id; }开启线程,异步处理。
private class EnqueueNotificationRunnable implements Runnable { private final NotificationRecord r; private final int userId; EnqueueNotificationRunnable(int userId, NotificationRecord r) { this.userId = userId; this.r = r; }; @Override public void run() { synchronized (mNotificationList) { final StatusBarNotification n = r.sbn; if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey()); NotificationRecord old = mNotificationsByKey.get(n.getKey()); if (old != null) { // Retain ranking information from previous record r.copyRankingInformation(old); } final int callingUid = n.getUid(); final int callingPid = n.getInitialPid(); final Notification notification = n.getNotification(); final String pkg = n.getPackageName(); final int id = n.getId(); final String tag = n.getTag(); final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); // Handle grouped notifications and bail out early if we // can to avoid extracting signals. handleGroupedNotificationLocked(r, old, callingUid, callingPid); // 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)) { int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW; if (old != null) { enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE; } EventLogTags.writeNotificationEnqueue(callingUid, callingPid, pkg, id, tag, userId, notification.toString(), enqueueStatus); } mRankingHelper.extractSignals(r); final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid); // blocked apps 判断pkg是否可以显示通知 if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) { if (!isSystemNotification) {//不拦截系统通知 if (isPackageSuspended) { Slog.e(TAG, "Suppressing notification from package due to package " + "suspended by administrator."); mUsageStats.registerSuspendedByAdmin(r); } else { Slog.e(TAG, "Suppressing notification from package by user request."); mUsageStats.registerBlocked(r); } return; } } // tell the ranker service about the notification if (mRankerServices.isEnabled()) { mRankerServices.onNotificationEnqueued(r); // TODO delay the code below here for 100ms or until there is an answer } // 获取是否已经发送过此notification int index = indexOfNotificationLocked(n.getKey()); if (index < 0) { // 如果是新发送的notification,就走新增流程. mNotificationList.add(r); mUsageStats.registerPostedByApp(r); } else { //如果有发送过,就获取oldNtificationRecord,后面走更新流程 mStatusBar.updateNotification(r.statusBarKey, n) old = mNotificationList.get(index); mNotificationList.set(index, r); mUsageStats.registerUpdatedByApp(r, old); // Make sure we don't lose the foreground service state. notification.flags |= old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; r.isUpdate = true; } Slog.d(TAG, "NotificationRecord, r = "+r); mNotificationsByKey.put(n.getKey(), r); // 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; } applyZenModeLocked(r); mRankingHelper.sort(mNotificationList);//将mNotificationList排序 // 如果notification设置了smallIcon,调用所有NotificationListeners的notifyPostedLocked方法, // 通知有新的notification,传入的参数为上面封装成的StatusBarNotification对象. if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.sbn : null; mListeners.notifyPostedLocked(n, oldSbn); } else { Slog.e(TAG, "Not posting notification without small icon: " + notification); if (old != null && !old.isCanceled) { mListeners.notifyRemovedLocked(n); } // ATTENTION: in a future release we will bail out here // so that we do not play sounds, show lights, etc. for invalid // notifications Slog.e(TAG, "WARNING: In a future release this will crash the app: " + n.getPackageName()); } // buzzBeepBlinkLocked方法负责对消息进行处理。 // 通知status bar显示该notification,确认是否需要声音,震动和闪光,如果需要,那么就发出声音,震动和闪光 buzzBeepBlinkLocked(r); } } }最后调用buzzBeepBlinkLocked方法对消息进行处理。
void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false; boolean beep = false; boolean blink = false; final Notification notification = record.sbn.getNotification(); final String key = record.getKey(); // Should this notification make noise, vibe, or use the LED?是否需要声音、震动、led final boolean aboveThreshold = record.getImportance() >= IMPORTANCE_DEFAULT; final boolean canInterrupt = aboveThreshold && !record.isIntercepted(); if (DBG || record.isIntercepted()) Slog.v(TAG, "pkg=" + record.sbn.getPackageName() + " canInterrupt=" + canInterrupt + " intercept=" + record.isIntercepted() ); final int currentUser; final long token = Binder.clearCallingIdentity(); try { currentUser = ActivityManager.getCurrentUser(); } finally { Binder.restoreCallingIdentity(token); } // If we're not supposed to beep, vibrate, etc. then don't. final String disableEffects = disableNotificationEffects(record); if (disableEffects != null) { ZenLog.traceDisableEffects(record, disableEffects); } // Remember if this notification already owns the notification channels. boolean wasBeep = key != null && key.equals(mSoundNotificationKey); boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey); // These are set inside the conditional if the notification is allowed to make noise. boolean hasValidVibrate = false; boolean hasValidSound = false; boolean smsRingtone = false; if (mCarrierConfig == null) { mCarrierConfig = mConfigManager.getConfig(); } else { smsRingtone = mCarrierConfig.getBoolean( CarrierConfigManager.KEY_CONFIG_SMS_RINGTONE_INCALL); } if ((disableEffects == null || (smsRingtone && mInCall)) && (record.getUserId() == UserHandle.USER_ALL || record.getUserId() == currentUser || mUserProfiles.isCurrentProfile(record.getUserId())) && canInterrupt && mSystemReady && mAudioManager != null) { if (DBG) Slog.v(TAG, "Interrupting!"); // should we use the default notification sound? (indicated either by // DEFAULT_SOUND or because notification.sound is pointing at // Settings.System.NOTIFICATION_SOUND) final boolean useDefaultSound = (notification.defaults & Notification.DEFAULT_SOUND) != 0 || Settings.System.DEFAULT_NOTIFICATION_URI .equals(notification.sound); Uri soundUri = null; if (useDefaultSound) { soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; // check to see if the default notification sound is silent ContentResolver resolver = getContext().getContentResolver(); hasValidSound = Settings.System.getString(resolver, Settings.System.NOTIFICATION_SOUND) != null; } else if (notification.sound != null) { soundUri = notification.sound; hasValidSound = (soundUri != null); } // 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 && (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE); // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. final boolean useDefaultVibrate = (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; hasValidVibrate = useDefaultVibrate || convertSoundToVibration || hasCustomVibrate; // We can alert, and we're allowed to alert, but if the developer asked us to only do // it once, and we already have, then don't. if (!(record.isUpdate && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)) { sendAccessibilityEvent(notification, record.sbn.getPackageName()); if (hasValidSound) { boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; AudioAttributes audioAttributes = audioAttributesForNotification(notification); mSoundNotificationKey = key; // do not play notifications if stream volume is 0 (typically because // ringer mode is silent) or if there is a user of exclusive audio focus if ((mAudioManager.getStreamVolume( AudioAttributes.toLegacyStreamType(audioAttributes)) != 0) && !mAudioManager.isAudioFocusExclusive()) { final long identity = Binder.clearCallingIdentity(); try { final IRingtonePlayer player = mAudioManager.getRingtonePlayer(); if (player != null) { if (DBG) Slog.v(TAG, "Playing sound " + soundUri + " with attributes " + audioAttributes); player.playAsync(soundUri, record.sbn.getUser(), looping, audioAttributes); beep = true; } } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(identity); } } } if (hasValidVibrate && !(mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT)) { mVibrateNotificationKey = key; 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(record.sbn.getUid(), record.sbn.getOpPkg(), useDefaultVibrate ? mDefaultVibrationPattern : mFallbackVibrationPattern, ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1, audioAttributesForNotification(notification)); buzz = true; } finally { Binder.restoreCallingIdentity(identity); } } else if (notification.vibrate.length > 1) { // If you want your own vibration pattern, you need the VIBRATE // permission mVibrator.vibrate(record.sbn.getUid(), record.sbn.getOpPkg(), notification.vibrate, ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1, audioAttributesForNotification(notification)); buzz = true; } } } } // If a notification is updated to remove the actively playing sound or vibrate, // cancel that feedback now if (wasBeep && !hasValidSound) { clearSoundLocked(); } if (wasBuzz && !hasValidVibrate) { clearVibrateLocked(); } // light // release the light boolean wasShowLights = mLights.remove(key); if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 && aboveThreshold && ((record.getSuppressedVisualEffects() & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) == 0)) { mLights.add(key); updateLightsLocked(); if (mUseAttentionLight) { mAttentionLight.pulse(); } blink = true; } else if (wasShowLights) { updateLightsLocked(); } if (buzz || beep || blink) { if (((record.getSuppressedVisualEffects() & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0)) { if (DBG) Slog.v(TAG, "Suppressed SystemUI from triggering screen on"); } else { EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0); // 后将mBuzzBeepBlinked post到工作handler,最后会调用到mStatusBar.buzzBeepBlinked() mHandler.post(mBuzzBeepBlinked); } } }
0 0
- Android N NotificationManagerService源码分析
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)
- android N dmesg源码分析
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(二)__原理分析
- Android 4.4 NotificationManagerService使用详解与原理分析(二)__原理分析
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(二)__原理分析
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(二)__原理分析
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解
- Android 4.4NotificationManagerService使用详解与原理分析(一)__使用详解
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解
- Android 4.4 KitKat NotificationManagerService使用详解与原理分析(一)__使用详解
- Android N SystemUI分析
- NotificationManagerService笔记
- 基于N源码Service启动分析
- Android源码/框架源码分析
- Android N代码分析:requestLayout
- Android N Idle模式分析
- android-自定义Dialog样式
- oracle创建数据库后创建自己的用户
- OSPF相关知识
- Windows下Redis的安装使用
- 32 的两种捕获
- Android N NotificationManagerService源码分析
- Unity5.3a4新版内存分析工具 -MemoryProfiler
- 新特性记录
- 第八周--项目3-顺序串算法
- 同步互斥
- Kernel panic – not syncing: Attempted to kill init 解决
- Android View绘制的13问13答。
- java.sql Connection
- python收集网页中的翻页