此文已由作者黎星授权网易云社区发布.
欢迎访问网易云社区 https://sq.163yun.com/blog?tag=M_tg_851_65 , 了解更多网易技术产品运营经验.
由于历史原因, Android 在发布之初对通知栏 Notification 的设计相当简单, 而如今面对各式各样的通知栏玩法, 谷歌也不得不对其进行更新迭代调整, 增加新功能的同时, 也在不断地改变样式, 试图迎合更多人的口味. 本文总结了 Android 通知栏的版本迭代过程, 在通知栏开发过程中所遇到的各种各样的坑, 以及一些解决技巧, 特别的, 对于大众期盼的 Android 7.0 的到来, 通知栏又会发生怎样的改变呢? 接下来一一进行介绍.
Android 通知栏发展历史
首先来看一张各个 Android 版本通知栏消息的全家福.
点击查看大图
Android 通知栏从最初的 Android1.1 系统一直到如今的 7.X 版本, 发生了翻天覆地的变化. 从图中可以看出, 1.X-2.2 版本的通知栏采用了白色背景和黑色字体; 2.3-4.X 版本, 默认背景变成了黑色, 而主标题采用白色字体, 内容为灰色字体. 从 Android5.0 开始, 又更改为白色背景和黑色字体. 当然, 这只是原生的 Android 系统通知栏默认颜色, 许多厂商对每个 Android 的版本都尝试了各式各样的修改, 在此不一一介绍.
下面分别介绍每个版本的更新和修改记录.
Android 1.X 修改记录 ^1
Android 1.X 版本也就是第一个 Android 诞生的版本. 从 Android1.1 版本开始, 提供基本的通知栏消息功能, 包含小图标, 主标题, 副标题和时间这四个元素. 右上角有一个清除通知栏消息的按钮. 需要说明的是, Android 从一开始就提供了清除通知栏消息的功能并且保留至今, 而 iOS 到现在都没有提供清除按钮.
Android 2.X 修改记录 ^2
Android 2.X 版本的通知栏消息功能上并未发生变化, 右上角的 "clear notifications" 缩减为了 "clear".2.2 版本以前沿用了 1.5 的通知栏样式, 从 2.3 版本开始重新设计, 改成了暗色背景.
Android 3.X 修改记录 ^3
Android 3.X 版本是专为 Pad 而设计的系统. 通知栏消息带来了一些新的功能.
非永久的通知栏消息的右边增加了 "X" 按钮, 点击后该条通知可以立即清除.
增加了 RemoteControlClient, 即远程控制媒体应用的功能.
增加了 LargeIcon, 可以使用大图展示通知栏消息.
Android 4.1 修改记录 ^4
Android 4.1 版本的通知栏在 3.X 版本的基础上进行了大量修改. 增加了不少新功能.
增加了 Style
增加了通知栏按钮
支持通知栏展示的优先级配置
通知栏背景改为黑色透明
通知栏样式
Android 4.1 通知栏最大的变化就是增加了丰富多样的 Style 样式. 通过设置样式, 可以展示更大区域的通知消息, 如展示大图和多行文字, 也可以展示类似邮箱收发信的样式, 同时支持自定义按钮并增加点击事件. 但需要注意的是, 只有最顶部的那条通知栏消息可以默认展示 Style 样式, 其他消息默认是以普通样式展示. Style 可以通过 Notification.Builder.setStyle(Style)进行设置. 具体支持的样式有:
Notification.BigPictureStyle
大图样式, 即除了普通的通知栏消息内容外, 可以在通知栏消息下方展示一张大图, 最大高度支持 256dp.
Notification.BigTextStyle
多行文字样式, 可以支持多行文字的展示. 经测试, 在不同手机上能够支持的行数不一样, 测试过的机子, 最大支持 12 行.
Notification.InboxStyle
收件箱样式. 支持展示具有一串消息内容的会话样式, 适用于短信, 邮件, IM 等.
通知栏按钮
通知栏消息不管是普通样式还是 Style 样式, 都支持两个按钮同时出现在一条通知栏消息的底部, 通过这两个按钮, 可以自定义一系列动作, 包括回复信息和邮件, 点赞等. 通过 Notification.Builder.addAction(Action)添加按钮.
通知栏优先级
Android 4.1 通知栏增加了优先级的配置, 优先级高的消息可以展示在最上方. 谷歌设计优先级的初衷是根据不同的优先级来防止用户整天被各种莫名其妙的通知栏消息骚扰, 重要的通知则应该适当提高优先级, 使得用户可以快速地看到并回应, 不重要的通知则降低优先级, 防止用户被打扰. 优先级一共有 5 个级别, 分别是:
- // 默认优先级
- public static final int PRIORITY_DEFAULT = 0;
- // 低优先级
- public static final int PRIORITY_LOW = -1;
- // 最低优先级
- public static final int PRIORITY_MIN = -2;
- // 高优先级
- public static final int PRIORITY_HIGH = 1;
- // 最高优先级
- public static final int PRIORITY_MAX = 2;
Android 4.3 修改记录 ^6
Android 4.3 通知栏没有发生大的变化. 主要增加了两个小功能.
增加了 Notification AccessApi, 允许可穿戴设备远程控制通知栏消息.
增加了 NotificationListenerService, 允许接收到系统通知栏列表的变化
Android 5.X 修改记录 ^7
Android 5.X 系统相较于以前的版本, 可以说是一个真正可以和 iOS 抗衡的系统. 材料设计给 Android 系统注入了新的活力, 相应的通知栏消息也相较于上一个版本进行了改版. 所发生的变化有:
通知栏修改为白色背景, 暗色字体, 以适应材料设计风格.
系统会忽略所有 non-alpha 通道的图标, 包括按钮图标和主图标.
可以通过 setColor()方法在图标后设置一个背景色.
通知消息的声音将通过 STREAM_RING 或者 STREAM_NOTIFICATION 控制, 以前是通过 STREAM_MUSIC 控制.
锁屏状态下, 可以控制通知栏消息的隐私程度.
移除了 RemoteControlClient, 更改为 NotificationCompat.MediaStyle 实现.
增加了 Heads-up 通知, 即通过状态栏浮动窗口展示通知消息.
Android 6.X 修改记录 ^8
移除了 Notification.setLatestEventInfo()方法, 通过持有 Notification.Builder, 然后使用 build()方法可以更新同一个通知栏实例.
允许用户控制应用通知的优先级.
加入了免打扰模式(Do Not Disturb).
增加了 getActiveNotifications()方法获取当前展示的通知消息.
Android 7.X 修改记录
通知栏样式全面改版, 小图标在左上角, 大图标在右边, 小图标, App 应用名, 副标题, 数量和时间在第一行, 第二行是主标题, 第三行是内容.
增加了 Notification.DecoratedCustomViewStyle()和 Notification.DecoratedMediaCustomViewStyle(), 帮助更好的装饰带有 RemoteViews 的通知栏消息.
需要动态设置 Builder.setShowWhen(true)才会显示时间.
支持 Action 的直接回复, 通过 RemoteInput 实现, 且回复的消息内容支持立即添加到通知栏.
支持通知消息组, 相似的消息在达到一定数量后会按照消息组来显示.
增加了 NotificationManager.areNotificationsEnabled 告知应用是否开启了通知权限.
Android 通知栏踩坑与填坑指南
魅族 5.X 手机, 大图显示问题
问题详情
Flyme 系统对原生 Android 源码做了修改, 采用 BigPictureStyle 方式显示大图通知栏的时候, 消息与大图重合了, 如下图.
解决方案
首先说一下为什么会有解决方案. 展示大图这个功能开发完成后, 拿去给产品演示. 碰巧产品的机型就是一魅族手机 T_T, 结果当然是不能接受的, 然后又一个巧合的事情出现了, 那就是产品的手机里, 京东 App 推了一条带大图的广告, 他们居然能够解决这个问题! 于是, 我开始研究解决方案.
首先, 通过 BigPictureStyle 来实现大图功能肯定是走不通的, 因为事实就摆着行不通的嘛. 京东的 App 肯定是通过 RemoteViews 来实现的. 于是, 开始走弯路, 尝试通过 RemoteViews 来展示大图. 但是谷歌规定, 自定义布局展示的通知栏消息最大高度是 64dp. 那么, 京东的 App 是怎么实现的? 在尝试了各种方法以后, 最后又是通过投机取巧的方式解决了问题:
- private void showBigPictureNotificationWithMZ(Context context) {
- NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
- Notification.Builder builder = new Notification.Builder(context);
- Notification notification = generateNotification(builder);
- notification.bigContentView = mRemoteViews;
- notificationManager.notify(notifyId, notification);
- }
需要先生成 Notification 的实例, 然后手动给 notification.bigContentView 赋值, 再 notify, 就可以了
顶部状态栏 (StatusBar) 小图标显示异常
问题详情
当通知来的时候, 如果不在通知栏浏览, 会在顶部状态栏出现一个向上翻滚动画的通知消息, 这条通知消息左边是一个小图标. 部分系统这个小图标显示异常, 是一个纯灰色的正方形, 如下图.
解决方案
首先产生灰色图标的原因就是 5.0 系统引入了材料设计, 谷歌强制使用带有 alpha 通道的图标, 并且 RGB 的 alpha 值必须是 0(实测不为 0 也是可以的, 但系统会忽略所有 RGB 值). 因此, 使用 JPG 的图片是不行的, 最好的代替方案就是一张背景透明的 PNG 图片.
Android 7.X 机型, 通知栏小图标显示成灰色
问题详情
这个问题跟第二个有点类似, 在 7.0 系统及以上, 有部分应用的小图标是灰色的, 大图可以正常显示. 碰巧的是, 显示异常的小图标, 颜色都是灰色的.
解决方案
与小图标显示异常解决方案类似, 将小图标替换为透明背景的 PNG 图片.
RemoteViews 显示异常
问题详情
由于系统提供的通知栏消息类型有时候不能满足要求, 部分通知栏消息采用自定义 RemoteViews 来实现. 采用 RemoteViews, 特别是手动生成 Bitmap 然后直接传给一个自定义 Layout, 再通过 setContentView 方式设置通知栏消息时, 会存在各种各样的坑.
Android 通知栏的背景色有几种情况, 白色, 暗色, 暗色透明和黑色. 如果生成的 Bitmap 带背景色, 这个背景色就很难选择. 如果选择黑色背景, 那么在白色通知栏的机型上就很难看. 因此不能完全在各个系统上面完美展示出来. 如果不带背景色, 那么字体颜色也面临同样的困惑. 试想, 如果在白色的背景上显示白色的文字, 用户看到白茫茫一片, 是什么感受?
另一方面, 大部分厂商对原生的 Android 系统都会有各种各样的改造, 通知栏的样式也不例外. 如果按照原生的样式来设计, 那么在大部分国内厂商的机子上显示都和正常的普通通知栏消息不一样. 例如华为 6.0 系统的机子, 原生系统的时间线在右上角, 华为的在左边, 这样会给用户带来错觉.
解决方案
详见 RemoteViews 适配一节.
大尺寸小图标在部分机型上显示不正确
问题详情
这个问题主要在部分机型的 4.X 系统上遇见, 小图标大小没有按照 24dp 裁剪, 而是采用了桌面图标一样的大小 96dp. 具体适配不正常的机型有 HTC Desire 820,Lenovo A320T.
解决方案
按照标准来, 小图标大小为 24dp, 大图标为桌面 icon 图标大小 96dp. 具体可参考这里 http://iconhandbook.co.uk/reference/chart/android/ ^14 http://iconhandbook.co.uk/reference/chart/android/
部分机型不支持 Style
具体机型见下图以及后面统计的表格. 顺便提下, 小米是其中之一, 不知道他们为什么不支持额外的这些 Style.
点击查看大图
通知栏更新频率
问题详情
每个应用基本都有自更新的逻辑, App 开机的时候提示用户升级, 点击升级按钮后在 Notification 出现一个下载带进度条的通知. 应用一般是在开启一个工作线程在后台下载, 然后在下载的过程中通过回调更新通知栏中的进度条. 我们知道, 下载进度的快慢是不可控的, 如果每次下载中的回调都去更新通知栏, 那么可能几百毫秒, 几十毫秒, 甚至几毫秒就更新一次通知栏, 应用可能就会 ANR, 甚至崩溃.
解决方案
控制通知栏更新频率, 一般控制在 0.5s 或者 1s 就可以了. 在某一个更新时间间隔内下载的进度回调直接丢弃, 需要注意的是下载完成的回调, 需要实时回调通知栏消息显示下载完成.
恶心的后台通知和 "守护" 通知
问题详情
这个坑我不愿多介绍, 只说结果. 但凡存在后台通知或者 "守护" 通知的应用, 在 7.0 系统以后都会原形毕露. 还没有适配 7.0 的应用, 可长点心儿吧~
解决方案
请弃坑.
小米推送 SDK 接入问题
问题详情
为了提升推送到达, 考拉接入了小米推送的 SDK. 小米推送分为通知栏消息和透传消息, 通知栏消息属于系统级推送, 在 MIUI 的机子上可以在进程被杀死的情况下也能收到应用推送. 然而有个问题, 小米认为应用在前台时, 不会回调任何方法; 小米认为应用在后台的时候, 收到通知栏消息的同时, 会回调 onNotificationMessageArrived 方法. 这时候就要小心翼翼地处理这条消息了. 因为如果你的应用前后台判断逻辑和小米的不一样, 那么就有可能小米帮你发了一条通知栏消息, 你自己又发了一遍, 造成通知栏消息的重复发送(这个坑考拉踩过 T_T). 另一方面, 在 7.0 系统的机子上, 主标题和小图标的颜色是可以改变的, 目前小米推送 SDK 没有开放这个接口供调用方定制.
解决方案
目前只能解决第一个问题 -- 前后台判断的问题. 应用是否在后台可以根据以下代码进行判断. 在 Android 5.0 以上, 可以通过 ActivityManager.RunningAppProcessInfo 判断, Android 5.0 及以下版本通过 ActivityManager.RunningTaskInfo 判断. 经测试, 这个方案在 Android 4.4 以上结果是可以完全匹配的.
- public static boolean isAppInBackgroundInternal(Context context) {
- ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
- List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();
- if (!ListUtils.isEmpty(runningProcesses)) {
- for (ActivityManager.RunningAppProcessInfo runningProcess : runningProcesses) {
- if (runningProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
- return false;
- }
- }
- }
- } else {
- List<ActivityManager.RunningTaskInfo> task = manager.getRunningTasks(1);
- if (!ListUtils.isEmpty(task)) {
- ComponentName info = task.get(0).topActivity;
- if (null != info) {
- return !isKaolaProcess(info.getPackageName());
- }
- }
- }
- return true;
- }
Android 通知栏适配
RemoteViews 适配
由于系统自带的通知栏消息样式不能完全满足产品们脑洞大开的需求, 有时候我们需要自定义布局样式展示通知栏消息. Android 系统可以将自定义布局通过 setContent(7.X 系统推荐使用 setCustomContentView)设置到 Notification.Builder 中, 来实现样式的更变. setContent 方法需要传入一个 RemoteViews 对象, 它是一个普通的数据类型, 不是 View, 作用是供其他进程展示视图. RemoteViews 只支持 4 种基本的布局 ^9:
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
这些布局下面只支持几种视图控件:
- AnalogClock
- Button
- Chronometer
- ImageButton
- ImageView
- ProgressBar
- TextView
- ViewFlipper
- ListView
- GridView
- StackView
- AdapterViewFlipper
只能通过上述组合生成一个 RemoteViews.
自定义布局与视图
除了上面提到的布局与控件, 有没有办法自定义布局与视图呢? 我们知道, 任何一个 View, 都可以生成一个 Bitmap 对象, 支持的视图控件里有 ImageView, 可以通过 ImageView.setBitmapResource()将自定义视图设置到一个 ImageView 中, 然后再随便放到一个布局上, 就可以实现通知栏消息的任意布局. 理想是美好的, 但现实是残酷的. 使用这种方式自定义的布局, 会存在与原生的通知栏消息样式不一致的可能, 包括小图标 / 大图标的大小, 字体的大小与颜色, 时间的显示方式(不同版本的时间显示位置和样式都不一样). 下面解决一个最关键, 也最致命的问题 -- 字体颜色. 如果字体颜色和背景颜色一样, 那这条通知栏消息就没法看了, 如 RemoteViews 显示异常一节介绍的一样.
解决字体颜色和背景颜色一样的问题有三种解决方案, 分别是:
背景色固定不透明, 字体颜色与背景色形成反差.(360 和京东的做法)
背景色透明, 字体颜色采用系统原生的 notification_style.
背景色透明, 通过特殊方式拿到通知栏字体颜色和字体大小.
其中, 第一种方案简单, 能够兼容所有厂商机型. 例如京东固定背景色为黑色, 字体为红色. 这种方式的唯一缺陷是样式上不能与普通通知栏消息重合, 在白色背景的通知栏上极为显眼. 第二种方式, 通过阅读源码可知, 系统的通知栏标题和内容采用的颜色分别是 @Android:color/primary_text_dark 和 @Android:color/secondary_text_dark, 但踩过坑之后发现并非所有的机型默认都是这两个颜色, 有可能获取不到值. 因此这种方案只能作为参考, 不能用于实际环境中. 最后详细介绍一下第三种方式.
来源: https://www.cnblogs.com/163yun/p/10038542.html