Notification 概述
Notification,是一种具有全局效果的通知,可以在系统的通知栏中显示。当 APP 向系统发出通知时,它将先以图标的形式显示在通知栏中。用户可以下拉通知栏查看通知的详细信息。通知栏和抽屉式通知栏均是由系统控制,用户可以随时查看。下面两张图均是来自 Google 官方文档。
通知的目的是告知用户 App 事件。在平时的使用中,通知主要有以下几个作用:
其中,前两点可以归结为与用户交互,第三点是实时的任务提醒,但不可否认的是,第三点也会与用户交互。
Notification 作为 Android 重要的用户界面组成部分,它有自己的设计指南。在 Android 5.0(Api level 21) 中引入的 Material Design 尤为重要。关于 Notification 的设计指南请参考 Notification Pattern
Notification 的概述就这么多,接下去就开始讲 Notification 的基本使用,中间会穿插 Notification 的基本 UI 、各个版本的区别、常见的通知效果以及自己在学习过程中踩到的坑。
Notification 的基本操作主要有创建、更新、取消这三种。一个 Notification 的必要属性有三项,如果不设置则在运行时会抛出异常:
除了以上三项,其它均为可选项。虽然如此,但还是应该给 Notification 设置一个 Action ,这样就可以直接跳转到 App 的某个 Activity 、启动一个 Service 或者发送一个 Broadcast。否则,Notification 仅仅只能起到通知的效果,而不能与用户交互。
当系统接收到通知时,可以通过震动、响铃、呼吸灯等多种方式进行提醒。
Notification 的创建主要涉及到 Notification.Builder 、 Notification 、 NotificationManager 。
获取 NotificationManager 对象:
NotificationManager mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
前面讲到,Notification 有三个必要属性。下面,我们就来创建一个简单的 Notification 。主要有以下三步:
private void sendNotification() { //获取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()); }
以上代码是对 Android 3.0 及之后的版本而言(包括使用 Support Library),对于 Android 3.0 之前的版本,主要使用 new Notification() 方法来创建 Notification 对象,本文不对此方式做任何讲解,代码如下:
NotificationManager mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); PendingIntent contentIntent = PendingIntent.getActivity( this, 0, new Intent(this, ResultActivity.class), 0); Notification notification = new Notification(icon, tickerText, when); notification.setLatestEventInfo(this, title, content, contentIntent); mNotifyMgr.notify(NOTIFICATIONS_ID, notification);
补充:
Android Support v4:这个包是为了照顾1.6及更高版本而设计的,这个包是使用最广泛的。
Android Support v7:这个包是为了考虑照顾2.1及以上版本而设计的,但不包含更低,故如果不考虑1.6,我们可以采用再加上这个包,另外注意,v7是要依赖v4这个包的,即,两个得同时被包含。
Android Support v13:这个包的设计是为了android 3.2及更高版本的,一般我们都不常用,平板开发中能用到。
在前一章节 创建 Notification 中发送的通知并不具备与用户交互的能力,这是因为我们并没有给 Notification 设置 Action 。在这一节,我们就来讲讲如何给 Notification 设置 Action 。这里,我们来实现一个点击 Notification 跳转到 MainActivity 的效果。代码如下:
/** * 发送一个点击跳转到MainActivity的消息 */ private void sendSimplestNotificationWithAction() { //获取PendingIntent Intent mainIntent = new Intent(this, MainActivity.class); PendingIntent mainPendingIntent = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT); //创建 Notification.Builder 对象 NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) //点击通知后自动清除 .setAutoCancel(true) .setContentTitle("我是带Action的Notification") .setContentText("点我会打开MainActivity") .setContentIntent(mainPendingIntent); //发送通知 mNotifyManager.notify(3, builder.build()); }
相比发送最简单的通知,发送具有 Action 的通知多了创建 Intent 、 PendingIntent 和 setContentIntent() 这几步。
不难看出, PendingIntent 才是重点,那么, PendingIntent 是什么呢?
如果您了解 PendingIntent ,请直接跳过本节。
PendingIntent 是一种特殊的 Intent ,字面意思可以解释为延迟的 Intent ,用于在某个事件结束后执行特定的 Action 。从上面带 Action 的通知也能验证这一点,当用户点击通知时,才会执行。
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 。
更新通知很简单,只需要再次发送相同 ID 的通知即可,如果之前的通知还未被取消,则会直接更新该通知相关的属性;如果之前的通知已经被取消,则会重新创建一个新通知。
更新通知跟发送通知使用相同的方式。详见上节:创建 Notification
取消通知有如下 5 种方式:
如果你是通过 NotificationManager.notify(String tag, int id, Notification notify) 方法创建的通知,那么只能通过 NotificationManager.cancel(String tag, int id) 方法才能清除对应的通知,调用NotificationManager.cancel(int id) 无效。
关于 Notification 的基本操作代码如下,布局文件代码这就不贴了。我是 Demo 传送门:
/** * 为了方便,大部分通知都没设置对应的Action,即PendingIntent * 除了sendFlagAutoCancelNotification()方法 */ public class SimpleNotificationActivity extends Activity implements View.OnClickListener { //Notification.FLAG_FOREGROUND_SERVICE //表示正在运行的服务 public static final String NOTIFICATION_TAG = "littlejie"; 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 的创建、更新和取消,以及给 Notification 设置 Action 等基本操作。那么,我怎么给 Notification 设置诸如震动、铃声、呼吸灯等效果呢?别急,接下来马上就会告诉你怎么给 Notification 添加效果。
Notification 有震动、响铃、呼吸灯三种响铃效果,可以通过 setDefaults(int defualts)
方法来设置。 Default 属性有以下四种,一旦设置了 Default 效果,自定义的效果就会失效。楼主在这里踩了坑,愣是调了半天没找到为什么自定义效果会消失,忘大家慎之。
//设置系统默认提醒效果,一旦设置默认提醒效果,则自定义的提醒效果会全部失效。具体可看源码 //添加默认震动效果,需要申请震动权限 // 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
Notification 通知效果的设置方式及注意事项全部在代码中,核心代码如下:
/** * 最普通的通知效果 */ private void showNotifyOnlyText() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(mLargeIcon) .setContentTitle("我是只有文字效果的通知") .setContentText("我没有铃声、震动、呼吸灯,但我就是一个通知"); mManager.notify(1, builder.build()); } /** * 展示有自定义铃声效果的通知 * 补充:使用系统自带的铃声效果: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()); } /** * 展示有震动效果的通知,需要在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()); } /** * 显示带有呼吸灯效果的通知,但是不知道为什么,自己这里测试没成功 */ 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); } /** * 显示带有默认铃声、震动、呼吸灯效果的通知 * 如需实现自定义效果,请参考前面三个例子 */ 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()); } /** * 通知无限循环,直到用户取消或者打开通知栏(其实触摸就可以了),效果与FLAG_ONLY_ALERT_ONCE相反 * 注:这里没有给Notification设置PendingIntent,也就是说该通知无法响应,所以只能手动取消 */ private void showInsistentNotify() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("我是一个死循环,除非你取消或者响应") .setContentText("啦啦啦~") .setDefaults(Notification.DEFAULT_ALL); Notification notify = builder.build(); notify.flags |= Notification.FLAG_INSISTENT; mManager.notify(6, notify); } /** * 通知只执行一次,与默认的效果一样 */ private void showAlertOnceNotify() { NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("仔细看,我就执行一遍") .setContentText("好了,已经一遍了~") .setDefaults(Notification.DEFAULT_ALL); Notification notify = builder.build(); notify.flags |= Notification.FLAG_ONLY_ALERT_ONCE; mManager.notify(7, notify); } /** * 清除所有通知 */ private void clearNotify() { mManager.cancelAll(); }
至此, Notification 的基本操作都已经讲完了,发送一个带有自定义效果的简单通知已经不在话下。接下去,我们会讲 Notification 的一些高级操作。快上车,没时间解释了~