这里涉及到的是v4支持包中的通知API,因为这些API能将一些比较新的特性兼容到4.0版本的设备,所以,我们第一步要做的是为项目添加v4包依赖:
implementation 'com.android.support:support-compat:26.0.0'
当然,如果你的项目中已经依赖了其他以com.android.support开头的库的话,就可以不用再添加上面的依赖了。
一、创建通知
一个基本的通知至少必须有三个部分:小图标,通知标题和通知的内容,如下:
NotificationCompat.Builder mNotifyBuilder= new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("我是通知标题")
.setContentText("我是通知内容:" + text)
//8.0以下的优先级直接在这里设置,如果是8.0及以上的则在notification channel中设置
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//这里的1010为当前这个通知的id,之后我们可以根据id对这个通知进行其他操作,如更新、删除等
mNotificationManager.notify(1010, mNotifyBuilder.build());
在上面的代码中,可以看到,构造器中有一个CHANNEL_ID,其实这个通道ID是8.0以上的系统要求的,8.0以下的设备可以忽略,所以,我们需要在程序开始运行的时候就用这个CHANNEL_ID向系统注册一个我们自己的通知通道,代码如下:
public class App extends Application {
private static App instance;
public static final String CHANNEL_ID="com.jackbear.notificationtimer";
@Override
public void onCreate() {
super.onCreate();
instance=this;
createNotificationChannel();
}
private void createNotificationChannel() {
//NotificationChannel这个类只存在于8.0以上的系统,所以需要判断一下
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = getString(R.string.notification_channel_name);
String description = getString(R.string.notification_channel_description);
//配置这个通道的相关信息和通知的优先级
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
//是否需要呼吸灯提示
channel.enableLights(true);
//呼吸灯颜色
// channel.setLightColor();
//是否需要振动提示
channel.enableVibration(true);
//振动模式
// channel.setVibrationPattern();
//上述信息一旦注册,后续的更改都无效了
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}
}
8.0以上的设备一定要注册通道,否则无法显示通知!!!
二、点击通知内容打开某个activity
要打开的activity分两种类型来判断,根据不同类型的activity来选择对应的打开方式。
特殊的activity:即在你的APP里面没有入口,只能通过通知打开的那些activity。针对这类activity,谷歌建议我们打开后,点击返回按钮时,直接返回到手机桌面,哪怕点击通知之前,我们的app为当前进程,点击通知打开了activity后,按返回按钮会直接退到桌面,而不是我们的APP。所以我们主要用以下方式来打开这类activity:
首先在清单文件中为目标activity配置android:taskAffinity=""属性使其不属于任何任务栈和 android:excludeFromRecents="true"属性,禁止其出现在 最近应用 列表中:
<activity android:name="com.CardviewTestActivity"
android:taskAffinity=""
android:excludeFromRecents="true"
/>
为Intent设置如下Flags,将 Activity 设置为在新的空任务中启动:
Intent resultIntent = new Intent(this, CardviewTestActivity.class);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 101,
resultIntent, PendingIntent
.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(resultPendingIntent);
非特殊的activity:即在我们的APP里面有入口的activity,针对这类activity,我们可以为其指定一个上级界面,当我们点击通知打开这类activity时,返回的是为它指定的这个上级界面,而非直接退到手机桌面。
在清单文件中为目标activity配置android:parentActivityName属性指定上级界面:
<activity android:name="com.CardviewTestActivity"
android:parentActivityName=".MainActivity"
>
<!--兼容4.1以下的设备-->
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
</activity>
通过TaskStackBuilder为目标activity创建一个新的任务栈:
Intent resultIntent = new Intent(this, CardviewTestActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(resultPendingIntent);
三、添加操作按钮
通知允许我们通过addAction方法添加最多三个操作按钮,每个操作也都是通过延期意图来定义:
Intent soonzIntent=new Intent(this,TestBroadcastReceiver.class);
soonzIntent.setAction("app.action.soonz");
soonzIntent.putExtra("notificationId",App.NOTIFICATION_TEST_ID);
PendingIntent broadcast = PendingIntent.getBroadcast(this, 0,
soonzIntent, 0);
mNotifyBuilder.addAction(R.drawable.notify,"稍后提醒",broadcast);
mNotifyBuilder.addAction(R.drawable.notify,"按钮2",broadcast);
mNotifyBuilder.addAction(R.drawable.notify,"按钮3",broadcast);
mNotifyBuilder.addAction(R.drawable.notify, "按钮4", broadcast);
四、7.0设备在通知上直接输入文本
相应的字符串资源:
<string name="reply_label">输入回复内容</string>
<string name="label">回复</string>
构建可以回复文字的通知:
String replyLabel = getResources().getString(R.string.reply_label);
//KEY_TEXT_REPLY为保存输入文本时的key
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
//通知面板中输入框的hint文本
.setLabel(replyLabel)
.build();
//用户点击通知面板上的发送按钮后发送的意图,这里是将文本广播给action为
//app.action.reply的广播接受者TestBroadcastReceiver
Intent replyIntent = new Intent();
replyIntent.setAction("app.action.reply");
replyIntent.putExtra("notificationId", App.NOTIFICATION_TEST_ID);
replyIntent.putExtra(NOTIFY_CONTENT,text);
PendingIntent replyPendingIntent =
PendingIntent.getBroadcast(getApplicationContext(),
618, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//label为通知面板中的回复按钮文本
NotificationCompat.Action replyAction =
new NotificationCompat.Action.Builder(R.drawable.send,
getString(R.string.label), replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
mNotifyBuilder.addAction(replyAction);
在广播接收器中获取用户输入的回复内容:
String action = intent.getAction();
int notificationId = intent.getIntExtra("notificationId", -1);
if (action.equals("app.action.reply")) {
String notifyContent = intent.getStringExtra(NOTIFY_CONTENT);
//隐藏通知面板中的回复按钮
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(context, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setContentTitle("我是通知标题")
.setContentText("我是通知内容:" + notifyContent)
//8.0以下的优先级直接在这里设置,如果是8.0及以上的则在notification channel中设置
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(notificationId, mNotifyBuilder.build());
//获取在通知面板中输入的内容
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
CharSequence messageReplied = remoteInput.getCharSequence(KEY_TEXT_REPLY);
}
//TODO 获取到输入内容后发送逻辑
}
对应的通知面板界面:
五、设置通知类别
setCategory()函数可以将通知设置为一个系统级的类别,当手机处于勿扰模式时,系统会根据你所设置的类别来决定是否显示你的通知,一般有五种类别,分别是:
CATEGORY_ALARM: 警报或计时器通知。
CATEGORY_REMINDER: 上次通知用户决定稍后提醒的通知。
CATEGORY_EVENT: 日历事件的通知。
CATEGORY_CALL: 来电等类似的通知。
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setContentTitle("我是通知标题")
.setContentText("我是通知内容")
.setCategory(NotificationCompat.CATEGORY_ALARM);
六、设置是否锁屏显示及显示水平
可以通过 setVisibility()函数设置通知在锁屏界面上的显示水平,有三种显示水平:
VISIBILITY_PUBLIC: 显示通知小图标、通知标题、全部内容。
VISIBILITY_PRIVATE: 只显示通知小图标、标题,隐藏全部内容。
VISIBILITY_SECRET: 不显示该条通知。
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setContentTitle("我是通知标题")
.setContentText("我是通知内容:")
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
七、移除通知
移除方式主要有以下几种:
setAutoCancel(): 创建通知时调用该方法,当用户点击该条通知后,通知自动消失。
setTimeoutAfter(): 创建通知时传入对应的时间,过了这个时间自动消失,当然,在这个时间之前你也可以通过其他方式移除
cancel() : 传入对应通知的id,可移除已经显示以及正在显示的通知。
cancelAll(): 移除所有通知。
八、即时通讯的消息通知模板
7.0开始,系统为即时通讯的软件提供了一个消息通知模板类NotificationCompat.MessagingStyle,如下代码:
long timeMillis = System.currentTimeMillis();
NotificationCompat.Builder notification = new NotificationCompat.Builder(this,
App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setStyle(new NotificationCompat.MessagingStyle("我:")
//会话标题,群组会话可以用群名称,没有群名称则显示部分群成员名称
.setConversationTitle("点餐群")
//第三个参数传null即表示这条信息是当前用户发的,系统会自动替换为你在
//MessagingStyle中传入的名称
.addMessage("嗨", timeMillis, null)
.addMessage("什么事?", timeMillis, "同事:")
.addMessage("没什么", timeMillis, null)
.addMessage("一起午餐怎么样?", timeMillis, "同事:"));
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
managerCompat.notify(0,notification.build());
除了文本信息,也可以通过NotificationCompat.MessagingStyle.Message.setData()函数显示图片等多媒体文件。
8.0开始,你还可以通过Notification.MessagingStyle.addHistoricMessage()方法向通知中插入历史性的消息。
九、显示右侧大图和扩展大图
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_notify_big);
//右侧大图
mNotifyBuilder.setLargeIcon(bitmap);
//扩展大图
mNotifyBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap));
十、显示一段文字
通知的内容默认只能显示一行,可以通过以下方式在通知上显示一段文字:
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(
"我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字" +
"我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字" +
"我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字"
));
十一、显示多行独立的文字
通知允许我们最多显示6行文本,这里的独立是指每行文本都是单独的,与其他行互相没有意思上的联系,区别与上面的一段文本或即时通讯的历史信息。
NotificationCompat.InboxStyle inboxStyle =
new NotificationCompat.InboxStyle();
String[] events = new String[8];
// Sets a title for the Inbox in expanded layout
inboxStyle.setBigContentTitle("我是独立行标题:");
// Moves events into the expanded layout
for (int i = 0; i < events.length; i++) {
inboxStyle.addLine(events[i] = "我是独立行我是独立行我是独立行我是独立行我是独立行我是独立行" + i);
}
// Moves the expanded layout object into the notification object.
mNotifyBuilder.setStyle(inboxStyle);
十二、将同一类型的通知整合到一个通知组里
从7.0开始,我们可以将同一个应用的同类型的通知整合为一个通知组显示,例如,你的电子邮件应用收到了多个不同的人给你发来的多封邮件,即时聊天应用收到了多个人给你发来了聊天信息。此时,我们不必每收到一封邮件或一个信息就去创建一个独立的通知,反之,可以将这些通知整合为一个通知组来显示。
以下为整合代码:
public static final String GROUP_NOTIFY_KEY="com.jackbear.notificationtimer.groupKey";
public void groupNotify(){
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.notify);
Notification summaryNotification =
new NotificationCompat.Builder(this, App.CHANNEL_ID)
.setContentTitle("归纳性通知标题")
//为支持7.0以下设备设置通知归纳性通知的通知内容
.setContentText("您有新的信息")
.setSmallIcon(R.drawable.notify)
//归纳性通知的相关信息
.setStyle(new NotificationCompat.InboxStyle()
.addLine("我是扩展的独立行文本1")
.addLine("我是扩展的独立行文本2")
.setBigContentTitle("新信息到了!")
.setSummaryText("jack_bear_csdn@163.com"))
//指定这个归纳性通知属于哪个组
.setGroup(GROUP_NOTIFY_KEY)
//指定这个通知作为归纳性通知
.setGroupSummary(true)
.build();
for (int i = 0; i < 5; i++) {
NotificationCompat.Builder notify=new NotificationCompat.Builder(this,App.CHANNEL_ID)
.setContentTitle("子通知标题"+i)
.setContentText("子通知内容"+i)
.setSmallIcon(R.drawable.notify)
.setLargeIcon(bitmap)
.setGroup(GROUP_NOTIFY_KEY);
//子通知必须作为一个完整单独的通知,所以每个子通知notify中的参数id不能相同
managerCompat.notify(i,notify.build());
}
//
managerCompat.notify(110,summaryNotification);
}
整合时的显示样式:
展开显示时的样式:
注意:按照谷歌的说法,7.0以上的设备,只要为每个子通知调用setGroup函数设置同一分组,系统会给我们自动归纳显示,不需要额外创建上述代码中的summaryNotification作为归纳性通知去归纳其他子通知,但 我在华为mate10(8.0)上不通过summaryNotification 的话还是没办法将子通知归纳到同一组,所以为了以防万一,我们最好还是额外借助summaryNotification 来作为归纳性通知来归纳其他子通知。
十三、通知通道notification channel
文章开头我们知道,8.0的系统要求我们必须向系统注册通道,当我们创建一个通知时,需要传入相应的通道id,然后这个通知就会走这个通道。我们可以根据重要性来将通道分为四种等级:
Urgent: 该通道中的通知一般作为最重要的警报性通知出现,并伴有提示音。
High: 高重要性通道,走该通道的通知会有提示音。
Medium: 中等重要性通道,其通知不会有提示音。
Low: 非重要通道,其通知既不会有提示音,也不会出现在状态栏中,只有往下拉状态栏时才看到。
注意: 除了将通知绑定到对应的通道,我们还应该将通知设定为不同的种类,如本文第五部分中所述。
因此,我们的一个APP可以同时向系统注册四种通道,最重要的通知我们让其走Urgent通道,高重要性的通知走High通道,不重要的通知走Low通道,这样,我们就可以在APP的设置界面中让用户去控制哪些类型的通知需要提示音、呼吸灯、振动等。我们只需要将对应通道的ID传入一下intent,打开通知管理界面让用户自己设置即可:
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, App.CHANNEL_ID);
startActivity(intent);
十四、自定义通知UI
自定义通知的UI,我们可以只自定义通知标题和通知内容的区域(不包括通知小图标、时间戳、主题和操作按钮所在的部分),也可以将整个通知区域全部替换为我们的自定义界面。需要注意的是:自定义的布局高度是有限制的,折叠时的布局最高为64dp,扩展时的布局最高为256dp, 高于这个高度,则会显示不全。以下是只自定义标题和内容区域布局的代码:
RemoteViews collapseView=new RemoteViews(getPackageName(),R.layout.notification_collapse_view);
RemoteViews expandedView=new RemoteViews(getPackageName(),R.layout.notification_expanded_view);
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
//告知系统我们要自定义布局
.setStyle(new NotificationCompat.DecoratedCustomViewStyle())
//折叠显示的自定义布局
.setCustomContentView(collapseView)
//扩展显示的自定义布局
.setCustomBigContentView(expandedView)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setCategory(NotificationCompat.CATEGORY_ALARM)
//8.0以下的优先级直接在这里设置,如果是8.0及以上的则在notification channel中设置
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
以下为自定义整个通知区域布局的代码,与上面相比,只需要将setStyle(new NotificationCompat.DecoratedCustomViewStyle())和.setCustomContentView(collapseView)注释掉即可:
RemoteViews collapseView=new RemoteViews(getPackageName(),R.layout.notification_collapse_view);
RemoteViews expandedView=new RemoteViews(getPackageName(),R.layout.notification_expanded_view);
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
//告知系统我们要自定义布局
// .setStyle(new NotificationCompat.DecoratedCustomViewStyle())
//折叠显示的自定义布局
// .setCustomContentView(collapseView)
//扩展显示的自定义布局
.setCustomBigContentView(expandedView)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setCategory(NotificationCompat.CATEGORY_ALARM)
//8.0以下的优先级直接在这里设置,如果是8.0及以上的则在notification channel中设置
.setPriority(NotificationCompat.PRIORITY_DEFAULT);