转载声明:文章来源:https://blog.csdn.net/zhao5214319/article/details/99058563
目录
1.参考:
2.NotificationListenerService:
3.时序图:
4.时序图解读:
————————————————
1.参考:
通知framework中的执行流程请参考:
https://blog.csdn.net/zhao5214319/article/details/98848708
2.NotificationListenerService:
在SystemUI中会注册这个Service用来接收从Framework中传过来的通信信息,也就是StatusBarNotification这个类。这个过程使用的是Binder通信机制,AIDL接口为INotificationListener,在Framework代码中实现了这个接口,但这个实现是一个抽象服务类(extends Service)的,本质上是一个Services,但在SystemUI中的使用上可以理解就是向NotifcationManageService中注册的一个回调,当需要发送一个通知时,Framewok中接收到通知信息进行记录并处理,处理完了之后调用回调将信息传到SystemUI进行处理。
注册流程:
要了解NotificationListenerService的注册流程首先要连接SystemUI的启动流程,在这个里做一个简单的介绍。
启动流程大约分为几个阶段:
1>java环境启动阶段(fastboot->Kernel->java虚拟机)
2>framework服务启动阶段(参考:https://blog.csdn.net/zhao5214319/article/details/96483297)
3>SystemUI启动阶段
主要简单的介绍下SystemUI阶段,因为NotificationListenerService注册就是在这个阶段进行的,SystemUI启动流程会另起一篇文章详细学习。
时序图如下:
重点解读:
1>SystemUIApplicaton创建SystemBar对象时会使用工厂模式,若工厂中存在则直接使用,若不存则通过反射的newInstance方法创建此对象,并将对象加入工厂中。这块时序图中不能清晰表达,因此在这重点介绍
2>在SystemBar创建PhoneStatusBar对象时会读取配置文件,根据配置文件中配置的包名+类名进行创建,因此这块厂家可以配置自己的statusBar的java类,在进行此项开发时最好继承PhoneStatusBar这个类,当然继承BaseStatusBar或其父类也可以,但这样就需要自己实现大量的功能,会消耗大量的资源。
3>congfig.xml的配置如下:
<string name="config_statusBarComponent"
translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>
4>时序图中调用了大量的start()方法,请注意:SystemBar、PhoneStatusBar和BaseStatusBar既不是服务,也不是新建立的线程,start就是一个java的最普通的方法。
5>在NotificationManagerService中NotificationListenerService一般有4个。
3.时序图:
4.时序图解读:
通知是怎么传到NotificationListenerService中的请参考此篇文章:
https://blog.csdn.net/zhao5214319/article/details/98848708
在NotificationListenerService的onNotificationPosted方法中首先就是新开一个线程,这样做有以下几点好处:
将framework中的线程释放掉,这样可以节省Framwork进程的内存空间,防止因内存不够导致的问题
释放Binder通道,防止资源暂用导致使用通道堵塞,从而引起的crash问题
由于新开的线程是运行在SystemUI进程中的,出问题后可以通过进程ID快速定位那个模块出的问题
新开线程处理的好处多多,简单列举几条,希望以后在使用Binder通信时,把这个当成一个准则来使用(Binder接口不需要返回值时,要开线程来处理所有的活动,无论是耗时还是非耗时)
线程启动后首先就是对RemoteImput的处理,这是通知的一种新功能,支持用户的输入操作,增加了通知于用户的可交互性,最常用的就是短信模块,可以通过此工具快速回复短消息,此功能会单独记录,在这就不在啰嗦。
在RemoteInput之后就需要判断此次通知消息发过了是要进行更新处理还是要进行增加处理了:在此之前要了解NotificationData的作用。NotificationData是一个记录所有SystemUI中通知信息的实体类,它存在一个内部静态类Entry,这个Entry存放一条通知在SystemUI中显示所有需要的所有信息(包含RemoteViews),而NotificationData中有一个成员变量mEntries,它是一个ArrayMap类型的变量,通过Key-Value方式将所有通知对应的Entry,也是就是说,你在通知栏中看到的每一条通知都会有其对应的一个Entry。新增一条通知时,SystemUI会将StatusBarNotification对象,按照一定的规则转成一条Entry存储在SystemUI中,并将StatusBarNotification的对象释放。
这个Key值很重要,判断是否是新增就需要判断这个Key值能否从NotificationData中拿到有效数据,若不存在,则新增;若存在就更新。因此就要保证这个Key值是唯一的,可以通过Framework的StatusbarNotification中的key()方法来读懂这个Key值是怎么保证唯一的:
private String key() {
String sbnKey = user.getIdentifier() + "|" + pkg + "|" + id + "|" + tag + "|" + uid;
if (overrideGroupKey != null && getNotification().isGroupSummary()) {
sbnKey = sbnKey + "|" + overrideGroupKey;
}
return sbnKey;
}
一般情况下发通知是不设置NotificationGroup的,系统会自动分配一个Default Group,因此上面的If语句就可以忽略不看了,主要看方法的第一句赋值语句。可以发现:这个Key与5个变量值有关:
1、userID 多用户使用,一般手机都是机主用户在使用,所以这个值始终为0
2、packageName 应用程序包名,系统通知都是”android“,在Andorid这个值可以区分不同的应用程序的
3、ID 通知ID值,这个在发送通知时要求是应用程序中唯一的
4、TAG 这个值用户发送通知时可以使用三参事的notifity方法自由设置的,但一般使用双参数的notify发通知时这个值默认为null
5、UID Linux分配给应用程序的唯一ID值,不同的应用程序UID是不同的,有些类似于PackageName
因此应用程序可以通过第2、3、4条保证这个Key值是唯一的,系统程序就需要在加上第5条了。
判断出来了刷新还是新增,就需要根据这个状态来执行相应的流程了。在这需要指出,这2个流程之间的关系是包含关系,也就是说新增流程包含刷新流程,但具体细节上又有一些差别,因此时序图上详细的画出了新增流程,刷新流程只画一少部分。
在addNotification方法中会创建通知对应的Entry、StatusBarIconView(状态栏上显示的小图标)、创建5个RemoteViews并对所有的View进行初始化,创建一个ExpandableNotificationRow,并将这5个RemoteViews存储到ExpandableNotificationRow中等待显示到界面上。最后还要加上点击事件的注册(点击此通知后,通知消失,打开对应注册的Activity)
创建完成Entry,并初始化完成后,就要调用addNotificationViews方法将Entry存储到NotificationData中,此时数据和UI上显示的就不一致了,就需要调用updateNotifications方法保持NotificationData中的数据和UI上显示的一致。
在刷新每一条通知数据时,首先要对数据进行排序,之后将新的通知ExpandableNotificationRow加入到NotificationStackScrollLayout上进行显示,这个过程会进行排序添加,添加完成后调用updateRowStates更新状态,最后就显示了通知下拉状态中了。
刷新过程要比添加流程简单一些,先在NotificationData中取到Entry,并将Entry中的ExpandableNotificationRow存储的5个RemoteViews进行更新,最后在刷新就完成了。
欢迎大家斧正
帖子还没人回复快来抢沙发