教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载本文是 YCBlogs 仓库中 Android 技术笔记的经典总结篇。以 android/15.经典总结/11.通知栏简单分析.md 为核心骨架结合 12.通知栏适配梳理.md 与 20.工具库Lib/21.通知栏方案设计实践.md 的配套内容系统梳理 Android 通知栏从基础创建、自定义布局、PendingIntent 交互到 Android 8.0 通知渠道适配的完整技术链路。读完本文你将掌握 Notification 的完整创建与取消流程、各类 FLAG 与提醒效果的取舍、自定义通知栏布局的实现方式以及通知渠道NotificationChannel的创建、管理、删除与未读角标适配等实战能力。一、Notification 是什么全局效果的通知机制Notification 是一种具有全局效果的通知可以在系统的通知栏中显示。当 APP 向系统发出通知时它会先以图标的形式显示在状态栏中用户下拉通知栏即可查看通知的详细信息。通知栏和抽屉式通知栏均由系统控制用户可以随时查看。在 YCBlogs 的 通知栏方案设计实践 中对通知用途做了进一步归纳Notification 通知用途主要分三类接收消息提醒显示接收到的短消息、及时消息等信息如 QQ、微信、新浪、短信推送消息触达显示客户端的推送消息如广告、优惠、版本更新、推荐新闻等常用的第三方 SDK 有 JPush、个推、信鸽、网易云信偏重 IM、阿里云推送实时任务提醒显示正在进行的事物例如后台运行的程序如音乐播放进度、下载进度等。前两点可以归结为与用户交互第三点是实时的任务提醒但不可否认的是第三点也会与用户交互。通知栏默认不占用任何空间只有当用户用手指在状态栏上向下滑动时通知栏的内容才会显示出来这在智能手机发展初期极大地解决了手机屏幕过小、内容展示区域不足的问题。二、Notification 的基本操作2.1 必须添加的属性创建一条 Notification 时以下属性是必须添加的小图标通过setSmallIcon()方法设置标题通过setContentTitle()方法设置内容通过setContentText()方法设置。在 通知栏方案设计实践 的缺少属性不显示通知栏一节中明确记录如果缺少这几个元素通知栏无法正常展示出来。这是通知栏开发中第一个容易踩的坑。2.2 Notification 创建步骤Notification 的创建主要涉及三个核心类Notification.Builder使用建造者模式构建 Notification 对象。由于 Notification.Builder 仅支持 Android 4.1 及之后的版本为了解决兼容性问题Google 在 Android Support v4 中加入了 NotificationCompat.Builder 类。需要说明的是对于某些在 Android 4.1 之后才有的特性即使 NotificationCompat.Builder 支持该方法在之前的版本中也不能运行。Notification通知对应类保存通知相关的数据NotificationManager 向系统发送通知时会用到。NotificationManager通知管理类它是一个系统服务。调用 NotificationManager 的notify()方法可以向系统发送通知。完整的创建与发送代码如下// 创建一个NotificationManager的引用 NotificationManager mNotificationManager (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); // 定义Notification的各种属性 Notification.Builder mBuilder new Notification.Builder(this.getApplicationContext()) .setSmallIcon(R.mipmap.ic_launcher) //设置通知的图标 .setContentTitle(这个是标题) //设置标题 .setContentText(这个是内容) //消息内容 .setAutoCancel(true); //打开程序后图标消失 //处理点击Notification的逻辑 Intent resultIntent new Intent(this, TestActivity.class); resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //添加为栈顶Activity resultIntent.putExtra(what,5); PendingIntent resultPendingIntent PendingIntent.getActivity(this,5,resultIntent,PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); //发送 mNotificationManager.notify(1, mBuilder.build()); //结束广播 //mNotificationManager.cancel(1);2.3 setSmallIcon() 与 setLargeIcon() 的区别在 NotificationCompat.Builder 中有设置通知大小图标的两个方法二者的区别如下当setSmallIcon()与setLargeIcon()同时存在时smallIcon 显示在通知的右下角largeIcon 显示在左侧当只设置setSmallIcon()时smallIcon 显示在左侧。需要注意的是对于部分 ROM可能修改过源码如 MIUI 上通知的大图标和小图标是没有区别的。2.4 Notification 的 Action 属性设置一个 Action就可以直接跳转到 App 的某个 Activity、启动一个 Service 或者发送一个 Broadcast否则Notification 仅仅只能起到通知的效果而不能与用户交互。具体代码如下//创建intent Intent resultIntent new Intent(this, TestActivity.class); resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //添加为栈顶Activity resultIntent.putExtra(what,5); PendingIntent resultPendingIntent PendingIntent.getActivity(this,5,resultIntent,PendingIntent.FLAG_UPDATE_CURRENT); //发送pendingIntent mBuilder.setContentIntent(resultPendingIntent);2.5 更新和取消通知栏更新 Notification更新通知很简单只需要再次发送相同 ID 的通知即可。如果之前的通知还未被取消则会直接更新该通知相关的属性如果之前的通知已经被取消则会重新创建一个新通知。更新通知跟发送通知使用相同的方式。取消 Notification取消通知有如下 5 种方式点击通知栏的清除按钮会清除所有可清除的通知设置了setAutoCancel()或FLAG_AUTO_CANCEL的通知点击该通知时会清除它通过 NotificationManager 调用cancel(int id)方法清除指定 ID 的通知通过 NotificationManager 调用cancel(String tag, int id)方法清除指定 TAG 和 ID 的通知通过 NotificationManager 调用cancelAll()方法清除所有该应用之前发送的通知。注意事项如果你是通过NotificationManager.notify(String tag, int id, Notification notify)方法创建的通知那么只能通过NotificationManager.cancel(String tag, int id)方法才能清除对应的通知调用NotificationManager.cancel(int id)无效。2.6 设置 flag 属性设置FLAG_NO_CLEAR表示设置通知不能被状态栏的清除按钮给清除掉也不能被手动清除但能通过cancel()方法清除。示例代码如下private void sendNotification9() { Notification.Builder mBuilder new Notification.Builder(this.getApplicationContext()) .setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI) .setSmallIcon(R.mipmap.ic_launcher) //设置通知的图标 .setTicker(有新消息呢9) //设置状态栏的标题 .setContentTitle(这个是标题9) //设置标题 .setContentText(这个是内容9) //消息内容 .setDefaults(Notification.DEFAULT_ALL) //设置默认的提示音 .setOngoing(false) //让通知左右滑的时候不能取消通知 .setAutoCancel(true); //打开程序后图标消失 Notification notification mBuilder.build(); //设置 Notification 的 flags FLAG_NO_CLEAR //FLAG_NO_CLEAR 表示该通知不能被状态栏的清除按钮给清除掉,也不能被手动清除,但能通过 cancel() 方法清除 //flags 可以通过 | 运算叠加效果 notification.flags | Notification.FLAG_NO_CLEAR; //获取NotificationManager 对象 mNotificationManager.notify(9, notification); } //取消通知 if(mNotificationManager!null){ mNotificationManager.cancelAll(); }2.7 设置 Notification 的通知效果Notification 有震动、响铃、呼吸灯三种提醒效果可以通过setDefaults(int defualts)方法来设置。Default 属性有以下四种一旦设置了 Default 效果自定义的效果就会失效原文档作者明确记录在此处踩过坑愣是调了半天没找到为什么自定义效果会消失。//设置系统默认提醒效果一旦设置默认提醒效果则自定义的提醒效果会全部失效 //添加默认震动效果需要申请震动权限uses-permission android:nameandroid.permission.VIBRATE / Notification.DEFAULT_VIBRATE //添加系统默认声音效果设置此值后调用setSound()设置自定义声音无效 Notification.DEFAULT_SOUND //添加默认呼吸灯效果使用时须与 Notification.FLAG_SHOW_LIGHTS 结合使用否则无效 Notification.DEFAULT_LIGHTS //添加上述三种默认提醒效果 Notification.DEFAULT_ALL除了以上几种设置 Notification 默认通知效果还可以通过以下几种 FLAG 设置通知效果//提醒效果常用Flag //三色灯提醒在使用三色灯提醒时候必须加该标志符 Notification.FLAG_SHOW_LIGHTS //发起正在运行事件活动中 Notification.FLAG_ONGOING_EVENT //让声音、振动无限循环直到用户响应取消或者打开 Notification.FLAG_INSISTENT //发起Notification后铃声和震动均只执行一次 Notification.FLAG_ONLY_ALERT_ONCE //用户单击通知后自动消失 Notification.FLAG_AUTO_CANCEL //只有调用NotificationManager.cancel()时才会清除 Notification.FLAG_NO_CLEAR //表示正在运行的服务 Notification.FLAG_FOREGROUND_SERVICE设置默认提醒// 添加默认声音提醒 builder.setDefaults(Notification.DEFAULT_SOUND); // 添加默认呼吸灯提醒自动添加FLAG_SHOW_LIGHTS builder.setDefaults(Notification.DEFAULT_LIGHTS);设置铃声属性用的很少private void sendNotification11() { Notification.Builder builder new Notification.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(我是伴有铃声效果的通知11) .setContentText(美妙么?安静听~11) //调用系统默认响铃,设置此属性后setSound()会无效 //.setDefaults(Notification.DEFAULT_SOUND) //调用系统多媒体裤内的铃声 //.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,2)); //调用自己提供的铃声位于 /res/values/raw 目录下 .setSound(Uri.parse(android.resource://com.yc.cn.ycnotification/ R.raw.hah)); //另一种设置铃声的方法 //Notification notify builder.build(); //调用系统默认铃声 //notify.defaults Notification.DEFAULT_SOUND; //调用自己提供的铃声 //notify.sound Uri.parse(android.resource://com.yc.cn.ycnotification/R.raw.sound); //调用系统自带的铃声 //notify.sound Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,2); //mManager.notify(11,notify); mNotificationManager.notify(11, builder.build()); }设置震动属性private void sendNotification12() { //震动也有两种设置方法,与设置铃声一样,在此不再赘述 long[] vibrate new long[]{0, 500, 1000, 1500}; Notification.Builder builder new Notification.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(我是伴有震动效果的通知) .setContentText(颤抖吧,逗比哈哈哈哈哈~) //使用系统默认的震动参数,会与自定义的冲突 //.setDefaults(Notification.DEFAULT_VIBRATE) //自定义震动效果 .setVibrate(vibrate); //另一种设置震动的方法 //Notification notify builder.build(); //调用系统默认震动 //notify.defaults Notification.DEFAULT_VIBRATE; //调用自己设置的震动 //notify.vibrate vibrate; //mManager.notify(3,notify); mNotificationManager.notify(12, builder.build()); }三、自定义通知栏布局操作通过setContent(RemoteViews)可以为通知设置自定义布局常用于音乐播放器等需要展示自定义控件的场景。核心代码如下.setContent(getRemoteViews()) // 设置通知栏的布局 //创建自定义布局 private RemoteViews getRemoteViews() { RemoteViews remoteViews new RemoteViews(getPackageName(), R.layout.notification_mobile_play); // 设置 点击通知栏的上一首按钮时要执行的意图 remoteViews.setOnClickPendingIntent(R.id.btn_pre, getActivityPendingIntent(11)); // 设置 点击通知栏的下一首按钮时要执行的意图 remoteViews.setOnClickPendingIntent(R.id.btn_next, getActivityPendingIntent(12)); // 设置 点击通知栏的播放暂停按钮时要执行的意图 remoteViews.setOnClickPendingIntent(R.id.btn_start, getActivityPendingIntent(13)); // 设置 点击通知栏的根容器时要执行的意图 remoteViews.setOnClickPendingIntent(R.id.ll_root, getActivityPendingIntent(14)); remoteViews.setTextViewText(R.id.tv_title, 标题); // 设置通知栏上标题 remoteViews.setTextViewText(R.id.tv_artist, 艺术家); // 设置通知栏上艺术家 return remoteViews; }RemoteViews 的核心价值在于它是跨进程可用的视图描述对象通知由系统进程NotificationManagerService负责渲染App 自身进程无法直接把 View 交给系统只能通过 RemoteViews 描述布局资源与控件动作再由系统进程完成加载。四、Notification 相关属性说明PendingIntent 深入4.1 PendingIntent 是什么PendingIntent 是一种特殊的 Intent字面意思可以解释为延迟的 Intent用于在某个事件结束后执行特定的 Action。从上面带 Action 的通知也能验证这一点当用户点击通知时才会执行。PendingIntent 是 Android 系统管理并持有的用于描述和获取原始数据的对象的标志引用。也就是说即便创建该 PendingIntent 对象的进程被杀死了这个 PendingIntent 对象在其他进程中还是可用的。日常使用中的短信、闹钟等都用到了 PendingIntent。在 YCBlogs 的 13.Intent深入分析思考.md 中对 PendingIntent 有专门的章节分析其与普通 Intent 的本质差异在于Intent 是即刻执行的意图而 PendingIntent 由系统持有、在未来某个时刻触发这也是通知栏交互得以实现的底层机制。4.2 PendingIntent 三种获取方式//获取一个用于启动 Activity 的 PendingIntent 对象 public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags); //获取一个用于启动 Service 的 PendingIntent 对象 public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags); //获取一个用于向 BroadcastReceiver 广播的 PendingIntent 对象 public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags);4.3 PendingIntent 的 flag 说明FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的 PendingIntent 对象那么就将先将已有的 PendingIntent 取消然后重新生成一个 PendingIntent 对象。 FLAG_NO_CREATE:如果当前系统中不存在相同的 PendingIntent 对象系统将不会创建该 PendingIntent 对象而是直接返回 null。 FLAG_ONE_SHOT:该 PendingIntent 只作用一次。 FLAG_UPDATE_CURRENT:如果系统中已存在该 PendingIntent 对象那么系统将保留该 PendingIntent 对象但是会使用新的 Intent 来更新之前 PendingIntent 中的 Intent 对象数据例如更新 Intent 中的 Extras。4.4 创建返回栈 PendingIntentTaskStackBuilder默认情况下从通知启动一个 Activity按返回键会回到主屏幕。但某些时候有按返回键仍然留在当前应用的需求这就要用到 TaskStackBuilder 了。示例代码如下Notification.Builder mBuilder new Notification.Builder(context) .setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle(广播接受者标题小杨) .setContentText(广播接受者内容扯犊子) .setAutoCancel(true); Log.i(TAG, onReceive: intent intent.getClass().getName()); Intent resultIntent new Intent(context, MainActivity.class); TaskStackBuilder stackBuilder TaskStackBuilder.create(context); //将该Activity添加为栈顶 stackBuilder.addParentStack(MainActivity.class); stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotificationManager (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.notify(1, mBuilder.build());注意要点如果用户的手机处于静音模式那么设置铃声或者震动效果将会失效。五、源码视角通知栏相关类的职责划分虽然原文档第五节部分源码分析思考为待完善的占位内容但结合仓库 通知栏方案设计实践 中梳理的类图可以从源码结构清晰地看到通知体系的职责划分Notification通知对应类保存通知相关的数据是客户端与系统之间传递的通知载体NotificationChannel8.0 新增的通知渠道用于对通知进行分类管理NotificationChannelGroup渠道组用于在设置界面中对多个渠道做分组展示NotificationManager通知管理类是一个系统服务调用其notify()方法向系统发送通知是客户端侧的入口INotificationManagerNotificationManager 通过 AIDLBinder与系统服务通信时使用的接口这点与 YCBlogs Toast源码深度分析 中INotificationManager.Stub.asInterface(ServiceManager.getService(notification))的获取方式一脉相承NotificationCompat / NotificationCompatBuilderAndroidX 提供的兼容版本解决不同系统版本之间的 API 差异NotificationManagerService系统侧的真正实现负责通知的排队、展示与调度。可以推断客户端发送通知的调用链为NotificationManager.notify() → INotificationManager → NotificationManagerService系统服务最终负责通知的渲染与交互。这也解释了为什么通知栏内容即使 App 进程被杀通知依然能正常展示与点击——真正持有通知的永远是系统进程。六、Android 8.0 通知栏适配通知渠道NotificationChannel6.1 Android O 通知的改变NotificationChannel 是 Android 8.0 新增的特性。如果 App 的 targetSDKVersion 26没有设置 channel 通知渠道的话就会导致通知无法展示。Android O 引入了通知渠道Notification Channels以提供统一的系统来帮助用户管理通知。如果是针对 android O 为目标平台时必须实现一个或者多个通知渠道以向用户显示通知。比如聊天软件可以为每个聊天组设置一个通知渠道指定特定声音、灯光等配置。从 8.0 开始Google 将每条通知与某个渠道绑定每个 App 都可以自由地创建当前 App 拥有哪些通知渠道但这些通知渠道的控制权都掌握在用户手上用户可以自由地选择这些通知渠道的重要程度、是否响铃、是否振动、或者是否要关闭这个渠道的通知。拥有了这些控制权之后用户就再也不用害怕那些垃圾推送消息的打扰了。值得注意的是通知渠道一旦创建之后就不能再修改了因此开发者需要仔细分析自己的 App 一共有哪些类型的通知然后再去创建相应的通知渠道例如聊天消息、邮件、订阅推送可以分别建成独立渠道。6.2 报错内容和解决方案报错内容Failed to post notification on channel null Target Api is 26解决方案第一种临时方案Google 也考虑到适配问题临时兼容方案是将 targetSDKVersion 降到 26 以下第二种最终方案创建 channel。如果你将项目中的 targetSdkVersion 指定到了 26 或者更高那么 Android 系统就会认为你的 App 已经做好了 8.0 系统的适配工作当然包括通知栏的适配。这个时候如果还不使用通知渠道的话那么 App 的通知将完全无法弹出。因此这里给出的建议是一定要适配。6.3 最终解决方案创建 NotificationChannel创建 NotificationChannel 对象指定 Channel 的 id、name 和通知的重要程度然后使用 NotificationManager 的 createNotificationChannel 方法来添加 Channelif (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { //android 8.0以上需要特殊处理也就是targetSDKVersion为26以上 createNotificationChannel(); } TargetApi(Build.VERSION_CODES.O) private void createNotificationChannel() { NotificationChannel channel new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT); getManager().createNotificationChannel(channel); }七、通知渠道的完整实践创建、优先级、发送与管理7.1 多渠道创建在实际业务中一个 App 往往需要多个渠道。以下代码演示了按业务类型聊天、邮件、订阅分别创建渠道的做法private void createChannel(){ if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { String channelId chat; String channelName 通知消息; int importance NotificationManager.IMPORTANCE_HIGH; createNotificationChannel(channelId, channelName, importance); channelId email; channelName 通知邮件; importance NotificationManager.IMPORTANCE_DEFAULT; createNotificationChannel(channelId, channelName, importance); channelId subscribe; channelName 通知订阅; importance NotificationManager.IMPORTANCE_DEFAULT; createNotificationChannel(channelId, channelName, importance); } } TargetApi(Build.VERSION_CODES.O) private void createNotificationChannel(String channelId, String channelName, int importance) { NotificationChannel channel new NotificationChannel(channelId, channelName, importance); if (mNotificationManagernull){ mNotificationManager (NotificationManager) getSystemService( NOTIFICATION_SERVICE); } mNotificationManager.createNotificationChannel(channel); }创建一个通知渠道至少需要渠道 ID、渠道名称以及重要等级这三个参数渠道 ID 可以随便定义只要保证全局唯一性即可渠道名称是给用户看的需要能够表达清楚这个渠道的用途重要等级的不同会决定通知的不同行为当然这只是初始状态下的重要等级用户可以随时手动更改某个渠道的重要等级App 是无法干预的。Channel 创建多次怎么办创建通知渠道的代码只在第一次执行的时候才会真正创建以后每次执行创建代码系统会检测到该通知渠道已经存在了因此不会重复创建也并不会影响任何效率。7.2 通知栏优先级为何要设置模拟一个类似微信的 App其 App 通知主要可以分为两类一类是聊天消息这类消息非常重要因此重要等级设为了IMPORTANCE_HIGH另一类是公众号的订阅消息这类消息不是那么重要因此重要等级设为了IMPORTANCE_DEFAULT。除此之外重要等级还可以设置为IMPORTANCE_LOW、IMPORTANCE_MIN分别对应更低的通知重要程度。由于这两个通知渠道的重要等级不同通知的行为也是不同的聊天消息可以发出提示音并在屏幕上弹出通知提醒通知而订阅消息只能发出提示音静音通知。这解释了为什么通知栏会显示提醒通知与静音通知的区别——前者是重要等级高的渠道会使用屏幕弹窗的方式通知用户后者只在状态栏上显示一个小小通知图标。7.3 发送带渠道的通知触发通知的代码和之前版本基本没有区别只是在构建通知对象的时候需要多传入一个通知渠道 ID表示这条通知是属于哪个渠道的public void sendChatMsg() { NotificationManager manager (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Notification notification new NotificationCompat.Builder(this, chat) .setContentTitle(收到聊天消息) .setContentText(在吗找你有事情) .setWhen(System.currentTimeMillis()) .setSmallIcon(R.drawable.ic_notification_music_logo) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_notification_music_logo)) .setAutoCancel(true) .build(); manager.notify(1, notification); }注意在 NotificationCompat.Builder 中需要多传入一个通知渠道 ID分别传入 chat 和 subscribe 渠道 ID 来区分消息类型。7.4 Android 8.0 通知交互的变化8.0 之后快速向左或者向右滑动可以关闭一条通知但如果你缓慢地向左或者向右滑动就会看到两个按钮时钟延迟按钮可以让通知延迟显示。例如一条比较重要的通知暂时没时间看也不想让它一直显示在状态栏里打扰就可以让它延迟一段时间后再显示这样不会错过任何信息设置按钮可以对通知渠道进行屏蔽和配置用户对每一个 App 的每一个通知渠道都有绝对的控制权。例如觉得订阅消息老是推荐广告太烦就可以将订阅消息的通知渠道关闭掉以后就不会再收到该渠道下的任何消息而聊天消息却不受影响——这就是 8.0 系统通知渠道最大的特色。7.5 管理通知栏渠道通知渠道一旦创建之后就不能再通过代码修改了。既然不能修改那还怎么管理呢为此Android 赋予了开发者读取通知渠道配置的权限如果某个功能必须按照指定要求来配置通知渠道才能使用那么就可以提示用户去手动更改通知渠道配置。例如类似微信的 App聊天消息至关重要如果用户不小心将聊天消息的通知渠道关闭了重要信息就全部丢失了为此一定要保证用户打开了聊天消息的通知渠道。通过getNotificationChannel()方法获取 NotificationChannel 对象然后读取该通知渠道下的所有配置//生效 NotificationChannel channel manager.getNotificationChannel(chat);具体判断逻辑如果通知渠道的 importance 等于IMPORTANCE_NONE就说明用户将该渠道的通知关闭了这时会跳转到通知的设置界面提醒用户手动打开public void openChannelSetting(NotificationChannel channel){ if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { if (channel.getImportance() NotificationManager.IMPORTANCE_NONE) { Intent intent new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId()); startActivity(intent); } } }当将指定消息的通知渠道关闭后下次再次发送消息将会直接跳转到通知设置界面提醒用户手动将通知打开。7.6 删除通知栏渠道Android 8.0 还赋予了删除通知渠道的功能NotificationManager manager (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.deleteNotificationChannel(channelId);这个功能非常不建议使用Google 为了防止应用程序随意地创建垃圾通知渠道会在通知设置界面显示所有被删除的通知渠道数量。仓库笔记也提出了一个待深入的话题——创建通知栏后 8.0 会有不同的 channelId如何获取 App 中其他地方创建的渠道值例如谷歌 SDK 在创建通知栏之前根据包名删除该应用其他通知栏的实现方式这需要结合 NotificationManagerService 的系统级实现进一步研究。7.7 显示未读角标未读角标功能长期以来都没有一个标准的 API 来实现很多都是要通过向系统发送广播来实现的而各个手机厂商的广播标准又不一致经常导致代码变得极其混杂。从 8.0 系统开始Google 制定了 Android 系统上的角标规范也提供了标准的 API。主要修改两个地方第一在创建通知渠道的时候调用 NotificationChannel 的setShowBadge(true)方法表示允许这个渠道下的通知显示角标第二在创建通知的时候调用setNumber()方法并传入未读消息的数量。需要注意的是即使不调用setShowBadge(true)方法Android 系统默认也是会显示角标的如果你想禁用角标功能那么记得一定要调用setShowBadge(false)方法。八、通知栏的两个进阶场景8.1 前台服务与通知栏Service 几乎都是在后台运行的所以 Service 系统优先级比较低当系统出现内存不足情况时就有可能回收掉正在后台运行的 Service。如果希望 Service 可以一直保持运行状态而不被系统回收就要提高 Service 的优先级其中一种方式就是使用前台服务。YCBlogs 的 Service基础介绍 中同样提到前台服务会一直有一个正在运行的图标在系统的状态栏显示非常类似于通知的效果而 BroadCastReceiver 中也指出担心 Service 被回收时可通过startForeground(int, Notification)提升其优先级。把 Service 设置成前台 Service 很简单使用startForeground即可要取消前台则使用stopForeground。Android 8.0 行为变更说明系统不允许后台应用创建后台服务。因此 Android 8.0 引入了一种全新的方法Context.startForegroundService()以在前台启动新服务在系统创建服务后应用有五秒的时间来调用该服务的startForeground()方法以显示新服务的用户可见通知。仓库笔记还记录了一个崩溃场景通过 startForegroundService 开启 Service 后如果不调用 startForeground 会出现崩溃异常信息为Context.startForegroundService() did not then call Service.startForeground()。通过崩溃日志可定位到 ActiveServices 类中的serviceForegroundTimeout方法和serviceForegroundCrash这正是系统对前台服务超时未升级的强制约束。8.2 悬挂式Heads Up通知悬挂式通知是 Android 5.0 新增的不需要下拉通知栏就直接显示出来悬挂在屏幕上方并且焦点不变仍在用户操作的界面因此不会打断用户的操作过几秒就会自动消失。这种行为通常适用于用户应立即知晓的重要通知而且仅在设备未锁定时才会显示。使用 Heads Up Notification 有必要前提条件Android API 21即 5.0 以上需要在设置中开启横幅通知权限在设置通知管理中不同机型可能存在差异必须设置震动或声音必须将通知设置为最高权限如IMPORTANCE_HIGH。同时要注意在某些平台上当用户正在使用设备时系统 UI 可能会选择显示一个提醒通知而不是启动setFullScreenIntent指定的意图这是不同系统版本和机型上的表现差异。九、总结与避坑清单综合 11.通知栏简单分析.md 与仓库配套笔记通知栏开发的核心要点可以归纳为一份避坑清单三个必填属性缺一不可小图标、标题、内容缺少则通知无法展示setDefaults 与自定义效果互斥一旦设置 Default 效果自定义的铃声、震动效果会全部失效取消通知要匹配创建方式通过notify(String tag, int id, ...)创建的通知只能用cancel(String tag, int id)清除8.0 适配是硬性要求targetSdkVersion 26 时必须创建通知渠道否则通知直接无法弹出报错Failed to post notification on channel null渠道一经创建不可修改创建前务必规划好渠道划分与重要等级渠道创建是幂等的重复创建无副作用用户拥有渠道绝对控制权App 无法干预用户对渠道重要程度的修改只能通过getNotificationChannel()读取状态并引导用户开启删除渠道要慎用系统会在设置界面展示被删除渠道的数量垃圾渠道会直接暴露给用户静音模式下铃声/震动失效如果用户的手机处于静音模式设置铃声或者震动效果将会失效。通知栏作为 App 触达用户的重要入口其实现细节横跨客户端 API、Binder 通信INotificationManager与系统服务NotificationManagerService三层。掌握本文梳理的创建流程、FLAG 语义与 8.0 渠道机制即可在 YCBlogs 所覆盖的各类业务场景IM 消息、推送触达、前台服务、音乐播放控制等中稳定落地通知栏能力。赞分享教程技术博客文档【免费下载链接】YCBlogs技术博客笔记大汇总包括Java基础线程并发数据结构Android技术博客等等常用设计模式常见的算法网络协议知识点部分flutter笔记还包括平时开发中遇到的bug汇总当然也在工作之余收集了大量的面试题长期更新维护并且修正持续完善……开源的文件是markdown格式的转载请注明出处谢谢项目地址https://gitcode.com/gh_mirrors/yc/YCBlogs点击查看免费下载相关推荐foobox-cn重新定义foobar2000界面美化打造极致音乐播放体验foobox cn重新定义foobar2000界面美化打造极致音乐播放体验 你是否曾为foobar2000强大的音频处理能力所吸引却对其陈旧的界面设计感到桌面应用音视频如何轻松下载网页视频猫抓扩展帮你解决三大难题如何轻松下载网页视频猫抓扩展帮你解决三大难题 你是否曾经遇到这样的情况在网上看到一个精彩的视频教程、有趣的短视频或者重要的在线课程想要下载保存却不知道如何音视频TuChart与Tushare完美结合如何获取并可视化中国股票市场数据TuChart与Tushare完美结合如何获取并可视化中国股票市场数据 TuChart 是一个基于PyQt和ECharts的强大股票可视化工具通过与 Tus上一篇把《骑马与砍杀2》变成终极多人联机BannerlordCoop完整上手指南下一篇被此电脑里的顽固快捷方式霸屏这款免费开源小工具一键帮你清理干净创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考