Notification源碼分析
- Android 0 以上發送一條消息的示例代碼:
@RequiresApi(api = Build.VERSION_CODES.O)
public void sendNotification(View view) {
String id = "channel_1";
String des = "101";
NotificationChannel channel = new NotificationChannel(id, des, NotificationManager.IMPORTANCE_MIN);
notificationManager.createNotificationChannel(channel);
Notification notification = new Notification.Builder(MainActivity.this, id)
.setContentTitle("Base Notification View")
.setContentText("您有一條新通知")
.setSmallIcon(R.drawable.jd_icon)
.setStyle(new Notification.MediaStyle())
.setAutoCancel(false)
.build();
notificationManager.notify(1, notification);
}
- 發送通知的源碼
應用調用Notification.notify()接口發送通知,然後會進入system_server進程,調用NotificationManagerService的enqueueNotificationWithTag()。
@UnsupportedAppUsage
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
//獲取NotificationManagerService
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
////主要是向notification加入應用信息和用戶ID
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
//修復一些icon問題,如果沒有就給他指定一個
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 {
//通知信息發送給NotificationManagerService
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- 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 incomingUserId) {
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
//檢查一下通知是否由系統發送過來的還是同一個通知發送過來的,會獲取通知信息提交者的uid,
//並與PackageManager中獲取分配給擁有指定包名的應用程序UID比對
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;
}
//創建一個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);
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;
}
// 白名單待處理的intent
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執行EnqueueNotificationRunnable
mHandler.post(new EnqueueNotificationRunnable(userId, r));
}
- 通知消息具體的權限攔截代碼
private int resolveNotificationUid(String opPackageName, int callingUid, int userId) {
// The system can post notifications on behalf of any package it wants
//系統應用發出的通知不受限制
if (isCallerSystemOrPhone() && opPackageName != null && !"android".equals(opPackageName)) {
try {
return getContext().getPackageManager()
.getPackageUidAsUser(opPackageName, userId);
} catch (NameNotFoundException e) {
/* ignore */
}
}
return callingUid;
}
/*
*
*檢查通知的規則
* Has side effects.
*/
private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag,
NotificationRecord r, boolean isAutogroup) {
final String pkg = r.sbn.getPackageName();
final boolean isSystemNotification =
isUidSystemOrPhone(callingUid) || ("android".equals(pkg));
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
//除了系統的通知或者是來自於註冊監聽的通知, 限制所有的通知數量, 防止DOS攻擊和處理泄露
if (!isSystemNotification && !isNotificationFromListener) {
synchronized (mNotificationLock) {
if (mNotificationsByKey.get(r.sbn.getKey()) == null && isCallerInstantApp(pkg)) {
throw new SecurityException("Instant app " + pkg
+ " cannot create notifications");
}
// rate limit updates that aren't completed progress notifications
if (mNotificationsByKey.get(r.sbn.getKey()) != null
&& !r.getNotification().hasCompletedProgress()
&& !isAutogroup) {
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 " + r.sbn.getKey() + ". package=" + pkg);
mLastOverRateLogTime = now;
}
return false;
}
}
// limit the number of outstanding notificationrecords an app can have
int count = getNotificationCountLocked(pkg, userId, id, tag);
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
mUsageStats.registerOverCountQuota(pkg);
Slog.e(TAG, "Package has already posted or enqueued " + count
+ " notifications. Not showing more. package=" + pkg);
return false;
}
}
}
// snoozed apps
if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
MetricsLogger.action(r.getLogMaker()
.setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
if (DBG) {
Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
}
mSnoozeHelper.update(userId, r);
savePolicyFile();
return false;
}
// blocked apps
if (isBlocked(r, mUsageStats)) {
return false;
}
return true;
}
protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
final String pkg = r.sbn.getPackageName();
final int callingUid = r.sbn.getUid();
// blocked apps
// 這裏是blocked的apps,就是你在settings中選擇Block all的app,這裏就會幫通知都屏蔽了
final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
if (isPackageSuspended) {
Slog.e(TAG, "Suppressing notification from package due to package "
+ "suspended by administrator.");
usageStats.registerSuspendedByAdmin(r);
return isPackageSuspended;
}
final boolean isBlocked =
mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
|| mRankingHelper.getImportance(pkg, callingUid)
== NotificationManager.IMPORTANCE_NONE
|| r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
if (isBlocked) {
Slog.e(TAG, "Suppressing notification from package by user request.");
usageStats.registerBlocked(r);
}
return isBlocked;
}
上面的代碼主要是過濾特殊通知處理,創建了StatusBarNotification、NotificationRecord對象,並防止DOS攻擊,對單個應用(非系統應用)的通知條數做了未讀消息只有50條的限制。
- PostNotificationRunnable進行最後的推送通知
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) {
mEnqueuedNotifications.add(r);
scheduleTimeoutLocked(r);
final StatusBarNotification n = r.sbn;
if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
//根據key值獲取Old NotificationRecord
NotificationRecord old = mNotificationsByKey.get(n.getKey());
if (old != null) {
// 保留以前記錄中的排序信息
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());
}
// 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;
}
//打印Event Log,可通過該LOG查看當前通知的信息,判斷當前通知是否發送成功;
EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
pkg, id, tag, userId, notification.toString(),
enqueueStatus);
}
//// 更新NotificationRecord內容,RankingHelper管理了通知的開關、channel維護,比如橫幅通知是否開啓,在此處更新;
mRankingHelper.extractSignals(r);
//
if (mAssistants.isEnabled()) {
mAssistants.onNotificationEnqueued(r);
mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
DELAY_FOR_ASSISTANT_TIME);
} else {
mHandler.post(new PostNotificationRunnable(r.getKey()));
}
}
}
}
NotificationListeners 的notifyPosted中將通知推送至SystemUI中顯示,在該方法中listener數據類型是NotificationListenerWrapper的代理對象,NotificationListenerWrapper在SystemUI進程,在此處listener是client, NotificationListenerWrapper是server。
- 關於android8.0通知通道的代碼,首先會創建通道,然後通過RankingHelper來更新Channels的信息,最終由NotificationListeners推送到SystemUI中。
@Override
public void createNotificationChannels(String pkg,
ParceledListSlice channelsList) throws RemoteException {
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);
Preconditions.checkNotNull(channel, "channel in list is null");
mRankingHelper.createNotificationChannel(pkg, uid, channel,
true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(uid),
mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
}
//本地存儲信息
savePolicyFile();
}
//NotificationManagerService 在init的時候會調用此方法
private void loadPolicyFile() {
if (DBG) Slog.d(TAG, "loadPolicyFile");
synchronized (mPolicyFile) {
InputStream infile = null;
try {
infile = mPolicyFile.openRead();
readPolicyXml(infile, false /*forRestore*/);
} catch (FileNotFoundException e) {
// No data yet
// Load default managed services approvals
readDefaultApprovedServices(USER_SYSTEM);
} catch (IOException e) {
Log.wtf(TAG, "Unable to read notification policy", e);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Unable to parse notification policy", e);
} catch (XmlPullParserException e) {
Log.wtf(TAG, "Unable to parse notification policy", e);
} finally {
IoUtils.closeQuietly(infile);
}
}
}
public void savePolicyFile() {
mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE);
mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE);
}
private void handleSavePolicyFile() {
if (DBG) Slog.d(TAG, "handleSavePolicyFile");
synchronized (mPolicyFile) {
final FileOutputStream stream;
try {
stream = mPolicyFile.startWrite();
} catch (IOException e) {
Slog.w(TAG, "Failed to save policy file", e);
return;
}
try {
writePolicyXml(stream, false /*forBackup*/);
mPolicyFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Failed to save policy file, restoring backup", e);
mPolicyFile.failWrite(stream);
}
}
BackupManager.dataChanged(getContext().getPackageName());
}
時序圖