概述

前文讲解了Notification的构造,现在来讲讲notification的发送,以及公布前文留下的疑问(自定义view不论高度是多高,最后只能显示为64dp,why?)

NotificationManager

在Notification构造完成后,会调用NotificationManager的notify方法来发送通知,我们就来看看该方法
frameworks/base/core/java/android/app/NotificationManager.java

public void notify(String tag, int id, Notification notification)
{...INotificationManager service = getService();...service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,stripped, idOut, UserHandle.myUserId());...
}复制代码

可以看出NotificationManager只是一个空壳,没有做什么实际上的事情,只是把notify的动作交给了service来做。
为了主干的清晰,直接给出enqueueNotificationWithTag的实现在NotificationManagerService中

NotificationManagerService

frameworks/base/services/java/com/android/server/NotificationManagerService.java

public 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);
}复制代码

所以重要的是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[] idOut, int incomingUserId) {...if (!isSystemNotification && !isNotificationFromListener) {...//MAX_PACKAGE_NOTIFICATIONS = 50;if (count >= MAX_PACKAGE_NOTIFICATIONS) {return;}}...mHandler.post(new Runnable() {@Overridepublic void run() {synchronized (mNotificationList) {...// blocked apps//如果用户设置了该引用不显示通知,并且不是系统通知的话,直接将该通知打分为-1000if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {if (!isSystemNotification) {//JUNK_SCORE = -1000;r.score = JUNK_SCORE;}}//SCORE_DISPLAY_THRESHOLD = -20;//打分小于阈值的通知不显示if (r.score < SCORE_DISPLAY_THRESHOLD) {// Notification will be blocked because the score is too low.return;}//垃圾通知,也不会显示if (isNotificationSpam(notification, pkg)) {mArchive.record(r.sbn);return;}...//只显示有图标的通知if (notification.icon != 0) {StatusBarNotification oldSbn = (old != null) ? old.sbn : null;mListeners.notifyPostedLocked(n, oldSbn);}...//声音,震动,闪光灯的控制buzzBeepBlinkLocked(r);}}});
}复制代码

可以看到要想发出通知必须得满足以下几个条件

  1. 非系统应用,最多只能发送50个通知消息
  2. 用户设置了允许应用发送通知
  3. 被系统判定为非垃圾通知(该功能是cm自己添加的,系统中会有一个数据库,然后根据通知栏的Extra信息来匹配,如果成功则判定为垃圾通知,但是该功能现在并没有实现)
  4. 通知必须得有icon

检查通过后再使用notifyPostedLocked方法做真正的发送动作。buzzBeepBlinkLocked很简单,不浪费篇幅叙述了。

INotificationListener

notifyPostedLocked方法最后调用notifyPosted方法,我们直接来看看该方法

private void notifyPosted(final ManagedServiceInfo info,final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {final INotificationListener listener = (INotificationListener)info.service;...listener.onNotificationPosted(sbnHolder, rankingUpdate);...
}复制代码

这里有一个INotificationListener对象,一看到以I开头的就可以知道,这里肯定又是一个IPC通信。
查看源码可以知道,onNotificationPosted的实现是在SystemUI进程中,也就是我们的状态栏进程。

BaseStatusBar

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java

@Override
public void onNotificationPosted(final StatusBarNotification sbn,final RankingMap rankingMap) {mHandler.post(new Runnable() {@Overridepublic void run() {...boolean isUpdate = mNotificationData.get(sbn.getKey()) != null|| isHeadsUp(sbn.getKey());...if (isUpdate) {updateNotification(sbn, rankingMap);} else {addNotification(sbn, rankingMap);}}});
}复制代码

状态栏会根据通知的唯一key值来判断该通知是否是更新还是新增的。
我们以新增的为例来讲.addNotification是一个抽象方法,实现是在BaseStatusBar的子类PhoneStatusBar

PhoneStatusBar

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java

public void addNotification(StatusBarNotification notification, RankingMap ranking) {...Entry shadeEntry = createNotificationViews(notification);if (shadeEntry == null) {return;}...addNotificationViews(shadeEntry, ranking);...
}复制代码

该方法做了2个重要的事情,一个就是创建Entry实例,另外一个就是将Entry添加到状态栏上,然后就显示完成了。
因为createNotificationViews的实现是在父类中,并且该方法十分重要,所以我们先跳过该方法。
先把Entry理解成一条通知,来讲addNotificationViews的实现。

protected void addNotificationViews(Entry entry, RankingMap ranking) {if (entry == null) {return;}// Add the expanded view and icon.mNotificationData.add(entry, ranking);updateNotifications();
}复制代码

先直接将得到的Entry添加到mNotificationData里面
最终updateNotifications会调用PhoneStatusBar中的updateNotificationShade方法

private void updateNotificationShade() {...ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());...for (int i=0; i<N; i++) {Entry ent = activeNotifications.get(i);...toShow.add(ent.row);}for (int i=0; i<toShow.size(); i++) {View v = toShow.get(i);if (v.getParent() == null) {mStackScroller.addView(v);}}...
}复制代码
  1. 从mNotificationData对象中获取一个list对象
  2. 将mNotificationData中的每一个Entry对象的row属性添加到List中
  3. 将ExpandableNotificationRow添加到mStackScroller里面

这个mStackScroller是NotificationStackScrollLayout的对象,而这个NotificationStackScrollLayout是一个继承自ViewGroup的,也就是我们下拉状态栏看到的整片view的根view.
那么ExpandableNotificationRow也就是对应着每一个通知了. ExpandableNotificationRow是继承自FrameLayout的

我们前面说到把Entry先理解为一条通知,看到这里,其实添加的是Entry对象里面的row属性到界面上,也就是ExpandableNotificationRow

createNotificationViews

这个是解答开头疑问的关键。 该方法是BaseStatusBar类的方法。

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {...// Construct the expanded view.NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);if (!inflateViews(entry, mStackScroller)) {handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);return null;}return entry;
}复制代码

这里首先实例化了NotificationData的内部类Entry。
NotificationData是一个十分重要的类,里面有几个比较重要的数据结构

ArrayMap mEntries = new ArrayMap<>();  //所有Entry的集合ArrayList mSortedAndFiltered = new ArrayList<>();  //排序后的Entry集合

那这个Entry到底是个什么东西呢?先来看看这个类的定义

public static final class Entry {...public ExpandableNotificationRow row; // the outer expanded viewpublic View expanded; // the inflated RemoteViewspublic View expandedPublic; // for insecure lockscreenspublic View expandedBig;...}复制代码

从定义里面可以看出,一个Entry对应了一条通知栏的所有Data信息,其中比较重要的是row属性,前面已经碰到过了。最后添加界面上的也就是这个row。
inflateViews方法里面,这个row会被赋值,我们来看看row是怎么被赋值的

private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {...//contentView和bigContentView是我们构造Notification时传过来的viewRemoteViews contentView = sbn.getNotification().contentView;RemoteViews bigContentView = sbn.getNotification().bigContentView;...ExpandableNotificationRow row;...//使用指定view填充row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,parent, false);...//这个expanded view就是我们在下拉状态栏中看到的每一条view,这里命名为expanded 应该是状态栏展开,而不是通知展开//NotificationContentView是继承自FrameLayout的,会根据不同状态来控制显示哪个view(默认通知/展开通知)NotificationContentView expanded =(NotificationContentView) row.findViewById(R.id.expanded);...//给每一条通知设置onClick的点击事件,以来相应我们设置的动作.PendingIntent contentIntent = sbn.getNotification().contentIntent;final View.OnClickListener listener = makeClicker(contentIntent, sbn.getKey(),isHeadsUp);row.setOnClickListener(listener);...///关键View contentViewLocal = null;View bigContentViewLocal = null;//将构造通知栏时设置的contentView & bigContentView(RemoteView)转换为viewcontentViewLocal = contentView.apply(mContext, expanded,mOnClickHandler, themePackageName);if (bigContentView != null) {bigContentViewLocal = bigContentView.apply(mContext, expanded,mOnClickHandler, themePackageName);}...//因为expanded 是一个FrameLayout的ViewGroup,所以往里面塞了2个viewexpanded.setContractedChild(contentViewLocal);expanded.setExpandedChild(bigContentViewLocal);
}复制代码

看完上面的代码,先来坐个小节,整理下思路。在Entry.row添加到屏幕上前,做了如下的属性赋值

  1. inflate布局文件status_bar_notification_row(这是每个通知栏的根view)
  2. 给根view设置监听器
  3. 将在构造通知过程中的bigContentView 和 contentView 塞到通知栏的根view里面

到这里,一个通知栏从初始化到显示的流程就讲完了,但是最开头的疑问不是还没有解答吗?来看答案

答案

contentView固定高度

expanded.setContractedChild方法前,传递进来的ContentView都还是自义定的view,没有做高度限制或者系统默认的view. 最后显示的时候却被限制了,说明在setContractedChild方法里做了手脚

public void setContractedChild(View child) {...sanitizeContractedLayoutParams(child);addView(child);...
}复制代码
private void sanitizeContractedLayoutParams(View contractedChild) {LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();lp.height = mSmallHeight;contractedChild.setLayoutParams(lp);
}复制代码

可以看到在sanitizeContractedLayoutParams方法里面,不论传递进来的contentView有多高最后的会被改成mSmallHeight的高度。这个mSmallHeight的值就是在SystemUI里面配置的,64dp

bigview最大高度

expanded.setExpandedChild的方法里面却没有做最大高度的限制,那么最大高度是在哪限制的呢?
这个时候就要看看ExpandableNotificationRow这个根view了
ExpandableNotificationRow继承自ExpandableView,来看看onMeasure方法

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//mMaxNotificationHeight是systemui中配置的值,256dpint ownMaxHeight = mMaxNotificationHeight;...for (int i = 0; i < childCount; i++) {View child = getChildAt(i);int childHeightSpec = newHeightSpec;ViewGroup.LayoutParams layoutParams = child.getLayoutParams();if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {if (layoutParams.height >= 0) {// An actual height is setchildHeightSpec = layoutParams.height > ownMaxHeight? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY): MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);}child.measure(getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),childHeightSpec);int childHeight = child.getMeasuredHeight();maxChildHeight = Math.max(maxChildHeight, childHeight);} else {mMatchParentViews.add(child);}}int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);for (View child : mMatchParentViews) {child.measure(getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),newHeightSpec);}...复制代码

如果bigviewlayoutParams.height == ViewGroup.LayoutParams.MATCH_PARENT则高度就是newHeightSpec。这个newHeightSpec要么是ownMaxHeight 要么是maxChildHeight,而这2个值的最大值就是256dp
如果bigviewlayoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT,最大值也是maxChildHeight 也就是256dp

注意: 这里并没有显示bigview的最小高度,所以bigview的高度范围是可以在(0,256dp ] 区间的

最后

a pic worth thousands of words,   tow pics worth double, lol

类图
Notification_class_diagram.jpg
流程图
Notification_seq_diagram.jpg

相关阅读

Notification之----Android5.0实现原理(一)
Notification之----自定义样式
Notification之----默认样式
Notification之----任务栈

Notification之 - Android5.0实现原理(二)相关推荐

  1. android5.0协调布局CoordinatorLayout(第二篇CollapsingToolbarLayout效果实现原理讲解)原理

    上一篇中已经讲解了CoordinatorLayout.AppBarLayout.CollapsingToolbarLayout之间的关系,这一篇探索一下CollapsingToolbarLayout内 ...

  2. Android10.0 Binder通信原理(二)-Binder入门篇

    摘要:本节主要来讲解Android10.0 Binder的设计原理,如何设计一个Binder通信 阅读本文大约需要花费15分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分 ...

  3. Android高级进阶--插曲-从Android5.0到Android10各版本变化

    从Android5.0到Android10 一.Android5 1.ANDROID 5.0 行为变更: 声音和振动 (1)如果您当前使用 Ringtone.MediaPlayer 或 Vibrato ...

  4. Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]

    摘要:本节主要来讲解Android10.0 日志系统的架构分析,以及logd.logcat的初始化操作 阅读本文大约需要花费15分钟. 文章首发微信公众号:IngresGe 专注于Android系统级 ...

  5. android5.0及以上版本的新特性

    android5.0及以上版本的新特性 Android5.0 Android6.0 Android7.0 Android8.0 Android9.0 Android5.0 Android 5.0 除了 ...

  6. (转)Android高级进阶--插曲-从Android5.0到Android9.0各版本变化

    原文:https://blog.csdn.net/gaoxiaoweiandy/article/details/83216001 从Android5.0到Android9.0 自从公司提出了一些出其不 ...

  7. android5.0后新特性修改标题头,Android5.0中Material Design的新特性

    Material Design简介 Material Design是谷歌新的设计语言,谷歌希望寄由此来统一各种平台上的用户体验,Material Design的特点是干净的排版和简单的布局,以此来突出 ...

  8. android 计算器显示不全,Android5.0 计算器计算结果不准确和结果末尾的多余的‘0’没有省略的解决方法...

    Android5.0 计算器计算结果不准确和结果末尾的多余的'0'没有省略 一.问题的描述: [测试步骤] 1.进入计算器 2.输入 100-99.9 3.查看计算结果 [测试结果] 1.结果为: 0 ...

  9. Android 10.0 PackageManagerService(二)权限扫描-[Android取经之路]

    摘要:PackageManagerService在systemReady()后,进行了/system/etc/permissions中的各种xml进行扫描,进行相应的权限存储,供以后使用 阅读本文大约 ...

最新文章

  1. Python3类方法和静态方法
  2. java 对象的定义是_浅析Java编程中类和对象的定义
  3. Mozilla 将 Firefox 的命运与 Rust 语言捆绑在一起
  4. 研发协同平台持续集成Jenkins作业设计演进
  5. windows Server 2003 尝试安装.NET Framework 4 失败
  6. C#利用Web Service实现短信发送(转)
  7. BZOJ 2754 喵星球上的点名(后缀数组)
  8. oracle 函数怎么个写法,Oracle表值函数的两种写法
  9. cobaltstrike之创建监听器与生成后门
  10. Dezender下载及使用说明
  11. 《马克思主义基本原理概论》复习笔记
  12. 欲为苍鹰,勿与鸟鸣, 欲为强者,莫与弱争!
  13. 【CAD】天河云cad,剖面线/细线已经改变图层,颜色却不变的问题
  14. 利用计算机辅助药物设计方法有何优点,计算机辅助药物设计的原理及应用
  15. java和ssm开发的医院体检预约系统有论文
  16. 卡通农场服务器无响应是怎么回事,卡通农场新买的平板打不开的解决方法
  17. 网桥(生成树网桥和源路由网桥)
  18. databricks spark 读取postgresql表
  19. 数据结构 | 3.树与二叉树
  20. 基于JavaEE的“三味”书屋网上售书系统

热门文章

  1. 无法访问‘/dev/ttyUSB: usb 基站,即显示端口被占用
  2. php基础篇-二维数组排序 array_multisort
  3. 程序员需要关注的十个大数据技术
  4. Strtus2工作流程及原理
  5. 在Windows Server 2012中配置NAT代理服务器
  6. 第2章数据库服务器的安装与卸载
  7. Non-static field ‘func1‘ cannot be referenced from from a static context
  8. tornado.httpclient.HTTPClient()的用法
  9. ModuleNotFoundError: No module named 'tinymce
  10. App.vue文件報錯