源码剖析: Notification的发送
NotificationManagerService启动
在system_server
进程中,启动了NotificationManagerService
frameworks/base/services/java/com/android/server/SystemServer.java
private void startOtherServices() { mSystemServiceManager.startService(NotificationManagerService.class);}
看下SystemServiceManager
如何启动它的
frameworks/base/services/core/java/com/android/server/SystemServiceManager.java
public <T extends SystemService> T startService(Class<T> serviceClass) { try { final String name = serviceClass.getName(); // ... final T service; try { Constructor<T> constructor = serviceClass.getConstructor(Context.class); service = constructor.newInstance(mContext); } catch (Exception ex) { }// ... startService(service); return service; } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } } public void startService(@NonNull final SystemService service) { // Register it. mServices.add(service); // Start it. long time = SystemClock.elapsedRealtime(); try { service.onStart(); } catch (RuntimeException ex) { throw new RuntimeException("Failed to start service " + service.getClass().getName() + ": onStart threw an exception", ex); } warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart"); }
通过反射获取带Context
参数的构造函数,然后创建对象,最后调用对象的onStart()
方法。
NotificationManagerService
的构造函数很简单,就是保存Context
参数,而它的onStart()
方法进行大量的变量初始化,当我们以后需要这些变量的时候再来分析它们初始化过程。
注册NotificationChannel
从Android 8.0开始,在发送通知前,需要向系统注册通知通道(Notification Channel
),注册通道的示例代码如下。
private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = getString(R.string.channel_name); String description = getString(R.string.channel_description); int importance = NotificationManager.IMPORTANCE_DEFAULT; // 创建通道 NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); NotificationManager notificationManager = getSystemService(NotificationManager.class); // 注册通道 notificationManager.createNotificationChannel(channel); }}
先看下如何构造NotificationChannel
public NotificationChannel(String id, CharSequence name, @Importance int importance) { this.mId = getTrimmedString(id); this.mName = name != null ? getTrimmedString(name.toString()) : null; this.mImportance = importance; } private String getTrimmedString(String input) { if (input != null && input.length() > MAX_TEXT_LENGTH) { return input.substring(0, MAX_TEXT_LENGTH); } return input; } public void setDescription(String description) { mDesc = getTrimmedString(description); }
构造函数很简单,就是简单的变量的赋值。当我们在手机的Settings
中查看一个应用的通知设置的时候,如果这个应用向系统注册过通知通道,那么你就会看到各种通道,每一个通道会显示一个name
和一个description
。
通知通道创建完毕后,然后通过NotificationManager
向系统注册
/** * Creates a notification channel that notifications can be posted to. * * This can also be used to restore a deleted channel and to update an existing channel's * name, description, and/or importance. * * The name and description should only be changed if the locale changes * or in response to the user renaming this channel. For example, if a user has a channel * named 'John Doe' that represents messages from a 'John Doe', and 'John Doe' changes his name * to 'John Smith,' the channel can be renamed to match. * *
The importance of an existing channel will only be changed if the new importance is lower * than the current value and the user has not altered any settings on this channel. * * All other fields are ignored for channels that already exist. */
public void createNotificationChannel(@NonNull NotificationChannel channel) { createNotificationChannels(Arrays.asList(channel)); } public void createNotificationChannels(@NonNull List<NotificationChannel> channels) { INotificationManager service = getService(); try { service.createNotificationChannels(mContext.getPackageName(), new ParceledListSlice(channels)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
NotificationManager
最终会把信息发送给服务端,服务端的实现在NotificationManagerService.java
中
public class NotificationManagerService extends SystemService { private final IBinder mService = new INotificationManager.Stub() { @Override public void createNotificationChannels(String pkg, ParceledListSlice channelsList) throws RemoteException { // 检查权限,system进程,phone进程,root进程以及当前app有权限 checkCallerIsSystemOrSameApp(pkg); createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList); } private void createNotificationChannelsImpl(String pkg, int uid, ParceledListSlice channelsList) { List<NotificationChannel> channels = channelsList.getList(); final int channelsSize = channels.size(); // 遍历并注册通道 for (int i = 0; i < channelsSize; i++) { final NotificationChannel channel = channels.get(i); // 注册的通道不能为null Preconditions.checkNotNull(channel, "channel in list is null"); // 创建通道 mRankingHelper.createNotificationChannel(pkg, uid, channel, true /* fromTargetApp */); // 通知监听者通道已经改变(监听者包括状态栏) mListeners.notifyNotificationChannelChanged(pkg, UserHandle.getUserHandleForUid(uid), mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false), NOTIFICATION_CHANNEL_OR_GROUP_ADDED); } savePolicyFile(); } } }
通道的通过mRankingHelper
来创建的,前面介绍过这个变量的创建过程,现在直接看下它如何创建通道的
public class RankingHelper implements RankingConfig { public void createNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromTargetApp) {// ...// 1. 获取或者创建Record对象 Record r = getOrCreateRecord(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); } if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) { throw new IllegalArgumentException("NotificationChannelGroup doesn't exist"); } if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { throw new IllegalArgumentException("Reserved id"); } NotificationChannel existing = r.channels.get(channel.getId()); // 如果通道已经存在就更新通道 if (existing != null && fromTargetApp) {// ... } // 通道的importance属性的范围检测 if (channel.getImportance() < NotificationManager.IMPORTANCE_NONE || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) { throw new IllegalArgumentException("Invalid importance level"); } // 2. 根据创建的Record对象更新channel的属性(这里覆盖了外部API调用给channel设置的这些属性) if (fromTargetApp) { // false channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); // r.visibility值为NotificationManager.VISIBILITY_NO_OVERRIDE channel.setLockscreenVisibility(r.visibility); } clearLockedFields(channel); if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) { channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE); } // r.showBadge值为true if (!r.showBadge) { channel.setShowBadge(false); } // 3. 用创建的Record对象保存channel r.channels.put(channel.getId(), channel); MetricsLogger.action(getChannelLog(channel, pkg).setType( MetricsProto.MetricsEvent.TYPE_OPEN)); }}
创建通道的过程大致分为三步,首先看第一步,利用包名和用户ID创建Record
对象
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT; private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE; private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED; private static final boolean DEFAULT_SHOW_BADGE = true; private Record getOrCreateRecord(String pkg, int uid) { return getOrCreateRecord(pkg, uid, DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE); } private Record getOrCreateRecord(String pkg, int uid, int importance, int priority, int visibility, boolean showBadge) { // 生成唯一的key值,形式为pkg + "|" + uid final String key = recordKey(pkg, uid); synchronized (mRecords) { // 从缓存中获取Record对象 Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get( key); // 如果是首次注册通道,r为null if (r == null) { // 创建Record对象,并给它的变量赋值 r = new Record(); r.pkg = pkg; r.uid = uid; r.importance = importance; r.priority = priority; r.visibility = visibility; r.showBadge = showBadge; try { // 这里是为了兼容版本处理,如果是Android 8.0以上的版本,这里不做任何处理 createDefaultChannelIfNeeded(r); } catch (NameNotFoundException e) { Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e); }// 把创建的Record对象保存到mRecords中 if (r.uid == Record.UNKNOWN_UID) { mRestoredWithoutUids.put(pkg, r); } else { mRecords.put(key, r); } } return r; } }
对于首次注册通道的应用,需要创建Record
对象,然后保存到mRecords
中,我们可以注意到,对Record
变量的某些属性进行赋值的时候使用了一些默认的值,例如importance
, priority
属性等等。
有了Record
对象后,现在就到了createNotificationChannel()
的第二步,利用这个Record
对象更新通道的属性,这里可以对比创建Record
对象使用的默认属性来分析,然后我们需要注意的是,这些属性其实都可以利用API设置,但是这里显然是覆盖了这些属性。然后就是第三步,用创建的Record
对象保存了channel
。
至此,通知通道的注册已经分析完毕,就是保存数据,大致步骤如下
- 为每个应用创建一个
Record
对象,并保存到mRecords
中 - 用这个创建出来的
Record
对象更新NotificationChannel
的某些属性,例如showBage
,lockscreenVisibility
等等。 - 最后用这个创建的
Record
对象保存NotificationChannel
。
发送通知
通知创建完毕后,就可以向通道发送通知了,示例代码如下。
private void showNotification() { Notification notification = new Notification.Builder(this, CHANNEL_ID) .setSmallIcon(R.mipmap.ic_launcher_round) .setContentTitle("My notification") .setContentText("Hello world!") .build(); NotificationManager notificationManager = getSystemService(Context.NOTIFICATION); notificationManager.notify(110, notification); }
从Android 8.0开始,在构建Notification
对象的时候,必须要传入通知通道的ID,这点可以从上面代码的Notification.Builder
的构造函数中发现。
我们先来看下如何构建Notification
对象,这里使用的是Builder
模式
public class Notification implements Parcelable{private Notification mN; public static class Builder { public Builder(Context context, String channelId) { this(context, (Notification) null); mN.mChannelId = channelId; } public Builder(Context context, Notification toAdopt) { mContext = context; Resources res = mContext.getResources(); // true mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);// false if (res.getBoolean(R.bool.config_enableNightMode)) { Configuration currentConfig = res.getConfiguration(); mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; }// 传入的toAdopt参数为null if (toAdopt == null) { mN = new Notification(); if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) { mN.extras.putBoolean(EXTRA_SHOW_WHEN, true); } // 0 mN.priority = PRIORITY_DEFAULT; // 0,代表在锁屏界面隐藏隐私信息 mN.visibility = VISIBILITY_PRIVATE; } else {// ... } } }}
Notification.Builder
的对象的构造非常简单,只是给mN
变量赋值,并设置了一些参数。
那么现在看下给这个Builder
对象设置其他一些参数,示例代码中看到设置了三个参数,一个是小图标,一个是标题,一个是内容。先看下标题和内容,这部分比较简单。
public Builder setContentTitle(CharSequence title) { mN.extras.putCharSequence(EXTRA_TITLE, safeCharSequence(title)); return this; } public Builder setContentText(CharSequence text) { mN.extras.putCharSequence(EXTRA_TEXT, safeCharSequence(text)); return this; }
很简单,就是用mN.extras
这个Bundle
对象保存数据。
再来看下如何保存小图标的
public Builder setSmallIcon(@DrawableRes int icon) { return setSmallIcon(icon != 0 ? Icon.createWithResource(mContext, icon) : null); } public Builder setSmallIcon(Icon icon) { mN.setSmallIcon(icon); if (icon != null && icon.getType() == Icon.TYPE_RESOURCE) { mN.icon = icon.getResId(); } return this; }
设置小图标有多种方法,这里分析代码中所展示的情况。可以看到也是用mN
来保存的,只是把资源ID转化为一个Icon
对象保存,可以看下如何转化的
public final class Icon implements Parcelable { private Icon(int mType) { this.mType = mType; } /** * Create an Icon pointing to a drawable resource. */ public static Icon createWithResource(Context context, @DrawableRes int resId) { if (context == null) { throw new IllegalArgumentException("Context must not be null."); } final Icon rep = new Icon(TYPE_RESOURCE); rep.mInt1 = resId; rep.mString1 = context.getPackageName(); return rep; }}
非常简单,就是一个加上包名和资源类型名的一个封装。
Notificatin.Builder
模式已经构建完毕,现在就是利用这个Builder
对象创建Notification
对象了
public class Notification implements Parcelable public static class Builder { public Notification build() { // 示例代码中没有设置过,略过 if (mUserExtras != null) { mN.extras = getAllExtras(); } mN.creationTime = System.currentTimeMillis(); // 其实就是mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, applicationInfo) // 保存ApplicationInfo信息 Notification.addFieldsFromContext(mContext, mN);// 示例代码中没有设置过,略过 buildUnstyled();// 没有设置过Style if (mStyle != null) { mStyle.reduceImageSizes(mContext); mStyle.purgeResources(); mStyle.buildStyled(mN); } // 对大图进行压缩,代码中没有设置过大图,略过 mN.reduceImageSizes(mContext);// 小于N的版本的处理,略过 if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N && (useExistingRemoteView())) {// ... }// 目前还没有设置过DEFAULT_LIGHTS这个标志 if ((mN.defaults & DEFAULT_LIGHTS) != 0) { mN.flags |= FLAG_SHOW_LIGHTS; } return mN; } }}
这里其实也是对Notification.Builder
中设置的参数保存到mN
变量中,为了方便分析,我们假设处理的情况是大于Android N版本的。
现在Notification
对象已经创建完毕,然后使用NotificationManager
发送这个通知
public class NotificationManager { /** * 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. */ public void notify(int id, Notification notification) { notify(null, id, notification); } public void notify(String tag, int id, Notification notification) { notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId())); } public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { INotificationManager service = getService(); String pkg = mContext.getPackageName(); // 前面代码中已经保存过参数 Notification.addFieldsFromContext(mContext, notification); // 处理设置过铃声的情况 if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } } // 修复历史遗留的小图标问题 fixLegacySmallIcon(notification, pkg);// 5.0之后,小图标不能为null 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 + ")"); // 压缩大图,前面已经做过 notification.reduceImageSizes(mContext); ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); boolean isLowRam = am.isLowRamDevice();// 根据手机配置是否是low ram,去掉一些参数,按照示例代码分析,这里不做任何事情 final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam); try { // 向服务端发送通知 service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, copy, user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }}
在对Notification
对象做一些处理后,向服务端发送信息,服务端的实现在NotificationManagerService.java
中
接收通知
public class NotificationManagerService extends SystemService { private final IBinder mService = new INotificationManager.Stub() { public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int userId) throws RemoteException { enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(), tag, id, notification, userId); } void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId) {// 检查权限 checkCallerIsSystemOrSameApp(pkg); final int userId = ActivityManager.handleIncomingUser(callingPid, callingUid, incomingUserId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); if (pkg == null || notification == null) { throw new IllegalArgumentException("null not allowed: pkg=" + pkg + " id=" + id + " notification=" + notification); } // The system can post notifications for any package, let us resolve that. // 如果这个应用在系统进程中运行,就返回系统进程的Uid,我们分析的情况不包括这个,因此notificationUid和callingUid是一样的 final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId); // Fix the notification as best we can. try { final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId); Notification.addFieldsFromContext(ai, notification); int canColorize = mPackageManagerClient.checkPermission( android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg); if (canColorize == PERMISSION_GRANTED) { notification.flags |= Notification.FLAG_CAN_COLORIZE; } else { notification.flags &= ~Notification.FLAG_CAN_COLORIZE; } } catch (NameNotFoundException e) { Slog.e(TAG, "Cannot create a context for sending app", e); return; } mUsageStats.registerEnqueuedByApp(pkg); // setup local book-keeping String channelId = notification.getChannelId(); if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) { channelId = (new Notification.TvExtender(notification)).getChannelId(); } // 这里检查是否已经注册过通知通道 final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg, notificationUid, channelId, false /* includeDeleted */); if (channel == null) { final String noChannelStr = "No Channel found for " + "pkg=" + pkg + ", channelId=" + channelId + ", id=" + id + ", tag=" + tag + ", opPkg=" + opPkg + ", callingUid=" + callingUid + ", userId=" + userId + ", incomingUserId=" + incomingUserId + ", notificationUid=" + notificationUid + ", notification=" + notification; Log.e(TAG, noChannelStr); doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" + "Failed to post notification on channel \"" + channelId + "\"\n" + "See log for more details"); return; }// 创建与状态栏相关的StatusBarNotification对象 final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, notificationUid, callingPid, notification, user, null, System.currentTimeMillis()); final NotificationRecord r = new NotificationRecord(getContext(), n, channel);// 前台服务的情况,略过 if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0 && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0 && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) { }// 检测通知是否合格,例如应用发送通知的数量。这里假设通知是合格的 if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r, r.sbn.getOverrideGroupKey() != null)) { return; } // 暂时没有用到,略过 if (notification.allPendingIntents != null) {// ... }// 发送一个消息来处理通知 mHandler.post(new EnqueueNotificationRunnable(userId, r)); } }}
代码很多,与我们分析相关的就是两点
- 创建
StatusBarNotification
对象,创建NotificationRecord
对象。创建对象所做的是变量的初始化,后面如果需要这些变量,可再查阅。 - 发送一个消息,处理
NotificationRecord
对象
现在来看下EnqueueNotificationRunnable
的处理流程
protected 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 (mNotificationLock) { // 1. 保存 mEnqueuedNotifications.add(r); // 处理创建通知时设置过超时参数的情况,我们没有设置过,这里分析略过 scheduleTimeoutLocked(r); final StatusBarNotification n = r.sbn; if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey()); NotificationRecord old = mNotificationsByKey.get(n.getKey()); // 目前的情况为null 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(); // Handle grouped notifications and bail out early if we // can to avoid extracting signals. // 处理分组的通知,这里略过 handleGroupedNotificationLocked(r, old, callingUid, callingPid); // if this is a group child, unsnooze parent summary if (n.isGroup() && notification.isGroupChild()) { mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey()); }// ...// 2. 提取通知的各种信息,并跟新给参数r,相当于更新信息 mRankingHelper.extractSignals(r);// 3. 再次发送一个消息,处理更新后的NotificationRecord // tell the assistant service about the notification if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueued(r); mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), DELAY_FOR_ASSISTANT_TIME); } else { mHandler.post(new PostNotificationRunnable(r.getKey())); } } } }
为了减少不必要的代码干扰,这里的流程分为了三步,第一步就是用mEnqueuedNotifications
保存这个NotificationRecord
对象。
第二步提取通知的各种信息,并更新给参数r。
public class RankingHelper implements RankingConfig { public void extractSignals(NotificationRecord r) { final int N = mSignalExtractors.length; for (int i = 0; i < N; i++) { NotificationSignalExtractor extractor = mSignalExtractors[i]; try { RankingReconsideration recon = extractor.process(r); // 如果recon不为null,代表无法处理的情况 if (recon != null) { mRankingHandler.requestReconsideration(recon); } } catch (Throwable t) { Slog.w(TAG, "NotificationSignalExtractor failed.", t); } } }}
mRankingHelper
在构造的时候,会给数组mSignalExtractors
填充数据,这个数组指向的是如下类的对象。
frameworks/base/core/res/res/values/config.xml
<string-array name="config_notificationSignalExtractors"> <item>com.android.server.notification.NotificationChannelExtractoritem> <item>com.android.server.notification.NotificationAdjustmentExtractoritem> <item>com.android.server.notification.ValidateNotificationPeopleitem> <item>com.android.server.notification.PriorityExtractoritem> <item>com.android.server.notification.ImportanceExtractoritem> <item>com.android.server.notification.NotificationIntrusivenessExtractoritem> <item>com.android.server.notification.VisibilityExtractoritem> <item>com.android.server.notification.BadgeExtractoritem> string-array>
利用这些类对NotificationRecord
进行处理,可以挑选其中的一个类再看下处理过程,我们挑选ImportanceExtractor
类
/** * Determines the importance of the given notification. */public class ImportanceExtractor implements NotificationSignalExtractor { public RankingReconsideration process(NotificationRecord record) { if (record == null || record.getNotification() == null) { if (DBG) Slog.d(TAG, "skipping empty notification"); return null; } if (mConfig == null) { if (DBG) Slog.d(TAG, "missing config"); return null; } record.setUserImportance(record.getChannel().getImportance()); return null; }}
利用通知通道的importance
属性更新参数NotificationRecord record
的属性。
更新了信息后,就到了EnqueueNotificationRunnable
的第三步,这里会再次发送一个信息,看下这个消息的处理
protected class PostNotificationRunnable implements Runnable { private final String key; PostNotificationRunnable(String key) { this.key = key; } @Override public void run() { synchronized (mNotificationLock) { try { // 1. 找到key对应的NotificationRecord对象 NotificationRecord r = null; int N = mEnqueuedNotifications.size(); for (int i = 0; i < N; i++) { final NotificationRecord enqueued = mEnqueuedNotifications.get(i); if (Objects.equals(key, enqueued.getKey())) { r = enqueued; break; } } if (r == null) { Slog.i(TAG, "Cannot find enqueued record for key: " + key); return; }// old为null NotificationRecord old = mNotificationsByKey.get(key); final StatusBarNotification n = r.sbn; final Notification notification = n.getNotification(); // 2. 用mNotificationList进行保存 int index = indexOfNotificationLocked(n.getKey()); if (index < 0) { // 如果不存在就保存到mNotificationList中 mNotificationList.add(r); mUsageStats.registerPostedByApp(r); } else { // 如果存在就更新集合中的值 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; }// 3. 用mNotificationsByKey保存 mNotificationsByKey.put(n.getKey(), r); // Ensure if this is a foreground service that the proper additional // flags are set. // 处理前台service情况 if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) { notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR; }// 添加zen mode的一些信息 applyZenModeLocked(r); // 4. 排序 mRankingHelper.sort(mNotificationList); if (notification.getSmallIcon() != null) { // 如果不是更新通知,oldSbn为null StatusBarNotification oldSbn = (old != null) ? old.sbn : null; // 5. 通知所有的监听者,有通知到来 mListeners.notifyPostedLocked(n, oldSbn); // 处理分组的通知,这里忽略 if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) { mHandler.post(new Runnable() { @Override public void run() { mGroupHelper.onNotificationPosted( n, hasAutoGroupSummaryLocked(n)); } }); } } else {// ... }// 6. 处理通知的声音以及led灯闪烁情况 buzzBeepBlinkLocked(r); } finally { // 7. 处理完毕后,就从mEnqueuedNotifications中移除 int N = mEnqueuedNotifications.size(); for (int i = 0; i < N; i++) { final NotificationRecord enqueued = mEnqueuedNotifications.get(i); if (Objects.equals(key, enqueued.getKey())) { mEnqueuedNotifications.remove(i); break; } } } } } }
第一步,从mEnqueuedNotifications
中找到已经处于队列中的NotificationRecord
。
第二步和第三步,分别用mNotificationList
和mNotificationsByKey
进行保存找到的NotificationRecord
对象。
第四步,对mNotificationList
进行排序,这个排序是根据通知的各个属性进行排序的,以后如果分析通知的显示顺序的时候,再来重点分析。
第五步,把通知发送给所有的监听者。
第六步,处理通知的声音以及led灯闪烁情况。
第七步,我们需要注意,发送了通知以后,需要从mEnqueuedNotifications
队列中移除。
现在重点关注第五步,看它是如何通知发送给相关的监听者的,mListeners
是 NotificationManagerService
的一个内部类 NotificationListeners
的对象。
public class NotificationManagerService extends SystemService { protected List<ManagedServiceInfo> getServices() { synchronized (mMutex) { List<ManagedServiceInfo> services = new ArrayList<>(mServices); return services; } } public class NotificationListeners extends ManagedServices { /** * asynchronously notify all listeners about a new notification * * Also takes care of removing a notification that has been visible to a listener before, * but isn't anymore. */ public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) { // Lazily initialized snapshots of the notification. TrimCache trimCache = new TrimCache(sbn);// 1. 遍历注册过的服务信息 for (final ManagedServiceInfo info : getServices()) { // 如果是系统注册的监听器,那么这里返回的就是true,例如状态栏注册的监听器就是如此 boolean sbnVisible = isVisibleToListener(sbn, info); boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false; // 如果新旧通知都不可见,就跳过 if (!oldSbnVisible && !sbnVisible) { continue; }// 2. 把mNotificationList中的所有信息包装成NotificationRankingUpdate对象 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);// 3. 发送消息给监听者 // 3.1 如果新通知不可见,就移除旧的通知,然后跳过 if (oldSbnVisible && !sbnVisible) { final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight(); mHandler.post(new Runnable() { @Override public void run() { notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED); } }); continue; }// 3.2 如果以上条件都不满足,就通知监听者 final StatusBarNotification sbnToPost = trimCache.ForListener(info); mHandler.post(new Runnable() { @Override public void run() { notifyPosted(info, sbnToPost, update); } }); } } private void notifyPosted(final ManagedServiceInfo info, final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { final INotificationListener listener = (INotificationListener) info.service; StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn); try { listener.onNotificationPosted(sbnHolder, rankingUpdate); } catch (RemoteException ex) { Log.e(TAG, "unable to notify listener (posted): " + listener, ex); } } }}
通过遍历注册过的服务信息找到监听者,然后提取保存的通知的信息发送给它们。通知肯定会显示在下拉状态栏中,那么状态栏肯定是其中一个监听者,那么状态栏是如何注册称为监听者,以及状态栏如何显示通知的,请听下回分解。
更多相关文章
- Android 删除 未接来电 通知
- Android 发送通知
- Android intent 传递数组对象序列化
- Android自动化工具Monkeyrunner使用(六) —— 根据ID查找对象
- 【notification】Android 中创建震动通知
- Android 技巧 - notification center 发出通知时显示文字
- Android 4.2 webview注入js对象时需要注意的问题
- android基础学习--->Android SharedPreferences存储对象和图片(An
- Android消息通知-Notification