通知 Notification 我是好久没用过了,说实话都忘的一干二净了,记得前几次用的时候都是现 baidu 的,baidu 几次过后依然是没啥印象,如此和没学何异。所以这笔记该自己写就自己写,不是为了给别人看,而是给自己看,梳理脑中 Notification 的知识点以方便记忆,不梳理的记忆如同杂草,不但没好处还有害
纵览 Notification API
官方代码,无论如何代码结构分层总是做的很好的,抓住了业功能分层就能了解全貌了
Notification 的核心 API:
- NotificationManager - 通知运行在自己的进程上,有自己的服务,NotificationManager 是实现和通知进程通信的外层管理类
- NotificationCompat.Builder - build 用于构建数据
- Notification - 保存通知参数传递给远程 NotificationService
- NotificationChannel - 8.0 要求通知都得加一个渠道用于管理
很简单,很经典的代码机构,不用多说大家都看得懂,都明白其中的意思,麻烦一些的是适配:
- 4.1 - 4.1 之后 Notification 有些变化,官方提供了 NotificationCompat 兼容老版本
- 8.0 - 同一个 app 的同送可能有很多,官方让加上渠道就是为了让用户不至于接受或者都不接受某个 app 的通知,而知你关心或者不关心的一些通知
// 1. build 构造函数都要传渠道号
var build = NotificationCompat.Builder(this, "7602")
// 2. 在 application 中创建渠道,这个渠道是加入到系统设置中去的
// 所以无法修改,只有 add 不能 updata,只有在 app 删除时系统才会删除通知渠道
var notificationManager : NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("7602", "test 渠道",NotificationManager.IMPORTANCE_HIGH)
// 可以添加多个渠道进去
notificationManager.createNotificationChannel(channel)
}
Notification 发送步奏:
// 1. 拿到远程系统服务
var notificationManager : NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// 2. 在 8.0 上添加渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("7602", "test 渠道",NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(channel)
}
// 3. build 构建参数
var build = NotificationCompat.Builder(this, "7602")
.setContentTitle("Title...")
.setContentText("text...")
.setSmallIcon(R.mipmap.ic_launcher)
// 4. 发送通知
notificationManager.notify(1, build.build())
build 中可配置参数
- setContentTitle(CharSequence) - 设置标题
- setContentText(CharSequence) - 设置内容
- setSubText(CharSequence) - 设置内容下面一小行的文字
- setTicker(CharSequence) - 设置收到通知时在顶部显示的文字信息
- setSmallIcon(int) - 设置右下角的小图标,在接收到通知的时候顶部也会显示这个小图标
- setLargeIcon(Bitmap) - 设置左边的大图标
- setWhen(long) - 设置通知时间,一般设置的是收到通知时的System.currentTimeMillis()
- setAutoCancel(boolean) - 用户点击Notification点击面板后是否让通知取消(默认不取消)
-
setDefaults(int) - 通知添加声音、闪灯和振动效果
- Notification.DEFAULT_VIBRATE - 添加默认震动提醒
- Notification.DEFAULT_SOUND - 添加默认声音提醒
- Notification.DEFAULT_LIGHTS - 添加默认三色灯提醒
- Notification.DEFAULT_ALL - 添加默认以上3种全部提醒
- setVibrate(long[]) - 设置振动方式,比如:new long[] {0,300,500,700},延迟 0ms,振动300ms,在延迟 500ms, 接着再振动 700ms
- setLights(int argb, int onMs, int offMs) - 设置三色灯,参数依次是:灯光颜色, 亮持续时间,暗的时间,不是所有颜色都可以,这跟设备有关,有些手机还不带三色灯; 另外,还需要为Notification设置flags为Notification.FLAG_SHOW_LIGHTS才支持三色灯提醒
-
setSound(Uri) - 设置接收到通知时的铃声
- setDefaults(Notification.DEFAULT_SOUND) - 默认铃声
- setSound(Uri.parse("file:///sdcard/xx/xx.mp3")) - 自定义铃声
- setSound(Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "5")) - Android多媒体库内的铃声
- setOngoing(boolean) - 设置为 ture,表示它为一个正在进行的通知,他们通常是用来表示一个后台任务,如播放音乐,文件下载
- setProgress(int,int,boolean) - 带进度条的通知,参数依次为:进度条最大数值,当前进度,setProgress(0, 0, false) 可以移除进度条,setProgress(0, 0, true) 可以一直显示进度条
- setContentIntent(PendingIntent) - 通知的点击事件
-
setVisibility(int) - 锁屏时显示不显示
- Notification.VISIBILITY_PUBLIC- 显示所有通知内容
- Notification.VISIBILITY_PRIVATE - 只显示标题
- Notification.VISIBILITY_SECRET - 不显示任何内容
Notification 参数的说明
-
Ticker 和 SubText 在哪显示
- setProgress
这里的 Progress 样式随着系统不同版本和手机厂商不同有所区别,样式不移动都一样,所以想要样式有没统一还是得采用自定义 Notification 的方式
SmallIcon 是必须设置的,不设置报错,SmallIcon 在左,LargeIcon 在右
注意点
setSmallIcon
setSmallIcon 必须设置的,要不然会直接报错修改NotificationChannel
NotificationChannel 之能往系统中添加,修改不存在的,相同 id 的 NotificationChannel 修改不生效,只有卸载 app 才会清除 NotificationChannel,替代方案是使用新 id 的 NotificationChannel
富文本
- BigTextStyle - 显示长条文字
- -
- -
1. BigTextStyle
一般要是文字多了的话,一行显示不下时就是下面这样
这是我们可是使用 BigTextStyle 包裹文字
var build = NotificationCompat.Builder(this, "7602")
.setStyle( NotificationCompat.BigTextStyle().bigText("AAAAA") )
2. BigPictureStyle
带大图的样式
var bigPictureStyle = NotificationCompat.BigPictureStyle()
bigPictureStyle.bigPicture(BitmapFactory.decodeResource(resources, R.drawable.ic_dog))
bigPictureStyle.setSummaryText( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" )
var build = NotificationCompat.Builder(this, "7602")
.setStyle(bigPictureStyle)
3. InboxStyle
列表
val inboxStyle = NotificationCompat.InboxStyle()
inboxStyle.addLine("AAA")
inboxStyle.addLine("BBB")
inboxStyle.addLine("CCC")
build.setStyle(inboxStyle)
4. MediaStyle
其实还有这个的,但是我看大家都写的略
,没找到具体的资料
通知的取消和更新
- 取消通知
取消通知我想大家都踩的到,就是用 id 来取消呗,既然在发送时用了 id ,那么必然大概率是可以通过 id 来取消的
- NotificationManager.cancel(int id)
- NotificationManager.cancel(String tag, int id)
- NotificationManager.cancelAll() - 清除所有该应用的通知
- 更新通知
利用上次的 build ,使用相同的 id 直接 notify 就行了,相同 id 的通知只能存在一个
优先级
使用 setPriority 方法可以设置通知的优先级,有五个级别:
- PRIORITY_DEFAULT - 默认
- PRIORITY_MIN - 最低,系统只会在用户下拉状态栏的时候才会显示
- PRIORITY_LOW - 较低,系统会将这类通知缩小,或者改变显示的顺序,将排在更重要的通知之后
- PRIORITY_HIGH - 较高,系统可能会将这类通知方法,或改变显示顺序,比较靠前
- PRIORITY_MAX - 最重要的程度, 会弹出一个单独消息框,让用户做出相应
8.0 添加去到之后,不光 build 要设置,channel 一样在构造函数时要传优先级进去
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel("7602", "test 渠道", NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(channel)
}
var build = NotificationCompat.Builder(this, "7602")
.setPriority(NotificationManager.IMPORTANCE_HIGH)
PRIORITY_MAX 现在不好使了,channel 取值范围不支持这个参数了,最高到 PRIORITY_HIGH,另外手机厂商基本都懂通知这块了,随意这个优先级其实不怎惯用了,比如 8.0 之后你把 app 的通知标记为不重要了,这里你即便设置成 PRIORITY_HIGH 也不管用
响应通知点击
状态栏上的通知最最终是要用户来点击的,我们可以把 Notification 当做一个 Button 来处理,Button 可以设置 OnClickListener,Notification 因为是运行在系统进程上的,OnClickListener 对象传递过去也没有意思,这里借助的 intent,是把点击行为包装在 intent 中,没法做太多的花样,这个特殊的 intent 就是 PendingIntent
PendingIntent 是一种特殊的 Intent, 作用和 Intent 一样是用于启动系统组件,可以是:
当点击通知后,系统便会调用 PendingIntent,启动一个活动,服务或广播,这取决于你获取的是那种 PendingIntent。在 PendingIntent 中传入的 Context 销毁以后,PendingIntent 依旧有效,它一般使用在当 Context 销毁后需要执行 Intent的地方,一般不是用于立即执行的时候,比如在点击通知后唤醒一个 Activity。比如若是我们想要用 PendingIntent 大来一个页面,我们在后台 kill 该 app 进程,点击通知时依然会开发目标页面,和启动新的进程一样,该走的初始化都会走,比如 application onCreate 函数
// 启动 Activity 的 PendingIntent
var intent = Intent(this, NotificationReceiver::class.java)
var pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
var build = NotificationCompat.Builder(this, "7602")
.setContentIntent(pendingIntent)
// 发送 Broadcast 的 PendingIntent
var intent2 = Intent("AAA")
var pendingIntent2 = PendingIntent.getBroadcast(this, 0, intent2, 0)
build.setContentIntent(pendingIntent2)
// 启动服务 的 PendingIntent
PendingIntent.getService(Context context, int requestCode, Intent intent, int flags);
PendingIntent.getActivities(Context context, int reqeustCode, Intent[] intents, int flags);
PendingIntent.getForgroundService(Context, int reqeustCode, Intent intent, int flags)
注意自 8.0 禁止静态广播后,经测试静态广播的方式不好用了。广播,服务,页面的 intent 里可以传具体指令或是参数,已实现通知和其他组件的通信
可以添加最多3个简单的按钮
var actionBuilder1 = NotificationCompat.Action.Builder(R.drawable.ic_launcher_foreground, "Action1", pendingIntent)
var actionBuilder2 = NotificationCompat.Action.Builder(R.drawable.ic_launcher_foreground, "Action2", pendingIntent)
var actionBuilder3 = NotificationCompat.Action.Builder(R.drawable.ic_launcher_foreground, "Action3", pendingIntent)
build.addAction( actionBuilder1.build() )
build.addAction( actionBuilder2.build() )
build.addAction( actionBuilder3.build() )
自定义通知样式 RemoteViews
RemoteViews 是远程 view,我们通过系统 API 把 layoutID,复制操作发送到远程去显示
这个就是,使用自己的 layout xml 布局,但是有个坑,通知能支持的 RemoteViews 的高度是固定的,比如官方原话:
普通视图布局限制为 64 dp,扩展视图布局限制为 256 dp
但是国内的厂商啊基本都自己改改的,我手里这台魅族 16 th,估计能支持 80dp 高了,所以这里大家注意一下
另外 RemoteViews 能支持的布局类型和 view 不多,不支持的会直接报错,下面的是能支持的:
- Layout
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
- view
- View Button
- ImageView
- ImageButton
- TextView
- ProgressBar
- ListView
- GridView
- StackView
- ViewStub
- AdapterViewFlipper
- ViewFlipper
- AnalogClock
- Chronometer
RemoteViews 不能 findViewById 来赋值,只能把 id 和值一起发给远程,包括点击也是这样的
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, "艺术家");
自定义通知应用很多的,比如音视频操作,下载,app 辅助等,通知和 app 组件之间的交互都是通过服务或是广播来接收通知相关的点击事件
NotificationChannel 通知渠道
8.0 要大家添加这个通知渠道,然后渠道内也是可以设置一些参数的
if (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);
channel.canBypassDnd();//是否绕过请勿打扰模式
channel.enableLights(true);//闪光灯
channel.setLockscreenVisibility(VISIBILITY_SECRET);//锁屏显示通知
channel.setLightColor(Color.RED);//闪关灯的灯光颜色
channel.canShowBadge();//桌面launcher的消息角标
channel.enableVibration(true);//是否允许震动
channel.getAudioAttributes();//获取系统通知响铃声音的配置
channel.getGroup();//获取通知取到组
channel.setBypassDnd(true);//设置可绕过 请勿打扰模式
channel.setVibrationPattern(new long[]{100, 100, 200});//设置震动模式
channel.shouldShowLights();//是否会有灯光
getManager().createNotificationChannel(channel);
}