Android中的Notification机制深入理解
本文需要解决的问题
笔者最近正在做一个项目,里面需要用到AndroidNotification机制来实现某些特定需求。我正好通过这个机会研究一下AndroidNotification相关的发送逻辑和接收逻辑,以及整理相关的笔记。我研究Notification机制的目的是解决以下我在使用过程中所思考的问题:
- 我们创建的Notification实例最终以什么样的方式发送给系统?
- 系统是如何接收到Notification实例并显示的?
- 我们是否能拦截其他app的Notification并获取其中的信息?
什么是AndroidNotification机制?
Notification,中文名翻译为通知,每个app可以自定义通知的样式和内容等,它会显示在系统的通知栏等区域。用户可以打开抽屉式通知栏查看通知的详细信息。在实际生活中,AndroidNotification机制有很广泛的应用,例如IMapp的新消息通知,资讯app的新闻推送等等。
源码分析
本文的源码基于Android7.0。
Notification的发送逻辑
一般来说,如果我们自己的app想发送一条新的Notification,可以参照以下代码:
NotificationCompat.BuildermBuilder=
newNotificationCompat.Builder(this)
.setSmallIcon(R.drawable.notification_icon)
.setWhen(System.currentTimeMillis())
.setContentTitle("TestNotificationTitle")
.setContentText("TestNotificationContent!");
IntentresultIntent=newIntent(this,ResultActivity.class);
PendingIntentcontentIntent=
PendingIntent.getActivity(
this,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManagermNotificationManager=
(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
//mIdallowsyoutoupdatethenotificationlateron.
mNotificationManager.notify(mId,mBuilder.build());
可以看到,我们通过NotificationCompat.Builder新建了一个Notification对象,最后通过NotificationManager#notify()方法将Notification发送出去。
NotificationManager#notify()
publicvoidnotify(intid,Notificationnotification)
{
notify(null,id,notification);
}
//省略部分注释
publicvoidnotify(Stringtag,intid,Notificationnotification)
{
notifyAsUser(tag,id,notification,newUserHandle(UserHandle.myUserId()));
}
/**
*@hide
*/
publicvoidnotifyAsUser(Stringtag,intid,Notificationnotification,UserHandleuser)
{
int[]idOut=newint[1];
INotificationManagerservice=getService();
Stringpkg=mContext.getPackageName();
//Fixthenotificationasbestwecan.
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){
thrownewIllegalArgumentException("Invalidnotification(novalidsmallicon):"
+notification);
}
}
if(localLOGV)Log.v(TAG,pkg+":notify("+id+","+notification+")");
finalNotificationcopy=Builder.maybeCloneStrippedForDelivery(notification);
try{
//!!!
service.enqueueNotificationWithTag(pkg,mContext.getOpPackageName(),tag,id,
copy,idOut,user.getIdentifier());
if(localLOGV&&id!=idOut[0]){
Log.v(TAG,"notify:idcorrupted:sent"+id+",gotback"+idOut[0]);
}
}catch(RemoteExceptione){
throwe.rethrowFromSystemServer();
}
}
我们可以看到,到最后会调用service.enqueueNotificationWithTag()方法,这里的是service是INotificationManager接口。如果熟悉AIDL等系统相关运行机制的话,就可以看出这里是代理类调用了代理接口的方法,实际方法实现是在NotificationManagerService当中。
NotificationManagerService#enqueueNotificationWithTag()
@Override
publicvoidenqueueNotificationWithTag(Stringpkg,StringopPkg,Stringtag,intid,
Notificationnotification,int[]idOut,intuserId)throwsRemoteException{
enqueueNotificationInternal(pkg,opPkg,Binder.getCallingUid(),
Binder.getCallingPid(),tag,id,notification,idOut,userId);
}
voidenqueueNotificationInternal(finalStringpkg,finalStringopPkg,finalintcallingUid,
finalintcallingPid,finalStringtag,finalintid,finalNotificationnotification,
int[]idOut,intincomingUserId){
if(DBG){
Slog.v(TAG,"enqueueNotificationInternal:pkg="+pkg+"id="+id
+"notification="+notification);
}
checkCallerIsSystemOrSameApp(pkg);
finalbooleanisSystemNotification=isUidSystem(callingUid)||("android".equals(pkg));
finalbooleanisNotificationFromListener=mListeners.isListenerPackage(pkg);
finalintuserId=ActivityManager.handleIncomingUser(callingPid,
callingUid,incomingUserId,true,false,"enqueueNotification",pkg);
finalUserHandleuser=newUserHandle(userId);
//Fixthenotificationasbestwecan.
try{
finalApplicationInfoai=getContext().getPackageManager().getApplicationInfoAsUser(
pkg,PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId==UserHandle.USER_ALL)?UserHandle.USER_SYSTEM:userId);
Notification.addFieldsFromContext(ai,userId,notification);
}catch(NameNotFoundExceptione){
Slog.e(TAG,"Cannotcreateacontextforsendingapp",e);
return;
}
mUsageStats.registerEnqueuedByApp(pkg);
if(pkg==null||notification==null){
thrownewIllegalArgumentException("nullnotallowed:pkg="+pkg
+"id="+id+"notification="+notification);
}
finalStatusBarNotificationn=newStatusBarNotification(
pkg,opPkg,id,tag,callingUid,callingPid,0,notification,
user);
//Limitthenumberofnotificationsthatanygivenpackageexcepttheandroid
//packageoraregisteredlistenercanenqueue.PreventsDOSattacksanddealswithleaks.
if(!isSystemNotification&&!isNotificationFromListener){
synchronized(mNotificationList){
if(mNotificationsByKey.get(n.getKey())!=null){
//thisisanupdate,ratelimitupdatesonly
finalfloatappEnqueueRate=mUsageStats.getAppEnqueueRate(pkg);
if(appEnqueueRate>mMaxPackageEnqueueRate){
mUsageStats.registerOverRateQuota(pkg);
finallongnow=SystemClock.elapsedRealtime();
if((now-mLastOverRateLogTime)>MIN_PACKAGE_OVERRATE_LOG_INTERVAL){
Slog.e(TAG,"Packageenqueuerateis"+appEnqueueRate
+".Sheddingevents.package="+pkg);
mLastOverRateLogTime=now;
}
return;
}
}
intcount=0;
finalintN=mNotificationList.size();
for(inti=0;i=MAX_PACKAGE_NOTIFICATIONS){
mUsageStats.registerOverCountQuota(pkg);
Slog.e(TAG,"Packagehasalreadyposted"+count
+"notifications.Notshowingmore.package="+pkg);
return;
}
}
}
}
}
//Whitelistpendingintents.
if(notification.allPendingIntents!=null){
finalintintentCount=notification.allPendingIntents.size();
if(intentCount>0){
finalActivityManagerInternalam=LocalServices
.getService(ActivityManagerInternal.class);
finallongduration=LocalServices.getService(
DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
for(inti=0;i
这里代码比较多,但通过注释可以清晰地理清整个逻辑:
- 首先检查通知发起者是系统进程或者是查看发起者发送的是否是同个app的通知信息,否则抛出异常;
- 除了系统的通知和已注册的监听器允许入队列外,其他app的通知都会限制通知数上限和通知频率上限;
- 将notification的PendingIntent加入到白名单;
- 将之前的notification进一步封装为StatusBarNotification和NotificationRecord,最后封装到一个异步线程EnqueueNotificationRunnable中
这里有一个点,就是mHandler,涉及到切换线程,我们先跟踪一下mHandler是在哪个线程被创建。
mHandler是WorkerHandler类的一个实例,在NotificationManagerService#onStart()方法中被创建,而NotificationManagerService是系统Service,所以EnqueueNotificationRunnable的run方法会运行在system_server的主线程。
NotificationManagerService.EnqueueNotificationRunnable#run()
@Override
publicvoidrun(){
synchronized(mNotificationList){
//省略代码
if(notification.getSmallIcon()!=null){
StatusBarNotificationoldSbn=(old!=null)?old.sbn:null;
mListeners.notifyPostedLocked(n,oldSbn);
}else{
Slog.e(TAG,"Notpostingnotificationwithoutsmallicon:"+notification);
if(old!=null&&!old.isCanceled){
mListeners.notifyRemovedLocked(n);
}
//ATTENTION:inafuturereleasewewillbailouthere
//sothatwedonotplaysounds,showlights,etc.forinvalid
//notifications
Slog.e(TAG,"WARNING:Inafuturereleasethiswillcrashtheapp:"+n.getPackageName());
}
buzzBeepBlinkLocked(r);
}
}
- 省略的代码主要的工作是提取notification相关的属性,同时通知notificationrankingservice,有新的notification进来,然后对所有notification进行重新排序;
- 然后到最后会调用mListeners.notifyPostedLocked()方法。这里mListeners是NotificationListeners类的一个实例。
NotificationManagerService.NotificationListeners#notifyPostedLocked() ->NotificationManagerService.NotificationListeners#notifyPosted()
publicvoidnotifyPostedLocked(StatusBarNotificationsbn,StatusBarNotificationoldSbn){
//Lazilyinitializedsnapshotsofthenotification.
TrimCachetrimCache=newTrimCache(sbn);
for(finalManagedServiceInfoinfo:mServices){
booleansbnVisible=isVisibleToListener(sbn,info);
booleanoldSbnVisible=oldSbn!=null?isVisibleToListener(oldSbn,info):false;
//Thisnotificationhasn'tbeenandstillisn'tvisible->ignore.
if(!oldSbnVisible&&!sbnVisible){
continue;
}
finalNotificationRankingUpdateupdate=makeRankingUpdateLocked(info);
//Thisnotificationbecameinvisible->removetheoldone.
if(oldSbnVisible&&!sbnVisible){
finalStatusBarNotificationoldSbnLightClone=oldSbn.cloneLight();
mHandler.post(newRunnable(){
@Override
publicvoidrun(){
notifyRemoved(info,oldSbnLightClone,update);
}
});
continue;
}
finalStatusBarNotificationsbnToPost=trimCache.ForListener(info);
mHandler.post(newRunnable(){
@Override
publicvoidrun(){
notifyPosted(info,sbnToPost,update);
}
});
}
}
privatevoidnotifyPosted(finalManagedServiceInfoinfo,finalStatusBarNotificationsbn,NotificationRankingUpdaterankingUpdate){
finalINotificationListenerlistener=(INotificationListener)info.service;
StatusBarNotificationHoldersbnHolder=newStatusBarNotificationHolder(sbn);
try{
listener.onNotificationPosted(sbnHolder,rankingUpdate);
}catch(RemoteExceptionex){
Log.e(TAG,"unabletonotifylistener(posted):"+listener,ex);
}
}
调用到最后会执行listener.onNotificationPosted()方法。通过全局搜索得知,listener类型是NotificationListenerService.NotificationListenerWrapper的代理对象。
NotificationListenerService.NotificationListenerWrapper#onNotificationPosted()
publicvoidonNotificationPosted(IStatusBarNotificationHoldersbnHolder,NotificationRankingUpdateupdate){
StatusBarNotificationsbn;
try{
sbn=sbnHolder.get();
}catch(RemoteExceptione){
Log.w(TAG,"onNotificationPosted:ErrorreceivingStatusBarNotification",e);
return;
}
try{
//converticonmetadatatolegacyformatforolderclients
createLegacyIconExtras(sbn.getNotification());
maybePopulateRemoteViews(sbn.getNotification());
}catch(IllegalArgumentExceptione){
//warnanddropcorruptnotification
Log.w(TAG,"onNotificationPosted:can'trebuildnotificationfrom"+sbn.getPackageName());
sbn=null;
}
//protectsubclassfromconcurrentmodificationsof(@linkmNotificationKeys}.
synchronized(mLock){
applyUpdateLocked(update);
if(sbn!=null){
SomeArgsargs=SomeArgs.obtain();
args.arg1=sbn;
args.arg2=mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,args).sendToTarget();
}else{
//stillpassalongtherankingmap,itmaycontainotherinformation
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE,mRankingMap).sendToTarget();
}
}
}
这里在一开始会从sbnHolder中获取到sbn对象,sbn隶属于StatusBarNotificationHolder类,继承于IStatusBarNotificationHolder.Stub对象。注意到这里捕获了一个RemoteException,猜测涉及到跨进程调用,但我们不知道这段代码是在哪个进程中执行的,所以在这里暂停跟踪代码。
笔者之前是通过向系统发送通知的方式跟踪源码,发现走不通。故个人尝试从另一个角度入手,即系统接收我们发过来的通知并显示到通知栏这个方式入手跟踪代码。
系统如何显示Notification,即对于系统端来说,Notification的接收逻辑
系统显示Notification的过程,猜测是在PhoneStatusBar.java中,因为系统启动的过程中,会启动SystemUI进程,初始化整个Android显示的界面,包括系统通知栏。
PhoneStatusBar#start() ->BaseStatusBar#start()
publicvoidstart(){
//省略代码
//Setuptheinitialnotificationstate.
try{
mNotificationListener.registerAsSystemService(mContext,
newComponentName(mContext.getPackageName(),getClass().getCanonicalName()),
UserHandle.USER_ALL);
}catch(RemoteExceptione){
Log.e(TAG,"Unabletoregisternotificationlistener",e);
}
//省略代码
}
这段代码中,会调用NotificationListenerService#registerAsSystemService()方法,涉及到我们之前跟踪代码的类。我们继续跟进去看一下。
NotificationListenerService#registerAsSystemService()
publicvoidregisterAsSystemService(Contextcontext,ComponentNamecomponentName,
intcurrentUser)throwsRemoteException{
if(mWrapper==null){
mWrapper=newNotificationListenerWrapper();
}
mSystemContext=context;
INotificationManagernoMan=getNotificationInterface();
mHandler=newMyHandler(context.getMainLooper());
mCurrentUser=currentUser;
noMan.registerListener(mWrapper,componentName,currentUser);
}
这里会初始化一个NotificationListenerWrapper和mHandler。由于这是在SystemUI进程中去调用此方法将NotificationListenerService注册为系统服务,所以在前面分析的那里:
NotificationListenerService.NotificationListenerWrapper#onNotificationPosted(),这段代码是运行在SystemUI进程,而mHandler则是运行在SystemUI主线程上的Handler。所以,onNotificationPosted()是运行在SystemUI进程中,它通过sbn从system_server进程中获取到sbn对象。下一步是通过mHandler处理消息,查看NotificationListenerService.MyHandler#handleMessage()方法,得知当message.what为MSG_ON_NOTIFICATION_POSTED时,调用的是onNotificationPosted()方法。
但是,NotificationListenerService是一个抽象类,onNotificationPosted()为空方法,真正的实现是它的实例类。
观察到之前BaseStatusBar#start()中,是调用了mNotificationListener.registerAsSystemService()方法。那么,mNotificationListener是在哪里进行初始化呢?
BaseStatusBar.mNotificationListener#onNotificationPosted
privatefinalNotificationListenerServicemNotificationListener=newNotificationListenerService(){
//省略代码
@Override
publicvoidonNotificationPosted(finalStatusBarNotificationsbn,finalRankingMaprankingMap){
if(DEBUG)Log.d(TAG,"onNotificationPosted:"+sbn);
if(sbn!=null){
mHandler.post(newRunnable(){
@Override
publicvoidrun(){
processForRemoteInput(sbn.getNotification());
Stringkey=sbn.getKey();
mKeysKeptForRemoteInput.remove(key);
booleanisUpdate=mNotificationData.get(key)!=null;
//Incasewedon'tallowchildnotifications,weignorechildrenof
//notificationsthathaveasummary,sincewe'renotgoingtoshowthem
//anyway.Thisistruealsowhenthesummaryiscanceled,
//becausechildrenareautomaticallycanceledbyNoManinthatcase.
if(!ENABLE_CHILD_NOTIFICATIONS&&mGroupManager.isChildInGroupWithSummary(sbn)){
if(DEBUG){
Log.d(TAG,"Ignoringgroupchildduetoexistingsummary:"+sbn);
}
//Removeexistingnotificationtoavoidstaledata.
if(isUpdate){
removeNotification(key,rankingMap);
}else{
mNotificationData.updateRanking(rankingMap);
}
return;
}
if(isUpdate){
updateNotification(sbn,rankingMap);
}else{
addNotification(sbn,rankingMap,null/*oldEntry*/);
}
}
});
}
}
//省略代码
}
通过上述代码,我们知道了在BaseStatusBar.java中,创建了NotificationListenerService的实例对象,实现了onNotificationPost()这个抽象方法;
在onNotificationPost()中,通过handler进行消息处理,最终调用addNotification()方法
PhoneStatusBar#addNotification()
@Override
publicvoidaddNotification(StatusBarNotificationnotification,RankingMapranking,EntryoldEntry){
if(DEBUG)Log.d(TAG,"addNotificationkey="+notification.getKey());
mNotificationData.updateRanking(ranking);
EntryshadeEntry=createNotificationViews(notification);
if(shadeEntry==null){
return;
}
booleanisHeadsUped=shouldPeek(shadeEntry);
if(isHeadsUped){
mHeadsUpManager.showNotification(shadeEntry);
//Markasseenimmediately
setNotificationShown(notification);
}
if(!isHeadsUped&¬ification.getNotification().fullScreenIntent!=null){
if(shouldSuppressFullScreenIntent(notification.getKey())){
if(DEBUG){
Log.d(TAG,"NoFullscreenintent:suppressedbyDND:"+notification.getKey());
}
}elseif(mNotificationData.getImportance(notification.getKey())
在这个方法中,最关键的方法是最后的addNotificationViews()方法。调用这个方法之后,你创建的Notification才会被添加到系统通知栏上。
总结
跟踪完整个过程中,之前提到的问题也可以一一解决了:
Q:我们创建的Notification实例最终以什么样的方式发送给系统?
A:首先,我们在app进程创建Notification实例,通过跨进程调用,传递到system_server进程的NotificationManagerService中进行处理,经过两次异步调用,最后传递给在NotificationManagerService中已经注册的NotificationListenerWrapper。而android系统在初始化systemui进程的时候,会往NotificationManagerService中注册监听器(这里指的就是NotificationListenerWrapper)。这种实现方法就是基于我们熟悉的一种设计模式:监听者模式。
Q:系统是如何获取到Notification实例并显示的?
A:上面提到,由于初始化的时候已经往NotificationManagerService注册监听器,所以系统SystemUI进程会接收到Notification实例之后经过进一步解析,然后构造出NotificationViews并最终显示在系统通知栏上。
Q:我们是否能拦截Notification并获取其中的信息?
A:通过上面的流程,我个人认为可以通过Xposed等框架去hook其中几个重要的方法去捕获Notification实例,例如hookNotificationManager#notify()方法去获取Notification实例。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。