写在前面,8.0以后的通知写法如下
最终通过 NotificationManager.notify()发送通知。
1、通知发送流程
相关类
frameworks/base/core/java/android/app/NotificationManager.java
frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
frameworks/base/core/java/android/service/notification/NotificationListenerService.java
packages/apps/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
packages/apps/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
NotificationManager notify() --> notifyAsUser -->
NotificationManagerService 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 incomingUserId) {
... ...
final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
checkRestrictedCategories(notification);
// Fix the notification as best we can.
try {
fixNotification(notification, pkg, userId);
} 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 = mPreferencesHelper.getNotificationChannel(pkg,
notificationUid, channelId, false /* includeDeleted */);
if (channel == null) {
... ... //channel 为NULL,提示并阻止通知发送,所以应用发送通知必需先创建NotificationChannel
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(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
... ...
mHandler.post(new EnqueueNotificationRunnable(userId, r));
}
enqueueNotificationInternal方法内部做一些检查,判断通知是否合法等。
NotificationManagerService.EnqueueNotificationRunnable.run() -->
NotificationManagerService.PostNotificationRunnable.run() -->
NotificationManagerService.NotificationListeners.notifyPostedLocked() -->
NotificationManagerService.NotificationListeners.notifyPosted() -->
INotificationListener.onNotificationPosted() -->
/** @hide */
protected class NotificationListenerWrapper extends INotificationListener.Stub {
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
return;
}
try {
// convert icon metadata to legacy format for older clients
createLegacyIconExtras(sbn.getNotification());
maybePopulateRemoteViews(sbn.getNotification());
maybePopulatePeople(sbn.getNotification());
} catch (IllegalArgumentException e) {
// warn and drop corrupt notification
Log.w(TAG, "onNotificationPosted: can't rebuild notification from " +
sbn.getPackageName());
sbn = null;
}
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
synchronized (mLock) {
applyUpdateLocked(update);
if (sbn != null) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
} else {
// still pass along the ranking map, it may contain other information
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,
mRankingMap).sendToTarget();
}
}
}
... ...
}
... ...
case MSG_ON_NOTIFICATION_POSTED: {
SomeArgs args = (SomeArgs) msg.obj;
StatusBarNotification sbn = (StatusBarNotification) args.arg1;
RankingMap rankingMap = (RankingMap) args.arg2;
args.recycle();
onNotificationPosted(sbn, rankingMap);
} break;
通过 MyHandler.MSG_ON_NOTIFICATION_POSTED,调用 NotificationListenerService.onNotificationPosted ()。
NotificationListener 继承 NotificationListenerWithPlugins 继承 NotificationListenerService。最终实现在NotificationListener中,
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
boolean isUpdate =
mEntryManager.getNotificationData().get(key) != null;
// In case we don't allow child notifications, we ignore children of
// notifications that have a summary, since` we're not going to show them
// anyway. This is true also when the summary is canceled,
// because children are automatically canceled by NoMan in that case.
if (!ENABLE_CHILD_NOTIFICATIONS
&& mGroupManager.isChildInGroupWithSummary(sbn)) {
if (DEBUG) {
Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
}
// Remove existing notification to avoid stale data.
if (isUpdate) {
mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
} else {
mEntryManager.getNotificationData()
.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap);
} else {
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
最后由 NotificationEntryManager.addNotification() --> NotificationEntryManager.addNotificationInternal() 完成通知创建显示。
2、设置APP通知开启/关闭接口
private void disableAppNotification(String packagename) {
INotificationManager mNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
try {
int uid = mPackageManagerService.getPackageUid(packagename, 0, UserHandle.USER_SYSTEM);
//Slog.i("NotificationsEnabled", "PMS getPackageUid disable app pkg=" + packagename + ",uid=" + uid);
mNotificationManager.setNotificationsEnabledForPackage(packagename, uid, false);
} catch (Exception e) {
Slog.e("NotificationsEnabled", "PMS Error calling NoMan" + e);
}
}
NotificationManagerService.setNotificationsEnabledForPackage 设置APP默认通知状态,可在系统启动时或者APK安装时(监听 Intent.ACTION_PACKAGE_ADDED 安装结束广播)进行设置。
@Override
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
enforceSystemOrSystemUI("setNotificationsEnabledForPackage");
mPreferencesHelper.setEnabled(pkg, uid, enabled);
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_BAN_APP_NOTES)
.setType(MetricsEvent.TYPE_ACTION)
.setPackageName(pkg)
.setSubtype(enabled ? 1 : 0));
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (!enabled) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
UserHandle.getUserId(uid), REASON_PACKAGE_BANNED, null);
}
try {
getContext().sendBroadcastAsUser(
new Intent(ACTION_APP_BLOCK_STATE_CHANGED)
.putExtra(NotificationManager.EXTRA_BLOCKED_STATE, !enabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
.setPackage(pkg),
UserHandle.of(UserHandle.getUserId(uid)), null);
} catch (SecurityException e) {
Slog.w(TAG, "Can't notify app about app block change", e);
}
handleSavePolicyFile();
}
通知开启关闭后还会发送一个 ACTION_APP_BLOCK_STATE_CHANGED,应用中可以进行监听判断通知状态变化。
3、强制过滤APP通知
packages/apps/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
packages/apps/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
if (CHATTY) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
// 获取包名和flags是否是常驻通知
String pkg = sbn.getPackageName();
boolean isClearable = (sbn.getNotification().flags
& Notification.FLAG_AUTO_CANCEL) == Notification.FLAG_AUTO_CANCEL;
// 根据通知的包名或者flags来过滤
if (!isClearable /*"com.xxx.xxx".equals(pkg)*/) {
return null;
}
NotificationData.Entry entry = new NotificationData.Entry(sbn);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
inflateViews(entry, mListContainer.getViewParentForNotification(entry));
return entry;
}
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) throws InflationException {
String key = notification.getKey();
if (DEBUG) Log.d(TAG, "addNotification key=" + key);
mNotificationData.updateRanking(ranking);
NotificationData.Entry shadeEntry = createNotificationViews(notification);
// 判断是否创建了通知
if (shadeEntry == null) {
return;
}
boolean isHeadsUped = shouldPeek(shadeEntry);
... ...
}
调用顺序是从 NotificationListener.onNotificationPosted --> NotificationEntryManager.addNotification --> addNotificationInternal ,最终创建显示通知布局。通过 StatusBarNotification 对象的属性值获知通知的包名及通知的flags,判断是否需要过滤。
常用的通知的flags如下
public static final int FLAG_SHOW_LIGHTS = 0x00000001;//设置闪光
public static final int FLAG_ONGOING_EVENT = 0x00000002;//将flag设置为这个属性那么通知就会常驻
public static final int FLAG_INSISTENT = 0x00000004;//重复发出声音,直到用户响应此通知
public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008;//标记声音或者震动一次
public static final int FLAG_AUTO_CANCEL = 0x00000010;//在通知栏上点击此通知后自动清除此通知
public static final int FLAG_NO_CLEAR = 0x00000020;//将flag设置为该值,则通知栏的清楚按钮不会出现
public static final int FLAG_FOREGROUND_SERVICE = 0x00000040;//前台服务标记
public static final int FLAG_HIGH_PRIORITY = 0x00000080;//高权限,已过时