N:全面解析Notification - linxiaoxing/MyTest GitHub Wiki

Samples

Android 通知栏Notification的整合 全面学习 (一个DEMO让你完全了解它)

比较旧:https://blog.csdn.net/vipzjyno1/article/details/25248021

Android Notification 通知详细笔记 通知通道(API 26新增,用户可以选择性屏蔽通知):NotificationChannel

https://denua.cn/blog/article/18/

Android通知栏(Notification)介绍及使用

2018年04月08日 15:50:13 cyt_victory 阅读数 18961更多 分类专栏: Android demo 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_34163551/article/details/79851542 在使用手机时,我们常常会碰到各种通知,例如微信,头条,UC等,天天不厌其烦的给你各种推送,当然了我们今天不讲推送,我们讲讲通知栏的构建和使用,以及自定义通知栏的布局和使用方法

构建一个通知栏一般分为这几个步骤:

1.创建通知栏管理工具 2.构建通知栏构造器 3.给构造器设置参数 4.发送请求

具体代码如下:

   /**
     *  创建通知栏管理工具
     */
    NotificationManager notificationManager = (NotificationManager) getSystemService
            (NOTIFICATION_SERVICE);
    /**
     *  实例化通知栏构造器
     */
    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
    /**
     *  设置Builder
     */
    //设置标题
    mBuilder.setContentTitle("我是标题")
            //设置内容
            .setContentText("我是内容")
            //设置大图标
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
            //设置小图标
            .setSmallIcon(R.mipmap.ic_launcher_round)
            //设置通知时间
            .setWhen(System.currentTimeMillis())
            //首次进入时显示效果
            .setTicker("我是测试内容")
            //设置通知方式,声音,震动,呼吸灯等效果,这里通知方式为声音
            .setDefaults(Notification.DEFAULT_SOUND);
    //发送通知请求
    notificationManager.notify(10, mBuilder.build());

因为我在代码中注释的比较清楚,这里就不一一赘述了

华为手机 小米手机 显然直接使用原生的通知栏会在不通顺手机上显示不同效果,无法形成统一性,也不是特别美观,所以我们需要自定义通知栏

自定义通知栏和使用原生的通知栏区别不大,最主要就是增加了自定义的布局,使用RemoteViews承接,并放入构造器中显示,具体代码如下

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
    NotificationManager notificationManager = (NotificationManager) getSystemService
            (NOTIFICATION_SERVICE);
    mBuilder.setSmallIcon(R.mipmap.timg);
    mBuilder.setContent(remoteViews);
    if (progress == 1) {
        mBuilder.setDefaults(Notification.DEFAULT_SOUND);
    }
    remoteViews.setImageViewResource(R.id.image, R.mipmap.timg);
    remoteViews.setTextViewText(R.id.title, "我是标题");
    remoteViews.setTextViewText(R.id.content, "我是内容");
    remoteViews.setProgressBar(R.id.pBar, 10, progress, false);
    remoteViews.setTextViewText(R.id.proNum, progress + "/10");
    notificationManager.notify(10, mBuilder.build());

自定义标题栏

点击跳转到其他页面:

Intent intent = new Intent(this, SecondeActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); mBuilder.setContentIntent(pendingIntent);

到此,具体工作就已经完成的差不多了,你会用了吗,当然还有很多知识点,想学更多的还有可以去看具体源码

遇到的问题: (1) 在Android O及以上版本报Developer warning for package “” Failed to post notification on channel “null” see log for more details 错误,错误原因是因为在8.0以上需要增加渠道名称和渠道ID,具体代码如下:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(id, name, NotificationManager .IMPORTANCE_DEFAULT); mBuilder.setChannelId(id); notificationManager.createNotificationChannel(channel);

        mBuilder.setSmallIcon(R.mipmap.timg);
        mBuilder.setContent(remoteViews);
        if (progress == 1) {
            mBuilder.setDefaults(Notification.DEFAULT_SOUND);
        }
        remoteViews.setImageViewResource(R.id.image, R.mipmap.timg);
        remoteViews.setTextViewText(R.id.title, "我是标题");
        remoteViews.setTextViewText(R.id.content, "我是内容");
        remoteViews.setProgressBar(R.id.pBar, 10, progress, false);
        remoteViews.setTextViewText(R.id.proNum, progress + "/10");
        notificationManager.notify(10, mBuilder.build());
    }

(2)mBuilder.setSmallIcon()是必须要加上的,这个是显示在顶部状态栏中的小图标,如果未加这个图标程序将会闪退,并报以下错误 java.lang.IllegalArgumentException: Invalid notification (no valid small icon): Notification(pri=0 contentView=cm.cui.testnotification/0x7f09001d vibrate=null sound=default defaults=0x1 flags=0x0 color=0x00000000 vis=PRIVATE)

csdn地址 GitHub下载 https://github.com/cuiyongtao/TestNotification

全面解析Notification

2016年12月11日 19:13:52 伯努力不努力 阅读数 23539 文章标签: 安卓通知栏 更多 分类专栏: 开发笔记 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/u012124438/article/details/53574649 Notification在Android中使用的频率可以说是非常高的,本篇博客,我将围绕着Notification的各方面进行解析,使大家对Notification有更好的认识。

Notification的使用步骤 1.获取NotificationManager

NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
1 2.创建NotificationCompat.Builder

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
1 3.对Builder设置一些Notification相关属性:

mBuilder.setContentTitle("标题")//设置通知栏标题
.setContentText("内容") //设置通知栏显示内容 .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL)) //设置通知栏点击意图
// .setNumber(number) //设置通知集合的数量
.setTicker("通知到来") //通知首次出现在通知栏,带上升动画效果的
.setWhen(System.currentTimeMillis())//通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
.setPriority(Notification.PRIORITY_DEFAULT) //设置该通知优先级
// .setAutoCancel(true)//设置这个标志当用户单击面板就可以让通知将自动取消
.setOngoing(false)//ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
.setDefaults(Notification.DEFAULT_VIBRATE)//向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用defaults属性,可以组合
//Notification.DEFAULT_ALL Notification.DEFAULT_SOUND 添加声音 // requires VIBRATE permission
.setSmallIcon(R.drawable.ic_launcher);//设置通知小ICON

4.使用Builder创建通知

Notification notification = mBuilder.build(); 1 5.使用NotificationManager将通知推送出去

int id = 199; LogUtils.d(TAG, "创建通知"); mNotificationManager.notify(id, notification);

Notification重要方法解析 Notification 的基本操作主要有创建、更新、取消这三种。一个 Notification 的必要属性有三项,如果不设置则在运行时会抛出异常:

小图标,通过 setSmallIcon() 方法设置 标题,通过 setContentTitle() 方法设置 内容,通过 setContentText() 方法设置 除了以上三项,其它均为可选项。虽然如此,但还是应该给 Notification 设置一个 Action ,这样就可以直接跳转到 App 的某个 Activity 、启动一个 Service 或者发送一个 Broadcast。否则,Notification 仅仅只能起到通知的效果,而不能与用户交互。 当系统接收到通知时,可以通过震动、响铃、呼吸灯等多种方式进行提醒。

  1. setSmallIcon() 与 setLargeIcon() 在 NotificationCompat.Builder 中有设置通知的大小图标的两个方法。这两个方法有什么区别呢?当 setSmallIcon() 与 setLargeIcon() 同时存在时, smallIcon 显示在largeIcon的右下角;当只设置 setSmallIcon() 时, smallIcon 显示在左侧。看下图你就明白了。对于部分 ROM ,可能修改过源码,如 MIUI 上通知的大图标和小图标是没有区别的。 这里写图片描述 Google 官方是这么解释 setSmallIcon() 这个方法的:

Set the small icon resource, which will be used to represent the notification in the status bar. The platform template for the expanded view will draw this icon in the left, unless a large icon has also been specified, in which case the small icon will be moved to the right-hand side. 1 2) 设置提醒标志符Flags 方法解释:提醒标志符,向通知添加声音、闪灯和振动效果等设置达到通知提醒效果,可以组合多个属性 a) 创建通知栏之后通过给他添加.flags属性赋值。

  1. Notification notification = mBuilder.build();
  2. notification.flags = Notification.FLAG_AUTO_CANCEL;

b) 通过setContentIntent(PendingIntent intent)方法中的意图设置对应的flags 1

  1. public PendingIntent getDefalutIntent(int flags){
  2.  PendingIntent pendingIntent= PendingIntent.getActivity(this, 1, new Intent(), flags);  
    
  3.  return pendingIntent;  
    
  4. }
    标志符介绍

Notification.FLAG_SHOW_LIGHTS //三色灯提醒,在使用三色灯提醒时候必须加该标志符 Notification.FLAG_ONGOING_EVENT //发起正在运行事件(活动中) Notification.FLAG_INSISTENT //让声音、振动无限循环,直到用户响应 (取消或者打开) Notification.FLAG_ONLY_ALERT_ONCE //发起Notification后,铃声和震动均只执行一次 Notification.FLAG_AUTO_CANCEL //用户单击通知后自动消失 Notification.FLAG_NO_CLEAR //只有全部清除时,Notification才会清除 ,不清楚该通知(QQ的通知无法清除,就是用的这个) Notification.FLAG_FOREGROUND_SERVICE //表示正在运行的服务

  1. .setDefaults(int defaults) (NotificationCompat.Builder中的方法,用于设置通知到来时,通过什么方式进行提示)

方法解释:向通知添加声音、闪灯和振动效果的最简单、使用默认(defaults)属性,可以组合多个属性(和方法1中提示效果一样的) 对应属性: Notification.DEFAULT_VIBRATE //添加默认震动提醒 需要 VIBRATE permission

Notification.DEFAULT_SOUND // 添加默认声音提醒 Notification.DEFAULT_LIGHTS// 添加默认三色灯提醒 Notification.DEFAULT_ALL// 添加默认以上3种全部提醒

/**

  • 显示带有默认铃声、震动、呼吸灯效果的通知
  • 如需实现自定义效果,请参考后面三个例子 */ private void showNotifyWithMixed() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("我是有铃声+震动+呼吸灯效果的通知") .setContentText("库里就是叼~") //等价于setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE); .setDefaults(Notification.DEFAULT_ALL); mManager.notify(5, builder.build()); }
  1. setVibrate(long[] pattern) 方法解释:设置震动的时间

.setVibrate(new long[] {0,300,500,700});
1 实现效果:延迟0ms,然后振动300ms,在延迟500ms,接着在振动700ms。 还有另外一种写法:

mBuilder.build().vibrate = new long[] {0,300,500,700};
1 如果希望设置默认振动方式,设置了方法(2)中默认为DEFAULT_VIBRATE 即可。 例子:

/**

  • 展示有震动效果的通知,需要在AndroidManifest.xml中申请震动权限
  • 补充:测试震动的时候,手机的模式一定要调成铃声+震动模式,否则你是感受不到震动的 */ private void showNotifyWithVibrate() { //震动也有两种设置方法,与设置铃声一样,在此不再赘述 long[] vibrate = new long[]{0, 500, 1000, 1500}; NotificationCompat.Builder builder = new NotificationCompat.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); mManager.notify(3, builder.build()); }

4)方法:.setLights(intledARGB ,intledOnMS ,intledOffMS ) 方法解释:android支持三色灯提醒,这个方法就是设置不同场景下的不同颜色的灯。 描述:其中ledARGB 表示灯光颜色、 ledOnMS 亮持续时间、ledOffMS 暗的时间。 注意: 1)只有在设置了标志符Flags为Notification.FLAG_SHOW_LIGHTS的时候,才支持三色灯提醒。 2)这边的颜色跟设备有关,不是所有的颜色都可以,要看具体设备。

Notification notify = mBuilder.build();
notify .setLights(0xff00eeff, 500, 200)

同理,以下方法也可以设置同样效果:

Notification notify = mBuilder.build();
notify.flags = Notification.FLAG_SHOW_LIGHTS;
notify.ledARGB = 0xff00eeff;
notify.ledOnMS = 500;
notify.ledOffMS = 400;

如果希望使用默认的三色灯提醒,设置了方法(2)中默认为DEFAULT_LIGHTS即可。 例子:

/**

  • 显示带有呼吸灯效果的通知,但是不知道为什么,自己这里测试没成功 */ private void showNotifyWithLights() { final NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("我是带有呼吸灯效果的通知") .setContentText("一闪一闪亮晶晶~") //ledARGB 表示灯光颜色、 ledOnMS 亮持续时间、ledOffMS 暗的时间 .setLights(0xFF0000, 3000, 3000); Notification notify = builder.build(); //只有在设置了标志符Flags为Notification.FLAG_SHOW_LIGHTS的时候,才支持呼吸灯提醒。 notify.flags = Notification.FLAG_SHOW_LIGHTS; //设置lights参数的另一种方式 //notify.ledARGB = 0xFF0000; //notify.ledOnMS = 500; //notify.ledOffMS = 5000; //使用handler延迟发送通知,因为连接usb时,呼吸灯一直会亮着 Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { mManager.notify(4, builder.build()); } }, 10000); }

5)方法:.setSound(Uri sound)

方法解释:设置默认或则自定义的铃声,来提醒。

//获取默认铃声
.setDefaults(Notification.DEFAULT_SOUND)
//获取自定义铃声
.setSound(Uri.parse("file:///sdcard/dance.mp3"))
//获取Android多媒体库内的铃声
.setSound(Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "5"))

同理相同效果的另一种设置方法这边就不讲, 和上面的都是一样的。 例子:

/**

  • 展示有自定义铃声效果的通知
  • 补充:使用系统自带的铃声效果:Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6"); */ private void showNotifyWithRing() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("我是伴有铃声效果的通知") .setContentText("美妙么?安静听~") //调用系统默认响铃,设置此属性后setSound()会无效 //.setDefaults(Notification.DEFAULT_SOUND) //调用系统多媒体裤内的铃声 //.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2")); //调用自己提供的铃声,位于 /res/values/raw 目录下 .setSound(Uri.parse("android.resource://com.littlejie.notification/" + R.raw.sound)); //另一种设置铃声的方法 //Notification notify = builder.build(); //调用系统默认铃声 //notify.defaults = Notification.DEFAULT_SOUND; //调用自己提供的铃声 //notify.sound = Uri.parse("android.resource://com.littlejie.notification/"+R.raw.sound); //调用系统自带的铃声 //notify.sound = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"); //mManager.notify(2,notify); mManager.notify(2, builder.build()); }

6)方法:.setPriority(int pri) 方法解释:设置优先级(实际项目中并无大用,设置最高级也不会使得你的通知栏出现在第一位) 这里写图片描述 对应属性: Notification.PRIORITY_DEFAULT(优先级为0) Notification.PRIORITY_HIGH Notification.PRIORITY_LOW Notification.PRIORITY_MAX(优先级为2) Notification.PRIORITY_MIN(优先级为-2)

Notification.PRIORITY_MAX是优先级最高,Notification.PRIORITY_MIN优先级最低

7)方法:setOngoing(boolean ongoing) 方法解释:设置为ture,表示它为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)

PS:我们看到360手机卫士的通知栏一直固定在手机中,就是通过设置这个标记,使用该标记后你的通知栏无法被用户手动进行删除,只能通过代码进行删除,慎用

8)setProgress(int max, int progress,boolean indeterminate) 属性:max:进度条最大数值 、progress:当前进度、indeterminate:表示进度是否不确定,true为不确定,false为确定 功能:设置带进度条的通知,可以在下载中使用

注意:此方法在4.0及以后版本才有用,如果为早期版本:需要自定义通知布局,其中包含ProgressBar视图 使用:如果为确定的进度条:调用setProgress(max, progress, false)来设置通知,在更新进度的时候在此发起通知更新progress,并且在下载完成后要移除进度条,通过调用setProgress(0, 0, false)既可。 如果为不确定(持续活动)的进度条,这是在处理进度无法准确获知时显示活动正在持续,所以调用setProgress(0, 0, true) ,操作结束时,调用setProgress(0, 0, false)并更新通知以移除指示条

9)如何更新 Notification 更新通知很简单,只需要再次发送相同 ID 的通知即可,如果之前的通知还未被取消,则会直接更新该通知相关的属性;如果之前的通知已经被取消,则会重新创建一个新通知。 更新通知跟发送通知使用相同的方式。

private void refreshNotification() { //获取NotificationManager实例 NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //实例化NotificationCompat.Builde并设置相关属性 NotificationCompat.Builder builder = new NotificationCompat.Builder(this) //设置小图标 .setSmallIcon(R.mipmap.icon_fab_repair) //设置通知标题 .setContentTitle("最简单的Notification") //设置通知内容 .setContentText("只有小图标、标题、内容") //设置通知时间,默认为系统发出通知的时间,通常不用设置 //.setWhen(System.currentTimeMillis()); //通过builder.build()方法生成Notification对象,并发送通知,id=1 notifyManager.notify(1, builder.build()); }

10)如何取消 Notification? 取消通知有如下 5 种方式:

  1. 点击通知栏的清除按钮,会清除所有可清除的通知
  2. 设置了 setAutoCancel() 或 FLAG_AUTO_CANCEL 的通知,点击该通知时会清除它
  3. 通过 NotificationManager 调用 cancel(int id) 方法清除指定 ID 的通知
  4. 通过 NotificationManager 调用 cancel(String tag, int id) 方法清除指定 TAG 和 ID 的通知
  5. 通过 NotificationManager 调用 cancelAll() 方法清除所有该应用之前发送的通知 如果你是通过 NotificationManager.notify(String tag, int id, Notification notify) 方法创建的通知,那么只能通过 NotificationManager.cancel(String tag, int id) 方法才能清除对应的通知,调用NotificationManager.cancel(int id) 无效。

例子:

public class DemoActivity extends Activity implements View.OnClickListener {

//Notification.FLAG_FOREGROUND_SERVICE    //表示正在运行的服务
public static final String NOTIFICATION_TAG = "test";
public static final int DEFAULT_NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_simple_notification);
    findViewById(R.id.btn_remove_all_notification).setOnClickListener(this);
    findViewById(R.id.btn_send_notification).setOnClickListener(this);
    findViewById(R.id.btn_remove_notification).setOnClickListener(this);
    findViewById(R.id.btn_send_notification_with_tag).setOnClickListener(this);
    findViewById(R.id.btn_remove_notification_with_tag).setOnClickListener(this);
    findViewById(R.id.btn_send_ten_notification).setOnClickListener(this);
    findViewById(R.id.btn_send_flag_no_clear_notification).setOnClickListener(this);
    findViewById(R.id.btn_send_flag_ongoing_event_notification).setOnClickListener(this);
    findViewById(R.id.btn_send_flag_auto_cancecl_notification).setOnClickListener(this);
    mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn_remove_all_notification:
            //移除当前 Context 下所有 Notification,包括 FLAG_NO_CLEAR 和 FLAG_ONGOING_EVENT
            mNotificationManager.cancelAll();
            break;
        case R.id.btn_send_notification:
            //发送一个 Notification,此处 ID = 1
            sendNotification();
            break;
        case R.id.btn_remove_notification:
            //移除 ID = 1 的 Notification,注意:该方法只针对当前 Context。
            mNotificationManager.cancel(DEFAULT_NOTIFICATION_ID);
            break;
        case R.id.btn_send_notification_with_tag:
            //发送一个 ID = 1 并且 TAG = littlejie 的 Notification
            //注意:此处发送的通知与 sendNotification() 发送的通知并不冲突
            //因为此处的 Notification 带有 TAG
            sendNotificationWithTag();
            break;
        case R.id.btn_remove_notification_with_tag:
            //移除一个 ID = 1 并且 TAG = littlejie 的 Notification
            //注意:此处移除的通知与 NotificationManager.cancel(int id) 移除通知并不冲突
            //因为此处的 Notification 带有 TAG
            mNotificationManager.cancel(NOTIFICATION_TAG, DEFAULT_NOTIFICATION_ID);
            break;
        case R.id.btn_send_ten_notification:
            //连续发十条 Notification
            sendTenNotifications();
            break;
        case R.id.btn_send_flag_no_clear_notification:
            //发送 ID = 1, flag = FLAG_NO_CLEAR 的 Notification
            //下面两个 Notification 的 ID 都为 1,会发现 ID 相等的 Notification 会被最新的替换掉
            sendFlagNoClearNotification();
            break;
        case R.id.btn_send_flag_auto_cancecl_notification:
            sendFlagOngoingEventNotification();
            break;
        case R.id.btn_send_flag_ongoing_event_notification:
            sendFlagAutoCancelNotification();
            break;
    }
}
/**
 * 发送最简单的通知,该通知的ID = 1
 */
private void sendNotification() {
    //这里使用 NotificationCompat 而不是 Notification ,因为 Notification 需要 API 16 才能使用
    //NotificationCompat 存在于 V4 Support Library
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Send Notification")
            .setContentText("Hi,My id is 1");
    mNotificationManager.notify(DEFAULT_NOTIFICATION_ID, builder.build());
}
/**
 * 使用notify(String tag, int id, Notification notification)方法发送通知
 * 移除对应通知需使用 cancel(String tag, int id)
 */
private void sendNotificationWithTag() {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Send Notification With Tag")
            .setContentText("Hi,My id is 1,tag is " + NOTIFICATION_TAG);
    mNotificationManager.notify(NOTIFICATION_TAG, DEFAULT_NOTIFICATION_ID, builder.build());
}
/**
 * 循环发送十个通知
 */
private void sendTenNotifications() {
    for (int i = 0; i < 10; i++) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("Send Notification Batch")
                .setContentText("Hi,My id is " + i);
        mNotificationManager.notify(i, builder.build());
    }
}
/**
 * 设置FLAG_NO_CLEAR
 * 该 flag 表示该通知不能被状态栏的清除按钮给清除掉,也不能被手动清除,但能通过 cancel() 方法清除
 * Notification.flags属性可以通过 |= 运算叠加效果
 */
private void sendFlagNoClearNotification() {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Send Notification Use FLAG_NO_CLEAR")
            .setContentText("Hi,My id is 1,i can't be clear.");
    Notification notification = builder.build();
    //设置 Notification 的 flags = FLAG_NO_CLEAR
    //FLAG_NO_CLEAR 表示该通知不能被状态栏的清除按钮给清除掉,也不能被手动清除,但能通过 cancel() 方法清除
    //flags 可以通过 |= 运算叠加效果
    notification.flags |= Notification.FLAG_NO_CLEAR;
    mNotificationManager.notify(DEFAULT_NOTIFICATION_ID, notification);
}
/**
 * 设置FLAG_AUTO_CANCEL
 * 该 flag 表示用户单击通知后自动消失
 */
private void sendFlagAutoCancelNotification() {
    //设置一个Intent,不然点击通知不会自动消失
    Intent resultIntent = new Intent(this, MainActivity.class);
    PendingIntent resultPendingIntent = PendingIntent.getActivity(
            this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Send Notification Use FLAG_AUTO_CLEAR")
            .setContentText("Hi,My id is 1,i can be clear.")
            .setContentIntent(resultPendingIntent);
    Notification notification = builder.build();
    //设置 Notification 的 flags = FLAG_NO_CLEAR
    //FLAG_AUTO_CANCEL 表示该通知能被状态栏的清除按钮给清除掉
    //等价于 builder.setAutoCancel(true);
    notification.flags |= Notification.FLAG_AUTO_CANCEL;
    mNotificationManager.notify(DEFAULT_NOTIFICATION_ID, notification);
}
/**
 * 设置FLAG_ONGOING_EVENT
 * 该 flag 表示发起正在运行事件(活动中)
 */
private void sendFlagOngoingEventNotification() {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setContentTitle("Send Notification Use FLAG_ONGOING_EVENT")
            .setContentText("Hi,My id is 1,i can't be clear.");
    Notification notification = builder.build();
    //设置 Notification 的 flags = FLAG_NO_CLEAR
    //FLAG_ONGOING_EVENT 表示该通知通知放置在正在运行,不能被手动清除,但能通过 cancel() 方法清除
    //等价于 builder.setOngoing(true);
    notification.flags |= Notification.FLAG_ONGOING_EVENT;
    mNotificationManager.notify(DEFAULT_NOTIFICATION_ID, notification);
}    

}

给Notification设置PendingIntent PendingIntent可以通过setContentIntent(PendingIntent intent)这个方法进行设置

当我们点击通知栏时想跳转一个Activity或者开启一个service时,就可以通过设置PendingIntent达成

PendingIntent 是 Android 系统管理并持有的用于描述和获取原始数据的对象的标志(引用)。也就是说,即便创建该PendingIntent对象的进程被杀死了,这个PendingItent对象在其他进程中还是可用的。 日常使用中的短信、闹钟等都用到了 PendingIntent。

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)

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 。

自定义Notification Android系统允许使用RemoteViews来自定义通知。自定义普通视图通知高度限制为64dp,大视图通知高度限制为256dp。同时,建议自定义通知尽量简单,以提高兼容性。 自定义通知需要做如下操作:1、创建自定义通知布局2、使用RemoteViews定义通知组件,如图标、文字等3、调用setContent()将RemoteViews对象绑定到NotificationCompat.Builder4、同正常发送通知流程

注意: 避免为通知设置背景,因为兼容性原因,有些文字可能看不清。 关于自定义Notification兼容问题,请阅读我的另一篇博客 Android通知栏版本兼容解决方案

例子:

RemoteViews notifactionView = new RemoteViews(mContext.getPackageName(), R.layout.cl_screen_notification); mBuilder.setContent(notifactionView); notifactionView.setOnClickPendingIntent(R.id.cl_screen_notification, pendingIntent); Bitmap pluginIcon = drawableToBitmap(pluginDrawable); LogUtils.d("myl", "获得icon" + pluginIcon); notifactionView.setImageViewBitmap(R.id.cl_plugin_icon, pluginIcon); notifactionView.setTextViewText(R.id.dl_plugin_msg, pluginContent); Notification notification = mBuilder.build(); int id = 199; LogUtils.d(TAG, "创建通知"); mNotificationManager.notify(id, notification);

Notification的各种样式

  1. BigText样式(Android 4.1以上) a) 最大高度一般为256dp b) 不是最新的通知时默认为折叠状态 c) 不设置SummaryText的话,展开后最下面一行的内容会消失

例子:

private void showBigViewText() { NotificationCompat.BigTextStyle textStyle = new BigTextStyle(); textStyle .setBigContentTitle("BigContentTitle") .setSummaryText("SummaryText") .bigText( "I am Big Text"); Notification notification = new NotificationCompat.Builder(context) .setLargeIcon(icon).setSmallIcon(R.drawable.ic_launcher) .setTicker("showBigView_Text").setContentInfo("contentInfo") .setContentTitle("ContentTitle").setContentText("ContentText") .setStyle(textStyle) .setAutoCancel(true).setDefaults(Notification.DEFAULT_ALL) .build(); manager.notify(NOTIFICATION_ID_2, notification); }

  1. Inbox样式 a) 高度一般为256dp b) 不是最新的通知时默认为折叠状态 c) 不设置SummaryText的话,展开后最下面一行的内容会消失 例子:

private void showBigViewInbox() { NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle(); inboxStyle.setBigContentTitle("BigContentTitle").setSummaryText( "SummaryText"); for (int i = 0; i < 5; i++) inboxStyle.addLine("news:" + i); Notification notification = new NotificationCompat.Builder(context) .setLargeIcon(icon).setSmallIcon(R.drawable.ic_launcher) .setTicker("showBigView_Inbox").setContentInfo("contentInfo") .setContentTitle("ContentTitle").setContentText("ContentText") .setStyle(inboxStyle) .setAutoCancel(true).setDefaults(Notification.DEFAULT_ALL) .build(); manager.notify(NOTIFICATION_ID_4, notification); }

  1. BigPicture样式 a) 高度一般为256dp b) 不是最新的通知时为默认折叠状态 c) 不设置SummaryText的话,展开后第二行字的内容会消失

例子:

private void showBigViewPic() { NotificationCompat.BigPictureStyle pictureStyle = new BigPictureStyle(); pictureStyle.setBigContentTitle("BigContentTitle") .setSummaryText("SummaryText").bigPicture(icon); Notification notification = new NotificationCompat.Builder(context) .setLargeIcon(icon).setSmallIcon(R.drawable.ic_launcher) .setTicker("showBigView_Pic").setContentInfo("contentInfo") .setContentTitle("ContentTitle").setContentText("ContentText") .setStyle(pictureStyle) .setAutoCancel(true).setDefaults(Notification.DEFAULT_ALL) .build(); manager.notify(NOTIFICATION_ID_3, notification); }

  1. 折叠式Notification 折叠式Notification是一种自定义视图的Notification,用来显示长文本和一些自定义的布局的场景。它有两种状态,一种是普通状态下的视图,一种是展开状态下的视图。和普通Notification不同的是,我们需要自定义的视图,而这个视图显示的进程和我们创建视图的进程不再一个进程,所以我们需要使用RemoteViews,首先要使用RemoteViews来创建我们的自定义视图:

//用RemoteViews来创建自定义Notification视图 RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.view);

视图的布局文件:

<TextView
    android:id="@+id/tv_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="30dp"
    android:layout_marginLeft="150dp"
    android:text="展开后的视图"
    android:textColor="@color/colorPrimaryDark"/>

把自定义视图赋值给Notification展开时的视图

//指定展开时的视图 notification.bigContentView = remoteViews;

也可以把自定义视图赋值给Notification普通状态时的视图

//指定普通状态时的视图 notification.contentView = remoteViews;

折叠式Notification完整代码:

Notification.Builder builder = new Notification.Builder(this); Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://blog.csdn.net/itachi85/")); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0); builder.setContentIntent(pendingIntent); builder.setSmallIcon(R.drawable.foldleft); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.lanucher)); builder.setAutoCancel(true); builder.setContentTitle("折叠式通知"); //用RemoteViews来创建自定义Notification视图 RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.view_fold); Notification notification = builder.build(); //指定展开时的视图 notification.bigContentView = remoteViews; notificationManager.notify(1, notification);

  1. 悬挂式Notification (5.0新增) 悬挂式Notification是android5.0新增加的方式,悬挂式Notification不需要下拉通知栏就直接显示出来悬挂在屏幕上方并且焦点不变仍在用户操作的界面因此不会打断用户的操作,过几秒就会自动消失。他需要调用setFullScreenIntent来将Notification变为悬挂式Notification

//如果描述的PendingIntent已经存在,则在产生新的Intent之前会先取消掉当前的 PendingIntent hangPendingIntent = PendingIntent.getActivity(this, 0, hangIntent, PendingIntent.FLAG_CANCEL_CURRENT); builder.setFullScreenIntent(hangPendingIntent, true);

悬挂式Notification完整代码:

Notification.Builder builder = new Notification.Builder(this); Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://blog.csdn.net/itachi85/")); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0); builder.setContentIntent(pendingIntent); builder.setSmallIcon(R.drawable.foldleft); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.lanucher)); builder.setAutoCancel(true); builder.setContentTitle("悬挂式通知"); //设置点击跳转 Intent hangIntent = new Intent(); hangIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); hangIntent.setClass(this, MyNotificationActivity.class); //如果描述的PendingIntent已经存在,则在产生新的Intent之前会先取消掉当前的 PendingIntent hangPendingIntent = PendingIntent.getActivity(this, 0, hangIntent, PendingIntent.FLAG_CANCEL_CURRENT); builder.setFullScreenIntent(hangPendingIntent, true); notificationManager.notify(2, builder.build());

这里写图片描述

  1. 锁屏通知 Android 5.0(API level 21)开始,通知可以显示在锁屏上。用户可以通过设置选择是否允许敏感的通知内容显示在安全的锁屏上。你的应用可以通过setVisibility()控制通知的显示等级:

VISIBILITY_PRIVATE : 显示基本信息,如通知的图标,但隐藏通知的全部内容 VISIBILITY_PUBLIC : 显示通知的全部内容 VISIBILITY_SECRET : 不显示任何内容,包括图标

builder.setVisibility(Notification.VISIBILITY_PUBLIC);

Android通知栏微技巧,8.0系统中通知栏的适配

大家好,今天我们继续来学习Android 8.0系统的适配。

之前我们已经讲到了,Android 8.0系统最主要需要进行适配的地方有两处:应用图标和通知栏。在上一篇文章当中,我们学习了Android 8.0系统应用图标的适配,还没有看过这篇文章的朋友可以先去阅读 Android应用图标微技巧,8.0系统中应用图标的适配 。

那么本篇文章,我们自然要将重点放在通知栏上面了,学习一下Android 8.0系统的通知栏适配。

其实在8.0系统之前,还有一次通知栏变动比较大的版本,就是5.0系统。关于5.0系统需要对通知栏进行适配的内容,我也整理了一篇文章,感兴趣的朋友可以去阅读 Android通知栏微技巧,那些你所没关注过的小细节 。

那么下面我们就开始进入本篇文章的正题。

为什么要进行通知栏适配? 不得不说,通知栏真是一个让人又爱又恨的东西。

通知栏是Android系统原创的一个功能,虽说乔布斯一直认为Android系统是彻彻底底抄袭iOS的一个产品,但是通知栏确实是Android系统原创的,反而苹果在iOS 5之后也加入了类似的通知栏功能。

通知栏的设计确实非常巧妙,它默认情况下不占用任何空间,只有当用户需要的时候用手指在状态栏上向下滑动,通知栏的内容才会显示出来,这在智能手机发展的初期极大地解决了手机屏幕过小,内容展示区域不足的问题。

可是随着智能手机发展的逐渐成熟,通知栏却变得越来越不讨人喜欢了。各个App都希望能抢占通知栏的空间,来尽可能地宣传和推广自己的产品。现在经常是早上一觉醒来拿起手机一看,通知栏上全是各种APP的推送,不胜其烦。

我个人虽然是Android应用开发者,但同时也是Android手机的资深用户。我已经使用了8年的Android手机,目前我对于通知栏的这种垃圾推送是零容忍的。现在每当我安装一个新的App时,我都会先到设置里面去找一找有没有推送开关,如果有的话我会第一时间把它关掉。而如果一个App经常给我推送垃圾信息却又无法关闭时,我会直接将它的通知总开关给关掉,如果还不是什么重要的App的话,那么我可能就直接将它卸载掉了。

为什么一个很好的通知栏功能现在却变得这么遭用户讨厌?很大一部分原因都是因为开发者没有节制地使用导致的。就好像App保活一样,直到今天还是不断有人问我该如何保活App,试想如何每个人都能保活自己的App,那么最终受害的人是谁?还不是使用Android手机的用户。大家的手机只会越来越卡,最后只想把手机丢掉,变成iPhone用户了。也是因为开发者没节制地使用,Android现在的每个版本都会不断收缩后台权限。

回到通知栏上也是一样,每个开发者都只想着尽可能地去宣传自己的App,最后用户的手机就乱得跟鸡窝一样了。但是通知栏又还是有用处的,比如我们收到微信、短信等消息的时候,确实需要通知栏给我们提醒。因此分析下来,通知栏目前最大的问题就是,无法让用户对感兴趣和不感兴趣的消息进行区分。就比如说,我希望淘宝向我推送卖家发货和物流的相关消息,但是我不想收到那些打折促销或者是让我去买衣服的这类消息。那么就目前来说,是没有办法对这些消息做区分的,我要么同意接受所有消息,要么就屏蔽所有消息,这是当前通知栏的痛点。

那么在Android 8.0系统中,Google也是从这个痛点开始下手的。

8.0系统的通知栏适配 从Android 8.0系统开始,Google引入了通知渠道这个概念。

什么是通知渠道呢?顾名思义,就是每条通知都要属于一个对应的渠道。每个App都可以自由地创建当前App拥有哪些通知渠道,但是这些通知渠道的控制权都是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动、或者是否要关闭这个渠道的通知。

拥有了这些控制权之后,用户就再也不用害怕那些垃圾推送消息的打扰了,因为用户可以自主地选择自己关心哪些通知、不关心哪些通知。举个具体的例子,我希望可以即时收到支付宝的收款信息,因为我不想错过任何一笔收益,但是我又不想收到支付宝给我推荐的周围美食,因为我没钱只吃得起公司食堂。这种情况,支付宝就可以创建两种通知渠道,一个收支,一个推荐,而我作为用户对推荐类的通知不感兴趣,那么我就可以直接将推荐通知渠道关闭,这样既不影响我关心的通知,又不会让那些我不关心的通知来打扰我了。

对于每个App来说,通知渠道的划分是非常需要仔细考究的,因为通知渠道一旦创建之后就不能再修改了,因此开发者需要仔细分析自己的App一共有哪些类型的通知,然后再去创建相应的通知渠道。这里我们来参考一下Twitter的通知渠道划分:

可以看到,Twitter就是根据自己的通知类型,对通知渠道进行了非常详细的划分,这样用户的自主选择性就比较高了,也就大大降低了用户不堪其垃圾通知的骚扰而将App卸载的概率。

我一定要适配吗? Google这次对于8.0系统通知渠道的推广态度还是比较强硬的。

首先,如果你升级了appcompat库,那么所有使用appcompat库来构建通知的地方全部都会进行废弃方法提示,如下所示:

上图告诉我们,此方法已废弃,需要使用带有通知渠道的方法才行。

当然,Google也并没有完全做绝,即使方法标为了废弃,但还是可以正常使用的。可是如果你将项目中的targetSdkVersion指定到了26或者更高,那么Android系统就会认为你的App已经做好了8.0系统的适配工作,当然包括了通知栏的适配。这个时候如果还不使用通知渠道的话,那么你的App的通知将完全无法弹出。因此这里给大家的建议就是,一定要适配。

好了,前面向大家介绍了这么多的背景知识,那么现在开始我们就正式进入正题,来学习一下如何进行8.0系统中通知栏的适配。

创建通知渠道 首先我们使用Android Studio来新建一个项目,就叫它NotificationTest吧。

创建好项目之后,打开app/build.gradle文件检查一下,确保targetSdkVersion已经指定到了26或者更高,如下所示:

apply plugin: 'com.android.application'

android { compileSdkVersion 26 defaultConfig { applicationId "com.example.notificationtest" minSdkVersion 15 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } }

可以看到,这里我在创建新项目的时候默认targetSdkVersion就是26,如果你是低于26的话,说明你的Android SDK有些老了,最好还是更新一下。当然如果你懒得更新也没关系,手动把它改成26就可以了。

接下来修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        String channelId = "chat";
        String channelName = "聊天消息";
        int importance = NotificationManager.IMPORTANCE_HIGH;
        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);
    NotificationManager notificationManager = (NotificationManager) getSystemService(
            NOTIFICATION_SERVICE);
    notificationManager.createNotificationChannel(channel);
}

}

代码不长,我来简单解释下。这里我们在MainActivity中创建了两个通知渠道,首先要确保的是当前手机的系统版本必须是Android 8.0系统或者更高,因为低版本的手机系统并没有通知渠道这个功能,不做系统版本检查的话会在低版本手机上造成崩溃。

创建一个通知渠道的方式非常简单,这里我封装了一个createNotificationChannel()方法,里面的逻辑相信大家都看得懂。需要注意的是,创建一个通知渠道至少需要渠道ID、渠道名称以及重要等级这三个参数,其中渠道ID可以随便定义,只要保证全局唯一性就可以。渠道名称是给用户看的,需要能够表达清楚这个渠道的用途。重要等级的不同则会决定通知的不同行为,当然这里只是初始状态下的重要等级,用户可以随时手动更改某个渠道的重要等级,App是无法干预的。

上述代码我是模拟了这样一个场景。想象一下我们正在开发一个类似于微信的App,其中App通知主要可以分为两类,一类是我和别人的聊天消息,这类消息非常重要,因此重要等级我设为了IMPORTANCE_HIGH。另一类是公众号的订阅消息,这类消息不是那么重要,因此重要等级我设为了IMPORTANCE_DEFAULT。除此之外,重要等级还可以设置为IMPORTANCE_LOW、IMPORTANCE_MIN,分别对应了更低的通知重要程度。

现在就可以运行一下代码了,运行成功之后我们关闭App,进入到设置 -> 应用 -> 通知当中,查看NotificationTest这个App的通知界面,如下图所示:

刚才我们创建的两个通知渠道这里已经显示出来了。可以看到,由于这两个通知渠道的重要等级不同,通知的行为也是不同的,聊天消息可以发出提示音并在屏幕上弹出通知,而订阅消息只能发出提示音。

当然,用户还可以点击进去对该通知渠道进行任意的修改,比如降低聊天消息的重要等级,甚至是可以完全关闭该渠道的通知。

至于创建通知渠道的这部分代码,你可以写在MainActivity中,也可以写在Application中,实际上可以写在程序的任何位置,只需要保证在通知弹出之前调用就可以了。并且创建通知渠道的代码只在第一次执行的时候才会创建,以后每次执行创建代码系统会检测到该通知渠道已经存在了,因此不会重复创建,也并不会影响任何效率。

让通知显示出来 触发通知的代码和之前版本基本是没有任何区别的,只是在构建通知对象的时候,需要多传入一个通知渠道ID,表示这条通知是属于哪个渠道的。

那么下面我们就来让通知显示出来。

首先修改activity_main.xml中的代码,如下所示:

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="发送聊天消息"
    android:onClick="sendChatMsg"
    />
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="发送订阅消息"
    android:onClick="sendSubscribeMsg"
    />

这里我们在布局文件中加入了两个按钮,很显然,一个是用于触发聊天消息渠道通知的,一个是用于触发订阅消息渠道通知的。

接下来修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

...
public void sendChatMsg(View view) {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Notification notification = new NotificationCompat.Builder(this, "chat")
            .setContentTitle("收到一条聊天消息")
            .setContentText("今天中午吃什么?")
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.drawable.icon)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
            .setAutoCancel(true)
            .build();
    manager.notify(1, notification);
}
public void sendSubscribeMsg(View view) {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Notification notification = new NotificationCompat.Builder(this, "subscribe")
            .setContentTitle("收到一条订阅消息")
            .setContentText("地铁沿线30万商铺抢购中!")
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.drawable.icon)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon))
            .setAutoCancel(true)
            .build();
    manager.notify(2, notification);
}

}

这里我们分别在sendChatMsg()和sendSubscribeMsg()方法中触发了两条通知,创建通知的代码就不再多做解释了,和传统创建通知的方法没什么两样,只是在NotificationCompat.Builder中需要多传入一个通知渠道ID,那么这里我们分别传入了chat和subscribe这两个刚刚创建的渠道ID。

现在重新运行一下代码,并点击发送聊天消息按钮,效果如下图所示:

由于这是一条重要等级高的通知,因此会使用这种屏幕弹窗的方式来通知用户有消息到来。然后我们可以下拉展开通知栏,这里也能查看到通知的详细信息:

用户可以通过快速向左或者向右滑动来关闭这条通知。

接下来点击发送订阅消息按钮,你会发现现在屏幕上不会弹出一条通知提醒了,只会在状态栏上显示一个小小的通知图标:

因为订阅消息通知的重要等级是默认级别,这就是默认级别通知的展示形式。当然我们还是可以下拉展开通知栏,查看通知的详细信息:

不过上面演示的都是通知栏的传统功能,接下来我们看一看Android 8.0系统中通知栏特有的功能。

刚才提到了,快速向左或者向右滑动可以关闭一条通知,但如果你缓慢地向左或者向右滑动,就会看到这样两个按钮:

其中,左边那个时钟图标的按钮可以让通知延迟显示。比方说这是一条比较重要的通知,但是我暂时没时间看,也不想让它一直显示在状态栏里打扰我,我就可以让它延迟一段后时间再显示,这样我就暂时能够先将精力放在专注的事情上,等过会有时间了这条通知会再次显示出来,我不会错过任何信息。如下所示:

而右边那个设置图标的按钮就可以用来对通知渠道进行屏蔽和配置了,用户对每一个App的每一个通知渠道都有绝对的控制权,可以根据自身的喜好来进行配置和修改。如下所示:

比如说我觉得订阅消息老是向我推荐广告,实在是太烦了,我就可以将订阅消息的通知渠道关闭掉。这样我以后就不会再收到这个通知渠道下的任何消息,而聊天消息却不会受到影响,这就是8.0系统通知渠道最大的特色。

另外,点击上图中的所有类别就可以进入到当前应用程序通知的完整设置界面。

管理通知渠道 在前面的内容中我们已经了解到,通知渠道一旦创建之后就不能再通过代码修改了。既然不能修改的话那还怎么管理呢?为此,Android赋予了开发者读取通知渠道配置的权限,如果我们的某个功能是必须按照指定要求来配置通知渠道才能使用的,那么就可以提示用户去手动更改通知渠道配置。

只讲概念总是不容易理解,我们还是通过具体的例子来学习一下。想一想我们开发的是一个类似于微信的App,聊天消息是至关重要的,如果用户不小心将聊天消息的通知渠道给关闭了,那岂不是所有重要的信息全部都丢了?为此我们一定要保证用户打开了聊天消息的通知渠道才行。

修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

...
public void sendChatMsg(View view) {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = manager.getNotificationChannel("chat");
        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);
            Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show();
        }
    }
    Notification notification = new NotificationCompat.Builder(this, "chat")
            ...
            .build();
    manager.notify(1, notification);
}
...

}

这里我们对sendChatMsg()方法进行了修改,通过getNotificationChannel()方法获取到了NotificationChannel对象,然后就可以读取该通知渠道下的所有配置了。这里我们判断如果通知渠道的importance等于IMPORTANCE_NONE,就说明用户将该渠道的通知给关闭了,这时会跳转到通知的设置界面提醒用户手动打开。

现在重新运行一下程序,效果如下图所示:

可以看到,当我们将聊天消息的通知渠道关闭后,下次再次发送聊天消息将会直接跳转到通知设置界面,提醒用户手动将通知打开。

除了以上管理通知渠道的方式之外,Android 8.0还赋予了我们删除通知渠道的功能,只需使用如下代码即可删除:

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.deleteNotificationChannel(channelId); 1 2 但是这个功能非常不建议大家使用。因为Google为了防止应用程序随意地创建垃圾通知渠道,会在通知设置界面显示所有被删除的通知渠道数量,如下图所示:

这样是非常不美观的,所以对于开发者来说最好的做法就是仔细规划好通知渠道,而不要轻易地使用删除功能。

显示未读角标 前面我们提到过,苹果是从iOS 5开始才引入了通知栏功能,那么在iOS 5之前,iPhone都是怎么进行消息通知的呢?使用的就是未读角标功能,效果如下所示:

实际上Android系统之前是从未提供过这种类似于iOS的角标功能的,但是由于很多国产手机厂商都喜欢跟风iOS,因此各种国产手机ROM都纷纷推出了自己的角标功能。

可是国产手机厂商虽然可以订制ROM,但是却没有制定API的能力,因此长期以来都没有一个标准的API来实现角标功能,很多都是要通过向系统发送广播来实现的,而各个手机厂商的广播标准又不一致,经常导致代码变得极其混杂。

值得高兴的是,从8.0系统开始,Google制定了Android系统上的角标规范,也提供了标准的API,长期让开发者头疼的这个问题现在终于可以得到解决了。

那么下面我们就来学习一下如何在Android系统上实现未读角标的效果。

修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

...
@TargetApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName, int importance) {
    NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
    channel.setShowBadge(true);
    NotificationManager notificationManager = (NotificationManager) getSystemService(
            NOTIFICATION_SERVICE);
    notificationManager.createNotificationChannel(channel);
}
public void sendSubscribeMsg(View view) {
    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    Notification notification = new NotificationCompat.Builder(this, "subscribe")
            ...
            .setNumber(2)
            .build();
    manager.notify(2, notification);
}

}

可以看到,这里我们主要修改了两个地方。第一是在创建通知渠道的时候,调用了NotificationChannel的setShowBadge(true)方法,表示允许这个渠道下的通知显示角标。第二是在创建通知的时候,调用了setNumber()方法,并传入未读消息的数量。

现在重新运行一下程序,并点击发送订阅消息按钮,然后在Launcher中找到NotificationTest这个应用程序,如下图所示:

可以看到,在图标的右上角有个绿色的角标,说明我们编写的角标功能已经生效了。

需要注意的是,即使我们不调用setShowBadge(true)方法,Android系统默认也是会显示角标的,但是如果你想禁用角标功能,那么记得一定要调用setShowBadge(false)方法。

但是未读数量怎么没有显示出来呢?这个功能还需要我们对着图标进行长按才行,效果如下图所示:

这样就能看到通知的未读数量是2了。

可能有些朋友习惯了iOS上的那种未读角标,觉得Android上这种还要长按的方式很麻烦。这个没有办法,因为这毕竟是Android原生系统,Google没有办法像国内手机厂商那样可以肆无忌惮地模仿iOS,要不然可能会吃官司的。但是我相信国内手机厂商肯定会将这部分功能进行定制,风格应该会类似于iOS。不过这都不重要,对于我们开发者来说,最好的福音就是有了统一的API标准,不管国内手机厂商以后怎么定制ROM,都会按照这个API的标准来定制,我们只需要使用这个API来进行编程就可以了。

好的,关于Android 8.0系统适配的上下两篇文章到这里就结束了,感谢大家阅读。

文章中的示例源码点击 这里 下载。