写在前面,8.0以后的通知写法如下
Android 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;//高权限,已过时