在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()
方法进行大量的变量初始化,当我们以后需要这些变量的时候再来分析它们初始化过程。
从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);
}
}
}
}
通过遍历注册过的服务信息找到监听者,然后提取保存的通知的信息发送给它们。通知肯定会显示在下拉状态栏中,那么状态栏肯定是其中一个监听者,那么状态栏是如何注册称为监听者,以及状态栏如何显示通知的,请听下回分解。