通知
当某个应用程序希望向用户发出一些提示信息,而该应用程序不再前台运行时,就可以借助通知来实现。
通知的基本用法
通知既可以在活动里创建,可以在广播接收器里创建,也可以在服务里创建。在活动里创建通知的场景比较少,因为一般只有当程序进入后台的时候,才需要使用通知。
Android O 引入了 通知渠道(Notification Channels),以提供统一的系统来帮助用户管理通知,如果是针对 android O 为目标平台时,必须实现一个或者多个通知渠道,以向用户显示通知。
Notification 的创建主要涉及到 Notification.Builder 、 Notification 、 NotificationManager 。Notification.Builer : 使用建造者模式构建 Notification 对象。由于 Notification.Builder 仅支持 Android 4.1及之后的版本,为了解决兼容性问题, Google 在 Android Support v4 中加入了 NotificationCompat.Builder 类。对于某些在 Android 4.1 之后才特性,即使 NotificationCompat.Builder 支持该方法,在之前的版本中也不能运行。点我 查看更多关于 Notification 兼容性问题处理。
Notification : 通知对应类,保存通知相关的数据。NotificationManager 向系统发送通知时会用到。
NotificationManager : NotificationManager 是通知管理类,它是一个系统服务。调用 NotificationManager 的 notify() 方法可以向系统发送通知。
创建通知的步骤为
1、获取 NotificationManager 对象:
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
2、实例化 NotificationCompat.Builder 并设置相关属性
//第二步:实例化 NotificationCompat.Builder 并设置相关属性
NotificationCompat.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//判断是否是8.0Android.O
String channelID = "1";
String channelName = "channel_name";
NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
notifyManager.createNotificationChannel(channel);
channel.setLightColor(Color.GREEN);
builder = new NotificationCompat.Builder(this, channelID);
} else {
builder = new NotificationCompat.Builder(this);
}
//设置小图标只能使用纯alpha图层的图片进行设置
builder.setSmallIcon(R.mipmap.ic_launcher)
//设置大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.avator))
//设置通知标题
.setContentTitle("最简单的Notification")
//设置通知内容
.setContentText("text")
.setAutoCancel(true);
//第三步:通过builder.build()方法生成Notification对象,并发送通知,id=1,要保证,每个通知的id都是不同的
notifyManager.notify(1, builder.build());
3、通过builder.build()方法生成Notification对象,并发送通知,id=1
notifyManager.notify(1, builder.build());
到此通知设置完成
对通知设置点击事件:PendingIntent,同Intent一样,指明某一个意图,用于启动活动,启动服务,发送广播。Intent倾向于立即执行,PendingIntent倾向于在某个合适的时机去执行某个动作,延迟执行的Intent。PendingIntent提供了几个静态方法获取PendingIntent的实例,如getActivity(),getBroadcast(),getService(),他们的参数都是一致的。
新建NotificationActivity。
修改MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatButton sendNotice = findViewById(R.id.send_notice);
sendNotice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendNotification();
}
});
}
private void sendNotification() {
//第一步:获取NotificationManager实例
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//第二步:实例化 NotificationCompat.Builder 并设置相关属性
NotificationCompat.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//判断是否是8.0Android.O
String channelID = "1";
String channelName = "channel_name";
NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
notifyManager.createNotificationChannel(channel);
channel.setLightColor(Color.GREEN);
builder = new NotificationCompat.Builder(this, channelID);
} else {
builder = new NotificationCompat.Builder(this);
}
Intent intent = new Intent(this, NotificationActivity.class);
//第一个参数 上下文对象
//第二个参数 一般用不到。传入0 即可
//第三个参数 Intent对象
//第四个参数 用于确定PendingIntent的行为,有FLAG_ONE_SHOP等,一般设为0
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
//设置小图标只能使用纯alpha图层的图片进行设置
builder.setSmallIcon(R.mipmap.ic_launcher)
//设置大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.avator))
//设置通知标题
.setContentTitle("最简单的Notification")
//设置通知内容
.setContentText("text")
.setContentIntent(pi)
.setAutoCancel(true);
//第三步:通过builder.build()方法生成Notification对象,并发送通知,id=1,要保证,每个通知的id都是不同的
notifyManager.notify(1, builder.build());
}
}
点击通知后的效果
更新 Notification
更新通知很简单,只需要再次发送相同 ID 的通知即可,如果之前的通知还未被取消,则会直接更新该通知相关的属性;如果之前的通知已经被取消,则会重新创建一个新通知。更新通知跟发送通知使用相同的方式。
取消 Notification
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) 无效。
通知的进阶技巧
设置Notification 的通知效果
给 Notification 设置诸如震动、铃声、呼吸灯等效果。
Notification 有震动、响铃、呼吸灯三种响铃效果,可以通过 setDefaults(int defualts) 方法来设置。 Default 属性有以下四种,一旦设置了 Default 效果,自定义的效果就会失效。
1、设置默认通知效果
//设置系统默认提醒效果,一旦设置默认提醒效果,则自定义的提醒效果会全部失效。
//添加默认震动效果,需要申请震动权限
//<uses-permission android:name="android.permission.VIBRATE" />
Notification.DEFAULT_VIBRATE
//添加系统默认声音效果,设置此值后,调用setSound()设置自定义声音无效
Notification.DEFAULT_SOUND
//添加默认呼吸灯效果,使用时须与 Notification.FLAG_SHOW_LIGHTS 结合使用,否则无效
Notification.DEFAULT_LIGHTS
//添加上述三种默认提醒效果
Notification.DEFAULT_ALL
2、除了以上几种设置 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
3、设置自定义通知效果
builder.setSmallIcon(R.mipmap.ic_launcher)
.....
//设置震动
.setVibrate(new long[]{0, 1000, 1000, 1000})
//设置音频 每一个手机的/system/media/audio/ringtones目录下都有很多音频
//调用系统多媒体库内的铃声
//.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"));
//调用自己提供的铃声,位于 /res/values/raw 目录下
// .setSound(Uri.parse("android.resource://包名/" + R.raw.sound));
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/xxxx")))
//设置呼吸灯
.setLights(Color.GREEN, 1000, 1000)
.........
notifyManager.notify(1, builder.build());
通知的高级功能
设置长文字、信息、图片等需要使用setStytle()方法。
当设置的内容过长时,通知无法显示完整,多余的部分会用.....代替,若真的需要显示一段长文字,代码如下:
builder.setSmallIcon(R.mipmap.ic_launcher)
//设置大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.avator))
//设置通知标题
.setContentTitle("title")
//设置通知内容
.setContentText("Learn how to build notifications ,send and sync data,and use voice actions.Get the official Android IDE and developer tools build apps for Android")
.setContentIntent(pi)
//设置震动
.setVibrate(new long[]{0, 1000, 1000, 1000})
//设置音频 每一个手机的/system/media/audio/ringtones目录下都有很多音频
//调用系统多媒体库内的铃声
//.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"));
//调用自己提供的铃声,位于 /res/values/raw 目录下
// .setSound(Uri.parse("android.resource://包名/" + R.raw.sound));
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/xxxx")))
//设置呼吸灯
.setLights(Color.GREEN, 1000, 1000)
.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications ,send and sync data,and use voice actions." +
"Get the official Android IDE and developer tools build apps for Android"))
.setAutoCancel(true);
//第三步:通过builder.build()方法生成Notification对象,并发送通知,id=1,要保证,每个通知的id都是不同的
//notifyManager.cancel(1);
notifyManager.notify(1, builder.build());
设置通知的重要程度
builder.setSmallIcon(R.mipmap.ic_launcher)
//设置大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.avator))
//设置通知标题
.setContentTitle("title")
//设置通知内容
.setContentText("Learn how to build notifications ,send and sync data,and use voice actions.Get the official Android IDE and developer tools build apps for Android")
.setContentIntent(pi)
//设置震动
.setVibrate(new long[]{0, 1000, 1000, 1000})
//设置音频 每一个手机的/system/media/audio/ringtones目录下都有很多音频
//调用系统多媒体库内的铃声
//.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"));
//调用自己提供的铃声,位于 /res/values/raw 目录下
// .setSound(Uri.parse("android.resource://包名/" + R.raw.sound));
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/xxxx")))
//设置呼吸灯
.setLights(Color.GREEN, 1000, 1000)
.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications ,send and sync data,and use voice actions." +
"Get the official Android IDE and developer tools build apps for Android"))
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(true);
//第三步:通过builder.build()方法生成Notification对象,并发送通知,id=1,要保证,每个通知的id都是不同的
//notifyManager.cancel(1);
notifyManager.notify(1, builder.build());
完整代码如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatButton sendNotice = findViewById(R.id.send_notice);
sendNotice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendNotification();
}
});
}
private void sendNotification() {
//第一步:获取NotificationManager实例
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//第二步:实例化 NotificationCompat.Builder 并设置相关属性
NotificationCompat.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//判断是否是8.0Android.O
String channelID = "1";
String channelName = "channel_name";
NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
notifyManager.createNotificationChannel(channel);
channel.setLightColor(Color.GREEN);
builder = new NotificationCompat.Builder(this, channelID);
} else {
builder = new NotificationCompat.Builder(this);
}
Intent intent = new Intent(this, NotificationActivity.class);
//第一个参数 上下文对象
//第二个参数 一般用不到。传入0 即可
//第三个参数 Intent对象
//第四个参数 用于确定PendingIntent的行为,有FLAG_ONE_SHOP等,一般设为0
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
//设置小图标只能使用纯alpha图层的图片进行设置
builder.setSmallIcon(R.mipmap.ic_launcher)
//设置大图标
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.avator))
//设置通知标题
.setContentTitle("title")
//设置通知内容
.setContentText("Learn how to build notifications ,send and sync data,and use voice actions.Get the official Android IDE and developer tools build apps for Android")
.setContentIntent(pi)
//设置震动
.setVibrate(new long[]{0, 1000, 1000, 1000})
//设置音频 每一个手机的/system/media/audio/ringtones目录下都有很多音频
//调用系统多媒体库内的铃声
//.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"));
//调用自己提供的铃声,位于 /res/values/raw 目录下
// .setSound(Uri.parse("android.resource://包名/" + R.raw.sound));
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/xxxx")))
//设置呼吸灯
.setLights(Color.GREEN, 1000, 1000)
.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications ,send and sync data,and use voice actions." +
"Get the official Android IDE and developer tools build apps for Android"))
.setPriority(NotificationCompat.PRIORITY_MAX)
.setAutoCancel(true);
//第三步:通过builder.build()方法生成Notification对象,并发送通知,id=1,要保证,每个通知的id都是不同的
//notifyManager.cancel(1);
notifyManager.notify(1, builder.build());
}
}
Notification的自定义布局
这里就需要提出一个新的知识点RemoteView,望文生义,即远程View。
RemoteView表示的是一种View结构,它可以在其他进程中显示(具体来讲是SystemServer进程),由于它是在其他进程中显示,为了更新它的界面,我们不能简单地使用普通View的那一套方法,RemoteView提供了一系列Set方法用于更新界面。
RemoteView没有findViewById方法,因此无法访问里面的View元素,而必须通过RemoteViews所提供的一系列set方法来完成,这是通过反射调用的。
通知栏由NotificationManager(NM)管理,而NM通过Binder和SystemService进程中的NotificationManagerService加载的,而它运行在系统的SystemService中,这就和我们进程构成了跨进程通讯。
构造方法
RemoteViews(String packageName, int layoutId),第一个参数是当前应用的包名,第二个参数是待加载的布局文件。
支持组件
布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout
组件:Button、ImageButton、ImageView、ProgressBar、TextView、ListView、GridView、ViewStub等(例如EditText是不允许在RemoveViews中使用的,使用会抛异常)。
原理
系统将view操作封装成Action对象,Action同样实现了Parcelable接口,通过Binder传递到SystemServer进程。远程进程通过RemoteViews的apply方法来进行view的更新操作,RemoteViews的apply方法内部则会去遍历所有的action对象并调用它们的apply方法来进行view的更新操作。
这样做的好处是不需要定义大量的Binder接口,其次批量执行RemoteViews中的更新操作提高了程序性能。
工作流程
首先RemoteViews会通过Binder传递到SystemService进程,因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews的包名等信息拿到该应用的资源;然后通过LayoutInflater去加载RemoteViews中的布局文件。接着系统会对View进行一系列界面更新任务,这些任务就是之前我们通过set来提交的。set方法对View的更新并不会立即执行,会记录下来,等到RemoteViews被加载以后才会执行。
给对应的布局View设置点击事件:
remoteViews.setOnClickPendingIntent(R.id.iv,pendingIntent1)
单击通知时的响应事件:
notification.contentIntent = pendingIntent//对应的是第一个pendingIntent
代码实现
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.LinearLayoutCompat 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"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.v7.widget.AppCompatButton
android:id="@+id/send_notice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="send notice"/>
</android.support.v7.widget.LinearLayoutCompat>
activity_another.xml
<RelativeLayout 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=".NotificationActivity">
<android.support.v7.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="24sp"
android:text=" This is another layout"/>
</RelativeLayout>
activity_notification.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".NotificationActivity">
<android.support.v7.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="24sp"
android:text=" This is notification layout"/>
</RelativeLayout>
remote_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_rv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="removte"
/>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="button"/>
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AppCompatButton sendNotice = findViewById(R.id.send_notice);
sendNotice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendNotification();
}
});
}
private void sendNotification() {
//第一步:获取NotificationManager实例
NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//第二步:实例化 NotificationCompat.Builder 并设置相关属性
NotificationCompat.Builder builder;
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//判断是否是8.0Android.O
String channelID = "1";
String channelName = "channel_name";
NotificationChannel channel = new NotificationChannel(channelID, channelName, NotificationManager.IMPORTANCE_HIGH);
notifyManager.createNotificationChannel(channel);
channel.setLightColor(Color.GREEN);
builder = new NotificationCompat.Builder(this, channelID);
} else {
builder = new NotificationCompat.Builder(this);
}
builder.setSmallIcon(R.mipmap.ic_launcher)
// .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.avator))
// .setContentTitle("RemoteViews Notification title")
// .setContentText("RemoteViews Notification text")
.setWhen(System.currentTimeMillis())
.setAutoCancel(true);
Notification notification = builder.build();
//设置用户点击notification的动作
// pendingIntent 延期的意图
Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
//自定义界面
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.remote_view);
remoteViews.setTextViewText(R.id.tv_rv, "我是自定义的 notification");
Intent intent1 = new Intent(this, AnotherActivity.class);
PendingIntent pendingIntent1 = PendingIntent.getActivity(this, 0, intent1, 0);
//对自定义布局中的控件的点击事件
remoteViews.setOnClickPendingIntent(R.id.button, pendingIntent1);
notification.contentView = remoteViews;
//对通知的点击事件
notification.contentIntent = pendingIntent;
//把定义的notification 传递给 notificationmanager
notifyManager.notify(1, notification);
}
}
参考文章
https://www.jianshu.com/p/ca92797d925a
https://blog.csdn.net/tianhui1234567/article/details/79505844
https://blog.csdn.net/SweetInsomnia/article/details/72466345
https://www.jianshu.com/p/9cefaa07a090
在此感谢你们的无私分享。