1.概述
当应用程序在后台运行,希望向用户发出一些提示学习,就需要借助Notification(通知)来实现。在发出一条通知后,手机最上方的状态栏会显示一个通知的图标,下拉状态栏后就可以看到通知的详细内容。
1.1 视图分类
Notification有两种视觉风格,一种是标准视图(Normal View),另外一种是大视图(Big view)。标准视图在Android中各版本是通用的,但是对于大视图而言,仅支持Android4.1+的版本。
1.2 标准视图
从官方文档了解到,一个标准视图显示的大小保持在64dp高。如下图所示:
其属性描述如下:
1代表: 通知标题
2代表: 大图标
3代表: 通知内容
4代表: 通知消息
5代表:小图标
6代表: 通知时间,一般为系统时间,也可以使用setWhen()设置。
1.3 大视图
对于大视图(Big View)而言,它的细节区域只能显示256dp高度的内容,并且只对Android4.1+之后的设备才支持,它比标准视图不一样的地方,均需要使用setStyle()方法设定,它大致的效果如下:
Android为我们提供了三个实现类,用于显示不同的场景。分别是:
Notification.BigPictureStyle, 在细节部分显示一个256dp高度的位图。
Notification.BigTextStyle,在细节部分显示一个大的文本块。
Notification.InboxStyle,在细节部分显示一段行文本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 notification = builder.build();
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
--------------------------------
2.基本用法
通知可以在活动里面创建,也可以在广播接收器里面创建,也可以在服务里面创建。下面介绍创建通知的步骤:
2.1 创建一个NotificationManager
创建一个NotificationManager来对通知进行管理。通过调用Context.getSystemService(String s)方法获取到NotificationManager实例对象,字符串s参数用于确定获取系统的哪个服务,这里传入Context.NOTIFICATION_SERVICE即可,如下所示:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
其中,Context.NOTIFICATION_SERVICE的值为:notification
public abstract class Context { ...... public static final String NOTIFICATION_SERVICE = "notification"; }
2.2 使用Builder构造器来创建Notification对象
在这里需要注意的是,为了解决API不稳定性问题和新老版本的兼容问题,使用support-v4提供的NotificationCompat类的Builder构造器来创建Notification对象,可以保证程序在所有的版本上都能正常工作。同时,Android8.0开始,废弃了Builder(@NonNull Context context)方法,改用public Builder(@NonNull Context context, @NonNull String channelId),如下所示:
//创建Notification,传入Context和channelId Notification notification = new NotificationCompat.Builder(this, "chat") .setAutoCancel(true) .setContentTitle("收到聊天消息") .setContentText("今天晚上吃什么") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setContentIntent(pendingIntent) //在build()方法之前还可以添加其他方法 .build();
Builder构造器其中一些方法说明如下:
setAutoCancel(boolean boolean)设置点击通知后自动清除通知setContent(RemoteView view)设置自定义通知setContentTitle(String string)设置通知的标题内容setContentText(String string)设置通知的正文内容setContentIntent(PendingIntent intent)设置点击通知后的跳转意图setWhen(long when)设置通知被创建的时间setSmallIcon(int icon)
设置通知的小图标
注意:只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上
setLargeIcon(Bitmap icon)
设置通知的大图标
下拉系统状态栏时就能看见
setPriority(int pri)设置通知的重要程度setStyle(Style style)
设置通知的样式
比如设置长文字、大图片等等
setVisibility(int defaults)设置默认setLight(int argb, int onMs, int offMs)设置呼吸闪烁效果setSound(Uri sound)设置通知音效setVibrate(long[] pattern)
设置震动效果,数组包含手机静止时长和震动时长
下标0代表手机静止时长
下标1代表手机整的时长
下标2代表手机静止时长
下标3,4,5.......以此类推
还需要在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.VIBRATE"/>
setColor(int argb)设置通知栏颜色setCategory(String category)设置通知类别setFullScreenIntent(PendingIntent intent, boolean b)设置弹窗显示
其中setPriority(int pri)方法参数一共有5个常量值可选,调用NotificationCompat的常量值,如下所示:
public class NotificationCompat { ....... //默认的重要程度,和不设置效果是一样的 public static final int PRIORITY_DEFAULT = 0; //最低的重要程度,系统可能只会在特定的场合显示这条通知 public static final int PRIORITY_MIN = -2; //较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序 public static final int PRIORITY_LOW = -1; //较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序 public static final int PRIORITY_HIGH = 1; //最高的重要程度,表示这类通知消息必须让用户看到,甚至做出响应 public static final int PRIORITY_MAX = 2; }
注意:当设置最高重要程度后,其显示效果和QQ发送好友消息一样,如果正在其他APP内,消息会显示在屏幕上让用户看见
2.3 调用NotificationManager的notify(int id, Notification notification)让通知显示
notify()方法接收两个参数,其中id表示每个通知所指定的id,要不一样。代码如下:
notificationManager.notify(1, notification);
使用此方法前,需要将NotificationChannel(通知渠道创建出来)
2.4 让通知从状态栏消失方法
setAutoCancel(true),另外一种就是通过动态代码方式,NotificationManager.cancel(int id),如下:
//传入对应通知的id notificationManager.cancel(1);
3.NotificationChannel(通知渠道)
3.1 概述
从Android 8.0系统开始,Google引入了通知渠道这个概念。
每条通知都要属于一个对应的渠道。每个App都可以自由地创建当前App拥有哪些通知渠道,但是这些通知渠道的控制权都是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动、或者是否要关闭这个渠道的通知。
即NotificationChannel
其实是把Notification
分了个类别,设置不同优先级,开关之类的。如果你的 app 适配了的话,用户可以关掉不喜欢的通知,以提高用户体验。垃圾推送消息的打扰了,因为用户可以自主地选择自己关心哪些通知、不关心哪些通知。举个具体的例子,我希望可以即时收到支付宝的收款信息,因为我不想错过任何一笔收益,但是我又不想收到支付宝给我推荐的周围美食,因为我没钱只吃得起公司食堂。这种情况,支付宝就可以创建两种通知渠道,一个收支,一个推荐,而我作为用户对推荐类的通知不感兴趣,那么我就可以直接将推荐通知渠道关闭,这样既不影响我关心的通知,又不会让那些我不关心的通知来打扰我了。
对于每个App来说,通知渠道的划分是非常需要仔细考究的,因为通知渠道一旦创建之后就不能再修改了,因此开发者需要仔细分析自己的App一共有哪些类型的通知,然后再去创建相应的通知渠道。
这里我们来参考一下Twitter的通知渠道划分,如下所示:
可以看到,Twitter就是根据自己的通知类型,对通知渠道进行了非常详细的划分,这样用户的自主选择性就比较高了,也就大大降低了用户不堪其垃圾通知的骚扰而将App卸载的概率。
---------------------
3.2 创建NotificationChannel
代码如下所示:
public class NotificationUtil {
private static final int NOTIFICATION_MUSIC_ID = 10000;
private static NotificationManager notificationManager;
......
//初始化NotificationManager
private static void initNotificationManager(Context context){
if (notificationManager == null){
notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
}
//判断是否为8.0以上:Build.VERSION_CODES.O为26
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//创建通知渠道ID
String channelId = "musicNotification";
//创建通知渠道名称
String channelName = "音乐播放器通知栏";
//创建通知渠道重要性
int importance = NotificationManager.IMPORTANCE_DEFAULT;
createNotificationChannel(context, channelId, channelName, importance);
}
}
//创建通知渠道
@TargetApi(Build.VERSION_CODES.O)
private static void createNotificationChannel(Context context, String channelId, String channelName, int importance) {
NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
//channel有很多set方法
......
//为NotificationManager设置通知渠道
notificationManager.createNotificationChannel(channel);
}
}
说明:这里传入的channelId要和创建的通知channelId一致,才能为指定通知建立通知渠道
1)NotificationChannel的方法列表
getId()获取ChannelIdenableLights(boolean boolean)是否开启指示灯(是否在桌面icon右上角展示小红点)setLightColor()设置指示灯颜色enableVibration()是否开启整的setVibrationPattern() 设置震动频率setImportance()设置频道重要性getImportance() 获取频道重要性setSound() 设置声音getSound() 获取声音setGroup()设置 ChannleGroupgetGroup() 得到 ChannleGroupsetBypassDnd() 设置绕过免打扰模式canBypassDnd() 检测是否绕过免打扰模式getName() 获取通知渠道名称setLockScreenVisibility() 设置是否应在锁定屏幕上显示此频道的通知getLockscreenVisibility() 检测是否应在锁定屏幕上显示此频道的通知setShowBadge()设置是否显示角标canShowBadge() 检测是否显示角标
2)重要程度
数值越高,提示权限就越高,最高的支持发出声音和悬浮通知,如下所示:
public class NotificationManager {
......
public static final int IMPORTANCE_DEFAULT = 3;
public static final int IMPORTANCE_HIGH = 4;
public static final int IMPORTANCE_LOW = 2;
public static final int IMPORTANCE_MAX = 5;
public static final int IMPORTANCE_MIN = 1;
public static final int IMPORTANCE_NONE = 0;
public static final int IMPORTANCE_UNSPECIFIED = -1000;
}
3)发出通知
调用NotificationManager的notify()方法即可
4)删除NotificationChann
int chatChannelId)即可。
4.案例
1)xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/chat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发送聊天通知"/>
</LinearLayout>
2)代码
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private NotificationManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
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);
}
Button chat = findViewById(R.id.chat);
chat.setOnClickListener(this);
Button get = findViewById(R.id.get);
get.setOnClickListener(this);
}
@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);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.chat: //聊天消息
Notification notification = new NotificationCompat.Builder(this, "chat")
.setAutoCancel(true)
.setContentTitle("收到聊天消息")
.setContentText("今天晚上吃什么")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
//设置红色
.setColor(Color.parseColor("#F00606"))
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pendingIntent)
.build();
manager.notify(1, notification);
break;
case R.id.get: //订阅消息
Notification notificationGet = new NotificationCompat.Builder(this, "subscribe")
.setAutoCancel(true)
.setContentTitle("收到订阅消息")
.setContentText("新闻消息")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pendingIntentGet)
.build();
manager.notify(2, notificationGet);
break;
}
}
}
3)效果
注意这里出现了大图标和小图标,聊天消息之所以是红色,因为setColor为红色,详情见:
5.使用RemoteViews自定义Notification
需要使用RemoteViews.RemoteViews描述了一个视图层次的结构,可以显示在另一个进程。
RemoteViews提供了多个构造函数,一般使用RemoteViews(String packageName,int layoutId)。第一个参数为包的名称,第二个为layout资源的Id。当获取到RemoteViews对象之后,可以使用它的一系列setXxx()方法通过控件的Id设置控件的属性。最后使用NotificationCompat.Builder.setContent(RemoteViews)方法设置它到一个Notification中。
6.PendingIntent
6.1 概述
对于一个通知而言,它显示的消息是有限的,一般仅用于提示一些概要信息。但是一般简短的消息,并不能表达需要告诉用户的全部内容,所以需要绑定一个意图,当用户点击通知的时候,调用一个意图展示出一个Activity用来显示详细的内容。而Notification中,并不使用常规的Intent去传递一个意图,而是使用PendingIntent。
Intent和PendingIntent的区别:PendingIntent可以看做是对Intent的包装,通过名称可以看出PendingIntent用于处理即将发生的意图,而Intent用来用来处理马上发生的意图。而对于通知来说,它是一系统级的全局通知,并不确定这个意图被执行的时间。当在应用外部执行PendingIntent时,因为它保存了触发应用的Context,使得外部应用可以如在当前应用中一样,执行PendingIntent里的Intent,就算执行的时候响应通知的应用已经被销毁了,也可以通过存在PendingIntent里的Context照常执行它,并且还可以处理Intent说带来的额外信息。因此可以将PendingIntent看做是延迟执行的Intent。
6.2 创建PendingIntent
获取PendingInten实例可以根据需求从如下方法中获取:
PendingInteng.getBroadcast(contex, requestCode, intent, flags)
PendingInteng.getService(contex, requestCode, intent, flags)
PendingInteng.getActivity(contex, requestCode, intent, flags)
PendingInteng.getActivities(contex, requestCode, intent, flags)
其中flags属性参数用于确定PendingIntent的行为:
FLAG_ONE_SHOT: 表示返回的PendingIntent仅能执行一次,执行完后自动消失
FLAG_NO_CREATE: 表示如果描述的PendingIntent不存在,并不创建相应的PendingIntent,而是返回NULL
FLAG_CANCEL_CURRENT: 表示相应的PendingIntent已经存在,则取消前者,然后创建新的PendingIntent
FLAG_UPDATE_CURRENT: 表示更新的PendingIntent,如果构建的PendingIntent已经存在,则替换它,常用。
setContentIntent(PendingIntent intent)方法,构建一个PendingIntent。
---------------------
6.3 使用案例
这里对目录4案例的代码进行改造,在代码中添加如下方法:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private NotificationManager manager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......
}
@TargetApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName, int importance) {
......
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.chat:
Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this, "chat")
......
.setContentIntent(pendingIntent)
.build();
manager.notify(1, notification);
break;
case R.id.get:
Intent intentGet = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntentGet = PendingIntent.getActivity(this, 0, intentGet, 0);
Notification notificationGet = new NotificationCompat.Builder(this, "subscribe")
......
.setContentIntent(pendingIntentGet)
.build();
manager.notify(2, notificationGet);
break;
}
}
}
点击通知消息后,就可以跳转到NotificationActivity对应的界面了