应用发送通知

创建通知渠道

1
2
3
4
5
6
// 渠道唯一ID
const val CHANNAL_ID = "channel_id"
//创建 NotificationChannel 对象
var channel = NotificationChannel(CHANNAL_ID, "message", NotificationManager.IMPORTANCE_DEFAULT)
//创建通知渠道
mNotificationManager.createNotificationChannel(channel)

创建并发送通知

创建通知涉及到 NotificationManager 和 NotificationCompat 两个类:

  • NotificationCompat : 用于兼容不同版本的 Notification。
  • NotificationManager : 状态栏通知的管理类,负责发通知,清除通知等操作。
1
2
3
4
5
6
7
8
9
10
11
// 获取 NotificationManager 对象来管理通知
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//通过创建 NotificationCompat.Builder 对象并调用相应属性的方法来设置通知的属性
val mBuilder = NotificationCompat.Builder(context,CHANNAL_ID)
.setContentTitle("这是通知标题")
.setContentText("这是通知内容")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.wechat)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.wechat))
//发送通知( id唯一,可用于更新通知时对应旧通知; 通过mBuilder.build()拿到notification对象 )
mNotificationManager.notify(1, mBuilder.build())

从上边可以看出,通知管理通过 NotificationManager 来实现的。

NotificationManager通知管理

代码位于:\frameworks\base\core\java\android\app\NotificationManager.java
下边是发送通知的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public void notify(int id, Notification notification){
notify(null, id, notification);
}

public void notify(String tag, int id, Notification notification){
notifyAsUser(tag, id, notification, mContext.getUser());
}

/**
* @hide
*/
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user){
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
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 + ")");
notification.reduceImageSizes(mContext);

ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
boolean isLowRam = am.isLowRamDevice();
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam, mContext);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

发送的代码逻辑一看就明白了,我们继续往下,NotificationManager 是将通知提交到 INotificationManager.enqueueNotificationWithTag 中, INotificationManager.aidl 的实现在 NotificationManagerService 中:

1
2
3
private final IBinder mService = new INotificationManager.Stub() {
......
}

NotificationManagerService 管理通知数据

代码位置: \frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java

通知数据存放在

1
2
3
4
5
6
7
8
9
// 通知列表
@GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mNotificationList = new ArrayList<>();
// 通知列表,有个key值对应
@GuardedBy("mNotificationLock")
final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();
// 通知排队列表
@GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();

enqueueNotificationWithTag

1
2
3
4
5
6
@Override
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);
}

继续 enqueueNotificationInternal

enqueueNotificationInternal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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) {
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
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.
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) ? 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);
boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
== NotificationManager.IMPORTANCE_NONE;

if (!appNotificationsOff) {
doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
"Failed to post notification on channel \"" + channelId + "\"\n" +
"See log for more details");
}
return;
}

final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));

if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
final boolean fgServiceShown = channel.isFgServiceShown();
if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
|| !fgServiceShown)
&& (r.getImportance() == IMPORTANCE_MIN
|| r.getImportance() == IMPORTANCE_NONE)) {
// Increase the importance of foreground service notifications unless the user had
// an opinion otherwise (and the channel hasn't yet shown a fg service).
if (TextUtils.isEmpty(channelId)
|| NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
} else {
channel.setImportance(IMPORTANCE_LOW);
if (!fgServiceShown) {
channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
channel.setFgServiceShown(true);
}
mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
r.updateNotificationChannel(channel);
}
} else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
channel.setFgServiceShown(true);
r.updateNotificationChannel(channel);
}
}

if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.sbn.getOverrideGroupKey() != null)) {
return;
}

// 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(),
WHITELIST_TOKEN, duration);
}
}
}
}

mHandler.post(new EnqueueNotificationRunnable(userId, r));
}

再往后就是 EnqueueNotificationRunnable 线程任务对通知进行排队管理。
在排队管理过后,就是将通知添加到数据列表中,是通过另外个线程任务 PostNotificationRunnable 来完成的。