Android Notifications
Android Notifications简介
Android Notifications,顾名思义就是通知,在谷歌开发文档中是这样描述的:“Notifications are glanceable, time-sensitive information and actions sent to the user.” 即:通知是发送给用户的可浏览、时间敏感的信息和操作。通知是指 Android 在您应用的界面之外显示的消息,旨在向用户提供提醒、来自他人的通信信息或您应用中的其他实时信息。用户可以点按通知来打开应用,或直接从通知中执行操作。
在设备上的外观
通知可以在不同的位置以不同的格式显示,例如,状态栏中的图标、抽屉式通知栏中比较详细的条目、应用图标上的标志,以及在配对的穿戴式设备上自动显示。
状态栏和抽屉式通知栏
发出通知后,通知会先以图标的形式显示在状态栏中
用户可以在状态栏向下滑动以打开抽屉式通知栏,并在其中查看更多详情及对通知执行操作。
用户可以向下拖动抽屉式通知栏中的某条通知以查看展开后的视图,其中会显示更多内容以及操作按钮(如果有)。在应用或用户关闭通知之前,通知会一直显示在抽屉式通知栏中。
提醒式通知
从 Android 5.0 开始,通知可以短暂地显示在浮动窗口中,称之为提醒式通知。这种行为通常适用于用户应立即知晓的重要通知,而且仅在设备未锁定时才会显示。
提醒式通知会在应用发出通知后立即出现,稍后便会消失,但仍照常显示在抽屉式通知栏中。例如,以下情况可能会触发提醒式通知:
用户的 Activity 处于全屏模式(应用使用 fullScreenIntent)。 通知的优先级很高,且在搭载 Android
7.1(API 级别 25)及更低版本的设备上使用铃声或振动。 在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知渠道的重要程度比较高。
锁定屏幕
从 Android 5.0 开始,通知可以显示在锁定屏幕上。
您可以采用编程方式设置您的应用在安全锁定屏幕上所发布通知的详情可见等级,甚至可以设置通知是否显示在锁定屏幕上。
用户可以通过系统设置来选择锁定屏幕通知的详情可见等级,包括选择停用所有锁定屏幕通知。从 Android 8.0开始,用户可以选择停用或启用各个通知渠道的锁定屏幕通知。
通知剖析
通知的设计由系统模板决定,您的应用只需要定义模板中各个部分的内容即可。通知的部分详情仅在展开后的视图中显示。
上图展示了通知最常见的部分,具体如下所示:
- 小图标:必须提供,通过 setSmallIcon() 进行设置。 应用名称:由系统提供。 时间戳:由系统提供,但您可以使用
- setWhen() 替换它或者使用 setShowWhen(false) 隐藏它。
- 大图标:可选内容(通常仅用于联系人照片,请勿将其用于应用图标),通过
- setLargeIcon() 进行设置。 标题:可选内容,通过
- setContentTitle() 进行设置。 文本:可选内容,通过 setContentText() 进行设置。
通知操作
尽管并非强制要求,但每个通知都应在被点按时打开相应的应用 Activity。除了这种默认的通知操作之外,您还可以添加可在通知中完成与应用相关任务的操作按钮(通常不需要打开 Activity),如下图所示。
从 Android 7.0(API 级别 24)开始,您还可以添加直接在通知中回复消息或输入其他文字的操作。
从 Android 10(API 级别 29)开始,平台可以自动生成操作按钮,此类按钮包含基于 intent 的建议操作。
展开式通知
默认情况下,通知的文字内容会被截断以放在一行。如果您需要长一些的通知,可以通过应用其他模板启用更大的展开式文本区域,如下图所示。
通知更新和分组
为了使用户在您提供后续更新时不会遭遇多个通知或多余的通知轰炸,您不妨考虑更新现有通知(而不是发出新通知),或者考虑使用收件箱样式的通知来显示会话更新。
不过,如有必要发出多个通知,则应将这些孤立的通知分为一组(可在搭载 Android 7.0 及更高版本的设备上这样做)。借助通知组,您可以以一条摘要的形式在抽屉式通知栏中将多个通知收拢成一条通知消息。用户便可以展开通知以查看每个通知的详情。
用户可以逐级展开通知组以及其中的每条通知以查看详情。
通知渠道
从 Android 8.0(API 级别 26)开始,必须为所有通知分配渠道,否则通知将不会显示。通过将通知归类到不同的渠道中,用户可以停用您应用的特定通知渠道(而非停用您的所有通知),还可以控制每个渠道的视觉和听觉选项,所有这些操作都在 Android 系统设置中完成(如图 11 所示)。用户还可以长按通知以更改所关联渠道的行为。
在搭载 Android 7.1(API 级别 25)及更低版本的设备上,用户仅可以按应用来管理通知(在搭载 Android 7.1 及更低版本的设备上,每个应用其实只有一个渠道)。
一个应用可以有多个通知渠道(每个渠道对应于该应用发出的每类通知)。应用还可以创建通知渠道来响应您应用的用户做出的选择。例如,您可以为用户在短信应用中创建的每个会话组设置单独的通知渠道。
在搭载 Android 8.0 及更高版本的设备上,渠道还可用于指定通知的重要程度等级。因此,发布到同一通知渠道的所有通知的行为都相同。
通知的重要程度
Android 利用通知的重要程度来决定通知应在多大程度上干扰用户(视觉上和听觉上)。通知的重要程度越高,干扰程度就越高。
在搭载 Android 8.0(API 级别 26)及更高版本的设备上,通知的重要程度由通知发布到的渠道的 importance 决定。用户可以在系统设置中更改通知渠道的重要程度(图 12)。 在搭载 Android 7.1(API 级别 25)及更低版本的设备上,每条通知的重要程度均由通知的 priority 决定。
可能的重要程度等级如下所示:
紧急:发出提示音,并以提醒式通知的形式显示。 高:发出提示音。 中:无提示音。 低:无提示音,且不会在状态栏中显示。
无论重要程度如何,所有通知都会在非干扰性的系统界面位置显示,例如,显示在抽屉式通知栏中,以及在启动器图标上作为标志显示
以上只是简单的展示了notification的一些特性,至于具体怎么使用在谷歌开发文档中有着详细的描述,这里就不再赘述。
Notification框架
Notification的框架总体上可任意分为三部分::系统服务端NotificationManagerService,通知显示端SystemUI,还有创建和更新通知的App端。
NotificationManager服务的注册过程
在系统开机时,SystemServer会拉起NotificationManagerService,并把这个服务注册到SystemServiceManager中,由SystemServiceManager进行管理(大多数的系统服务都是这样的),之后需要通知服务的应用可以通过Binder机制,通过SystemServiceManager获得NotificationManagerService的一个动态代理,实现对NotificationManagerService的调用。
,但是绑定到ServiceManager中Context.NOTIFICATION_
SERVICE的服务类是NotificationManager,所有开发者通过Context.getSystemService(Context.NOTIFICATION_SERVICE)获取回来的服务类不是NotificationManagerServiced服务对象,而是NotificationManager对象,需要再通过NotificationManager对象中的getService()方法,获取SystemServiceManager系统服务管理对象中保存的INotificationManager.Stub()对象。这样NotificationManager就能通过INotificationManager.Stub()对象和NotificationManagerService服务对象进行远程通信了。
NotificationManagerServiced.javapublic void onStart() {SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {....// 在这里把NotificationManagerService注册为系统服务,供使用者获得publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);publishLocalService(NotificationManagerInternal.class, mInternalService);....}
1.从简单使用中看如何获取NotificationManagerServiced服务
如下,notify将通知发出之后,会通过跨进程调用,获得NotificationManager的服务代理对象,
fun realSendNotification() {// 在这里只是获取到NotificationManager, 在NotificationManager会获取// NotificationManagerServiced服务的var manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManagerval hangIntent = Intent(this, MainActivity::class.java)val hangPendingIntent =PendingIntent.getActivity(this, 1001, hangIntent, PendingIntent.FLAG_UPDATE_CURRENT)val CHANNEL_ID = "your_custom_id" //应用频道Id唯一值, 长度若太长可能会被截断,val CHANNEL_NAME = "your_custom_name" //最长40个字符,太长会被截断//显然采用了建造者模式var notification = NotificationCompat . Builder (this, CHANNEL_ID).setContentTitle(mBinding?.vm?.notificationText.toString()).setContentText("点我返回应用").setSmallIcon(R.mipmap.ic_launcher).setContentIntent(hangPendingIntent).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.img)).setAutoCancel(true).build()val notificationChannel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel(CHANNEL_ID,CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW)} else {TODO("VERSION.SDK_INT < O")}manager.createNotificationChannel(notificationChannel)manager.notify(1000, notification);}
NotificationManagerService服务(截取部分代码)
以下代码为NotificationManagerService 类的部分代码,一些分析具体见注释
public class NotificationManagerService extends SystemService {// 该函数会在NotificationManagerService 服务被拉起时调用@Overridepublic void onStart() {mListeners = new NotificationListeners();//绑定和发布key为Context.NOTIFICATION_SERVICE的NotificationManager系统服务类publishBinderService(Context.NOTIFICATION_SERVICE, mService);//添加到内部系统服务类的集合类LocalServices中publishLocalService(NotificationManagerInternal.class, mInternalService);}//只开放给系统内部调用的API// manager.notify发出一个通知后最终会调到这里private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {@Overridepublic void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,String tag, int id, Notification notification, int[] idReceived, int userId) {enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,idReceived, userId);}@Overridepublic void removeForegroundServiceFlagFromNotification(String pkg, int notificationId,int userId) {checkCallerIsSystem();synchronized (mNotificationList) {int i = indexOfNotificationLocked(pkg, null, notificationId, userId);if (i < 0) {return;}NotificationRecord r = mNotificationList.get(i);StatusBarNotification sbn = r.sbn;sbn.getNotification().flags =(r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE);mRankingHelper.sort(mNotificationList);mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */);}}};//INotificationManager.Stub用于与NotificationManager类和SystemUI进程进行远程通信的桩 (或者其它模块)private final IBinder mService = new INotificationManager.Stub() {@Overridepublic void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,Notification notification, int[] idOut, int userId) throws RemoteException {enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),Binder.getCallingPid(), tag, id, notification, idOut, userId); //添加或更新Notification}@Overridepublic void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {checkCallerIsSystemOrSameApp(pkg);userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,Binder.getCallingUid() == Process.SYSTEM_UID? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId, REASON_NOMAN_CANCEL,null); //删除Notification}@Overridepublic void cancelAllNotifications(String pkg, int userId) {checkCallerIsSystemOrSameApp(pkg);userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),Binder.getCallingUid(), userId, true, false, "cancelAllNotifications", pkg);cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),pkg, 0, Notification.FLAG_FOREGROUND_SERVICE, true, userId,REASON_NOMAN_CANCEL_ALL, null); //删除所有Notification}// 一个notification从app发出后,经过NotificationManagerService后// 最后会发给接收者(例如SystemUI),这些接收者将在这里注册@Overridepublic void registerListener(final INotificationListener listener,final ComponentName component, final int userid) {enforceSystemOrSystemUI("INotificationManager.registerListener");//把NotificationListenerService对象注册到ManagedServices服务管理子类NotificationListeners对象中mListeners.registerService(listener, component, userid);}@Overridepublic void unregisterListener(INotificationListener listener, int userid) {//把NotificationListenerService对象从ManagedServices服务管理子类NotificationListeners对象中解除mListeners.unregisterService(listener, 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[] idOut, int incomingUserId) {//...mHandler.post(new Runnable() {@Overridepublic void run() {synchronized (mNotificationList) {//...//调用服务管理对象mListeners来更新所有注册到mListeners中的NotificationListenerService对象mListeners.notifyPostedLocked(n, oldSbn);//实现播放notification的铃声,使led灯亮起来或者震动等操作。buzz:嗡嗡叫,beep: 嘟嘟响,blink: 闪烁buzzBeepBlinkLocked(r);}}});idOut[0] = id;}//ManagedServices服务管理类,就是用于一类服务的管理对象,例如:如需要管理几个同一类的服务对象NotificationListenerService//只需要把相关的NotificationListenerService对象注册到ManagedServices服务管理对象中,需要更新的时候,只需要调用//ManagedServices服务管理对象对注册的NotificationListenerService对象进行更新即可public class NotificationListeners extends ManagedServices { public NotificationListeners() {super(getContext(), mHandler, mNotificationList, mUserProfiles);}@Overrideprotected IInterface asInterface(IBinder binder) {return INotificationListener.Stub.asInterface(binder);}//新添加的方法,调用这个方法,就会更新所有注册进来的NotificationListenerService对象来更新//调用服务管理对象mListeners来更新所有注册到mListeners中的NotificationListenerService对象public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {StatusBarNotification sbnClone = null;StatusBarNotification sbnCloneLight = null;for (final ManagedServiceInfo info : mServices) {//...if (trim == TRIM_LIGHT && sbnCloneLight == null) {sbnCloneLight = sbn.cloneLight();} else if (trim == TRIM_FULL) {sbnClone = sbn.clone();}final StatusBarNotification sbnToPost =(trim == TRIM_FULL) ? sbnClone : sbnCloneLight;mHandler.post(new Runnable() {@Overridepublic void run() {notifyPosted(info, sbnToPost, update); //调用更新通知方法}});}}public void notifyRemovedLocked(StatusBarNotification sbn) {final StatusBarNotification sbnLight = sbn.cloneLight();for (final ManagedServiceInfo info : mServices) {final NotificationRankingUpdate update = makeRankingUpdateLocked(info);mHandler.post(new Runnable() {@Overridepublic void run() {notifyRemoved(info, sbnLight, update);}});}}//调用更新通知方法private void notifyPosted(final ManagedServiceInfo info,final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {final INotificationListener listener = (INotificationListener)info.service;StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);try {//回调NotificationListenerService对象中的方法onNotificationPosted(),在SystemUI中显示Notificationlistener.onNotificationPosted(sbnHolder, rankingUpdate);} catch (RemoteException ex) {Log.e(TAG, "unable to notify listener (posted): " + listener, ex);}}private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,NotificationRankingUpdate rankingUpdate) {if (!info.enabledAndUserMatches(sbn.getUserId())) {return;}final INotificationListener listener = (INotificationListener) info.service;StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);try {listener.onNotificationRemoved(sbnHolder, rankingUpdate);} catch (RemoteException ex) {Log.e(TAG, "unable to notify listener (removed): " + listener, ex);}}//...}
}//系统服务类,NotificationManagerService的父类
/android/frameworks/base/services/core/java/com/android/server/notification/ManagedServices.java abstract protected IInterface asInterface(IBinder binder);protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();abstract public class ManagedServices {abstract protected void onServiceAdded(ManagedServiceInfo info);protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }//把Service对象从ManagedServices服务管理类对象中删除public void unregisterService(IInterface service, int userid) {checkNotNull(service);unregisterServiceImpl(service, userid);}//把Service对象注册到ManagedServices服务管理类对象中public void registerService(IInterface service, ComponentName component, int userid) {checkNotNull(service);ManagedServiceInfo info = registerServiceImpl(service, component, userid);if (info != null) {onServiceAdded(info);}}private ManagedServiceInfo registerServiceImpl(final IInterface service,final ComponentName component, final int userid) {synchronized (mMutex) {try {ManagedServiceInfo info = newServiceInfo(service, component, userid,true /*isSystem*/, null, Build.VERSION_CODES.LOLLIPOP);service.asBinder().linkToDeath(info, 0);mServices.add(info);return info;} catch (RemoteException e) {}}return null;}private void unregisterServiceImpl(IInterface service, int userid) {ManagedServiceInfo info = removeServiceImpl(service, userid);if (info != null && info.connection != null) {mContext.unbindService(info.connection);}}
}
SystemUI服务与NotificationManagerService的绑定
SystemUI的启动不是本文的重点,这篇文章将跳过SystemUI服务的启动过程,直接看看SystemUI服务与NotificationManagerService是如何绑定的。
SystemUI进程在初始化过程中,会创建一个NotificationListenerService服务类,服务对象中创建一个INotificationListener(这个在上文中也有描述)对象并通过远程过程调用把这个INotificationListener对象注册到NotificationManagerService服务对象的服务管理类子类NotificationListeners对象mListeners中
NotificationManagerService类只是管理Notification的逻辑,显示端是在SystemUI进程中实现的,管理端和显示端处于不同的进程,他们之间的通过binder完成的,,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。所以我们先来看下NotificationManagerService服务和SystemUI进程通信的服务接口文件
在上面的代码中展示了notificaition的简单使用,显然这个notificaition的创建采用了建造者模式,即通过.setxxx(),来定义相关属性,最后通过.build()构造出一个notification。最后在notification处将这个通知发送出去。
流程分析
下面从源码中分析流程
notify()最后通过 service.enqueueNotificationWithTag()将通知传给了NotificationManagerService,具体分析见代码注释。这里的是 service 是 INotificationManager 接口。如果熟 悉 AIDL 等系统相关运行机制的话,就可以看出这里是代理类调用了代理接口的方法,实际方法实现是在 NotificationManagerService 当中。
NotificationManager.java//调用到这里的notifypublic 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());}...// 中间省略了一些调用@UnsupportedAppUsagepublic void notifyAsUser(String tag, int id, Notification notification, UserHandle user){// 获得NotificationManagerService的代理对象INotificationManager service = getService();String pkg = mContext.getPackageName();try {if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");// 调用 NotificationManagerService.enqueueNotificationWithTag,把// notification 作为参数传进去,同事附加了一些额外信息。service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,fixNotification(notification), user.getIdentifier());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
service.enqueueNotificationWithTag已经在NotificationManagerService中了,属于NotificationManagerService进程了,在NotificationManagerService中经过一些列调用会走到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, boolean postSilently) {if (DBG) {Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id+ " notification=" + notification);}if (pkg == null || notification == null) {throw new IllegalArgumentException("null not allowed: pkg=" + pkg+ " id=" + id + " notification=" + notification);}final int userId = ActivityManager.handleIncomingUser(callingPid,callingUid, incomingUserId, true, false, "enqueueNotification", pkg);final UserHandle user = UserHandle.of(userId);// Can throw a SecurityException if the calling uid doesn't have permission to post// as "pkg"final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);if (notificationUid == INVALID_UID) {throw new SecurityException("Caller " + opPkg + ":" + callingUid+ " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);}// 检查通知是否属于仅限系统使用的类别类型,如果是,则抛出SecurityExceptioncheckRestrictedCategories(notification);// Fix the notification as best we can.try {fixNotification(notification, pkg, tag, id, userId);} catch (Exception e) {if (notification.isForegroundService()) {throw new SecurityException("Invalid FGS notification", e);}Slog.e(TAG, "Cannot fix notification", e);return;}// Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE,// but it's also possible that the app has called notify() with an update to an// FGS notification that hasn't yet been displayed. Make sure we check for any// FGS-related situation up front, outside of any locks so it's safe to call into// the Activity Manager.final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(notification, tag, id, pkg, userId);if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {// Proceed if the notification is already showing/known, otherwise ignore// because the service lifecycle logic has retained responsibility for its// handling.if (!isNotificationShownInternal(pkg, tag, id, userId)) {reportForegroundServiceUpdate(false, notification, id, pkg, userId);return;}}mUsageStats.registerEnqueuedByApp(pkg);// 把Notification包装成StatusBarNotification,加了一些应用信息// user信息等等,后续用于System UI交互final StatusBarNotification n = new StatusBarNotification(pkg, opPkg, id, tag, notificationUid, callingPid, notification,user, null, System.currentTimeMillis());// setup local book-keeping// 获取channel信息,对channel进行规范性检查String channelId = notification.getChannelId();if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {channelId = (new Notification.TvExtender(notification)).getChannelId();}String shortcutId = n.getShortcutId();final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(pkg, notificationUid, channelId, shortcutId,true /* parent ok */, 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;Slog.e(TAG, noChannelStr);boolean appNotificationsOff = mPreferencesHelper.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;}// //Notification封装成StatusBarNotification,添加了一些应用信息,// user信息等等,后续用于System UI交互final NotificationRecord r = new NotificationRecord(getContext(), n, channel);r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));r.setPostSilently(postSilently);r.setFlagBubbleRemoved(false);r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));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.setSystemImportance(IMPORTANCE_LOW);} else {channel.setImportance(IMPORTANCE_LOW);r.setSystemImportance(IMPORTANCE_LOW);if (!fgServiceShown) {channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);channel.setFgServiceShown(true);}mPreferencesHelper.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);}}ShortcutInfo info = mShortcutHelper != null? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user): null;if (notification.getShortcutId() != null && info == null) {Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");}r.setShortcutInfo(info);r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));r.userDemotedAppFromConvoSpace(mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));// /检查应用发送通知的速率,个数等等,决定是否允许继续发送通知if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,r.getSbn().getOverrideGroupKey() != null)) {return;}if (info != null) {// Cache the shortcut synchronously after the associated notification is posted in case// the app unpublishes this shortcut immediately after posting the notification. If the// user does not modify the notification settings on this conversation, the shortcut// will be uncached by People Service when all the associated notifications are removed.mShortcutHelper.cacheShortcut(info, user);}// temporarily allow apps to perform extra work when their pending intents are launchedif (notification.allPendingIntents != null) {final int intentCount = notification.allPendingIntents.size();if (intentCount > 0) {final long duration = LocalServices.getService(DeviceIdleInternal.class).getNotificationAllowlistDuration();for (int i = 0; i < intentCount; i++) {PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);if (pendingIntent != null) {mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(),ALLOWLIST_TOKEN, duration,TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED,REASON_NOTIFICATION_SERVICE,"NotificationManagerService");mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER| FLAG_SERVICE_SENDER));}}}}// Need escalated privileges to get package importancefinal long token = Binder.clearCallingIdentity();boolean isAppForeground;try {isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;} finally {Binder.restoreCallingIdentity(token);}// //发送通知到主线程mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));}
首先检查通知发起者是系统进程或者是查看发起者发送的是否是同个 app 的通知信息,否则抛出异常; 除了系统的通知和已注册的监听器允许入队列外,其他 app 的通知都会限制通知数上限和通知频率上限; 将 notification 的 PendingIntent 加入到白名单; 将之前的 notification 进一步封装为 StatusBarNotification 和 NotificationRecord,最后封装到一个异步线程 EnqueueNotificationRunnable 中。
StatusBarNotification以key作为其唯一标识,定义如下:
private String key() { String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid; if (overrideGroupKey != null && getNotification().isGroupSummary()) { sbnKey = sbnKey + "|" + overrideGroupKey; } return sbnKey; }
例如:0|com.example.mi.xiaomiapp|11223344|null|10159,源码定义如下所示:因为是通过 mHandler.post将消息发送到主线程,所以 EnqueueNotificationRunnable继承自Runnable接口,执行的主要任务在他的run方法中。
protected class EnqueueNotificationRunnable implements Runnable {private final NotificationRecord r;private final int userId;private final boolean isAppForeground;EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {this.userId = userId;this.r = r;this.isAppForeground = foreground;}@Overridepublic void run() {synchronized (mNotificationLock) {// 待补充final Long snoozeAt =mSnoozeHelper.getSnoozeTimeForUnpostedNotification(r.getUser().getIdentifier(),r.getSbn().getPackageName(), r.getSbn().getKey());final long currentTime = System.currentTimeMillis();if (snoozeAt.longValue() > currentTime) {(new SnoozeNotificationRunnable(r.getSbn().getKey(),snoozeAt.longValue() - currentTime, null)).snoozeLocked(r);return;}final String contextId =mSnoozeHelper.getSnoozeContextForUnpostedNotification(r.getUser().getIdentifier(),r.getSbn().getPackageName(), r.getSbn().getKey());if (contextId != null) {(new SnoozeNotificationRunnable(r.getSbn().getKey(),0, contextId)).snoozeLocked(r);return;}// 将通知加入队列mEnqueuedNotifications.add(r);scheduleTimeoutLocked(r);//根据key来查找是否已有相同的Record,有的话保持相同的排序信息//内部结构:final ArrayMap<String, NotificationRecord> //mNotificationsByKey = new ArrayMap<>();final StatusBarNotification n = r.getSbn();if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());NotificationRecord old = mNotificationsByKey.get(n.getKey());if (old != null) {// Retain ranking information from previous recordr.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();// We need to fix the notification up a little for bubblesupdateNotificationBubbleFlags(r, isAppForeground);// Handle grouped notifications and bail out early if we// can to avoid extracting signals.// 处理NotificationGroup的信息handleGroupedNotificationLocked(r, old, callingUid, callingPid);// if this is a group child, unsnooze parent summaryif (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;}EventLogTags.writeNotificationEnqueue(callingUid, callingPid,pkg, id, tag, userId, notification.toString(),enqueueStatus);}// tell the assistant service about the notificationif (mAssistants.isEnabled()) {mAssistants.onNotificationEnqueuedLocked(r);mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),DELAY_FOR_ASSISTANT_TIME);} else {// 在这里又通过mHandler对通知进行处理,相关逻辑在// PostNotificationRunnable中进行了处理mHandler.post(new PostNotificationRunnable(r.getKey()));}}}}
protected class PostNotificationRunnable implements Runnable {private final String key;PostNotificationRunnable(String key) {this.key = key;}@Overridepublic void run() {synchronized (mNotificationLock) {try {NotificationRecord r = null;int N = mEnqueuedNotifications.size();for (int i = 0; i < N; i++) {//从队列中取出NotificationRecordfinal 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;}if (isBlocked(r)) {Slog.i(TAG, "notification blocked by assistant request");return;}final boolean isPackageSuspended =isPackagePausedOrSuspended(r.getSbn().getPackageName(), r.getUid());r.setHidden(isPackageSuspended);if (isPackageSuspended) {mUsageStats.registerSuspendedByAdmin(r);}NotificationRecord old = mNotificationsByKey.get(key);final StatusBarNotification n = r.getSbn();final Notification notification = n.getNotification();// Make sure the SBN has an instance ID for statsd logging.if (old == null || old.getSbn().getInstanceId() == null) {n.setInstanceId(mNotificationInstanceIdSequence.newInstanceId());} else {n.setInstanceId(old.getSbn().getInstanceId());}// 根据key判断mNotificationList里面有没有此Record,// 有就更新,没有就新增int index = indexOfNotificationLocked(n.getKey());if (index < 0) {mNotificationList.add(r);mUsageStats.registerPostedByApp(r);r.setInterruptive(isVisuallyInterruptive(null, r));} else {old = mNotificationList.get(index); // Potentially *changes* oldmNotificationList.set(index, r);mUsageStats.registerUpdatedByApp(r, old);// Make sure we don't lose the foreground service state.notification.flags |=old.getNotification().flags & FLAG_FOREGROUND_SERVICE;r.isUpdate = true;final boolean isInterruptive = isVisuallyInterruptive(old, r);r.setTextChanged(isInterruptive);r.setInterruptive(isInterruptive);}mNotificationsByKey.put(n.getKey(), r);// Ensure if this is a foreground service that the proper additional// flags are set.// /如果是前台service,则继续添加FLAG_ONGOING_EVENT和FLAG_NO_CLEAR// 标志位if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {notification.flags |= FLAG_ONGOING_EVENT| FLAG_NO_CLEAR;}// 对通知进行排序mRankingHelper.extractSignals(r);mRankingHelper.sort(mNotificationList);final int position = mRankingHelper.indexOf(mNotificationList, r);int buzzBeepBlinkLoggingCode = 0;if (!r.isHidden()) {buzzBeepBlinkLoggingCode = buzzBeepBlinkLocked(r);}if (notification.getSmallIcon() != null) {StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;// 调用NotificationListeners的notifyPostedLocked方法// mListeners 是 NotificationListeners 类的一个实例mListeners.notifyPostedLocked(r, old);if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))&& !isCritical(r)) {mHandler.post(new Runnable() {@Overridepublic void run() {mGroupHelper.onNotificationPosted(n, hasAutoGroupSummaryLocked(n));}});} else if (oldSbn != null) {final NotificationRecord finalRecord = r;mHandler.post(() -> mGroupHelper.onNotificationUpdated(finalRecord.getSbn(), hasAutoGroupSummaryLocked(n)));}} else {Slog.e(TAG, "Not posting notification without small icon: " + notification);if (old != null && !old.isCanceled) {mListeners.notifyRemovedLocked(r,NotificationListenerService.REASON_ERROR, r.getStats());mHandler.post(new Runnable() {@Overridepublic void run() {mGroupHelper.onNotificationRemoved(n);}});}// ATTENTION: in a future release we will bail out here// so that we do not play sounds, show lights, etc. for invalid// notificationsSlog.e(TAG, "WARNING: In a future release this will crash the app: "+ n.getPackageName());}if (mShortcutHelper != null) {mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,false /* isRemoved */,mHandler);}maybeRecordInterruptionLocked(r);maybeRegisterMessageSent(r);maybeReportForegroundServiceUpdate(r, true);// Log event to statsdmNotificationRecordLogger.maybeLogNotificationPosted(r, old, position,buzzBeepBlinkLoggingCode, getGroupInstanceId(r.getSbn().getGroupKey()));} finally {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;}}}}}}
以上代码会 notification ranking service,有新的 notification 进 来,然后对所有 notification 进行重新排序; 然后到最后会调用 mListeners.notifyPostedLocked() 方法。这里 mListeners 是 NotificationListeners 类的一个实例。
public class NotificationListeners extends ManagedServices {/*** asynchronously notify all listeners about a new notification** <p>* Also takes care of removing a notification that has been visible to a listener before,* but isn't anymore.*/// 异步通知所有侦听器新通知@GuardedBy("mNotificationLock")public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {notifyPostedLocked(r, old, true);}/*** @param notifyAllListeners notifies all listeners if true, else only notifies listeners* targetting <= O_MR1*/// 参数中的 boolean notifyAllListeners 标记是否通知所有的Listeners监听// 本次通知@GuardedBy("mNotificationLock")private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,boolean notifyAllListeners) {try {// Lazily initialized snapshots of the notification.StatusBarNotification sbn = r.getSbn();StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;TrimCache trimCache = new TrimCache(sbn);for (final ManagedServiceInfo info : getServices()) {boolean sbnVisible = isVisibleToListener(sbn, r. getNotificationType(), info);boolean oldSbnVisible = (oldSbn != null)&& isVisibleToListener(oldSbn, old.getNotificationType(), info);// This notification hasn't been and still isn't visible -> ignore.if (!oldSbnVisible && !sbnVisible) {continue;}// 如果通知是隐藏的,则不要将目标设置为版本 <P 的notifyPosted侦听//器。相 反,当通知取消隐藏时,这些侦听器将收到notifyPosted。if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {continue;}// 如果我们不应该通知所有侦听器,这意味着通知的隐藏状态已更改。// 不要通知版本>=P的已发布侦听器。相反,这些侦听器将收到//notifyRankingUpdateif (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {continue;}// 提取之前更新好的排序信息,以便发送给System UIfinal NotificationRankingUpdate update = makeRankingUpdateLocked(info);// This notification became invisible -> remove the old one.if (oldSbnVisible && !sbnVisible) {final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();mHandler.post(() -> notifyRemoved(info, oldSbnLightClone, update, null, REASON_USER_STOPPED));continue;}// Grant access before listener is notifiedfinal int targetUserId = (info.userid == UserHandle.USER_ALL)? UserHandle.USER_SYSTEM : info.userid;updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);final StatusBarNotification sbnToPost = trimCache.ForListener(info);// 在这里又过通handler机制处理mHandler.post(() -> notifyPosted(info, sbnToPost, update));}} catch (Exception e) {Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);}}
StatusBarNotification会发送给上述中的所有注册了NotificationListenerService的client,包括system UI等。上述的ManagedServiceInfo可以有system UI,手机管家以及用户自己实现NLS的任意APP。 mHandler.post(() -> notifyPosted(info, sbnToPost, update))最后会执行
notifyPosted(info, sbnToPost, update);
NotificationManagerService.javaprivate void notifyPosted(final ManagedServiceInfo info,final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {final INotificationListener listener = (INotificationListener) info.service;StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);try {//回调NotificationListenerService对象中的方法//onNotificationPosted(),在SystemUI中显示Notificationlistener.onNotificationPosted(sbnHolder, rankingUpdate);} catch (RemoteException ex) {Slog.e(TAG, "unable to notify listener (posted): " + info, ex);}}
之后就会交给systemUI去对通知进行处理(systemUI的处理过程还在分析)
Android Notifications相关推荐
- android Notifications通知
1.Notifications超越屏幕上的UI. 首先在布局中定义两个Button控件 2.在MainActivity中写应用程序,可以发送通知和清除通知. package com.example.a ...
- android 实现定时通知,在Android中创建定时通知(例如,针对事件)
对于某些Android应用程序,我想集成以下功能: 用户可以定义他想要被提醒的时间.当时间到了,应用程序应该在通知栏中创建通知,即使此时用户没有使用该应用程序. 为此,需要查看AlarmManager ...
- Android Tips - 填坑手册
转载自汤奇V分享的Android-Tips 学习 Android 至今,大大小小的坑没少踩,庆幸的是,在强大的搜索引擎与无私奉献的人们的帮助下,我遇到的坑都顺利地被填平了. 为了便于日后遇到同样的问题 ...
- android 开发从入门到精通
Android-Tips This is an awesome list of tips for android. If you are a beginner, this list will be t ...
- Android Tips - 专业填坑手册
Android-Tips 学习 Android 至今,大大小小的坑没少踩,庆幸的是,在强大的搜索引擎与无私奉献的人们的帮助下,我遇到的坑都顺利地被填平了. 为了便于日后遇到同样的问题时,能免于再次搜索 ...
- android教学大纲
android班 教学大纲 每次课4个小时 第1次课:Android开发环境搭建及工具介绍 Android系统简介 Android开发环境简介及搭建 AndroidStudio提供的工具组件 第2次课 ...
- android手机远程控制_如何远程查看和控制您的Android手机
android手机远程控制 If you've ever wished you could see your Android phone's screen on your desktop or rem ...
- Appium-Open Notifications(打开通知)
Open Notifications Example Usage Support Appium Server Appium Clients HTTP API Specifications Endpoi ...
- MQTT基本应用(Mosquitto+Eclipse Paho)
本文主要介绍,MQTT 基本概念和实现方式: 1.概述 1.1MQTT协议 MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),由IBM在1999 ...
最新文章
- HDU2066(Dijstra算法)
- 5-MST 多生成树 //IOU模拟
- Ouath 验证过程
- Spring Boot切换其他嵌入式的Servlet容器
- Oracle 审计文件
- 开源Easydarwin流媒体服务器Windows编译、配置、部署
- Oracle游标使用
- HTML5游戏引擎Egret发布2.0版 开发工具亦获更新
- 【路径规划】基于matlab蚁群算法无人机巡检路径规划【含Matlab源码 138期】
- linux texlive 中文,Ubuntu 安装 TexLive2013 及中文支持
- QML Text 文字元素
- Python软件安装教程
- JPA如何查询部分字段
- 多种电压转换的电路设计方案
- 射频电路习题解答(一)——利用电子smith图解题
- 系统架构设计笔记(91)—— 安全性规章
- chm转换成txt的url顺序问题
- 行为树 --- [4] 简单树
- 100层楼两个玻璃球怎么能够找到玻璃球破碎的那一层
- 两个摄像头合成一路_两个摄像头怎样用一个显示屏