横幅通知,也称为提醒式通知,效果如下图:
这个效果在QQ,微信,钉钉等一些主流的App当中,大家一定很熟悉,今天就来说说如何实现。
可能会触发提醒式通知的条件有如下3种:
- 用户的Activiity处于全屏模式(应用使用fullScreenIntent)
- 通知的优先级很高,且在搭载Android 7.1(API级别25)及更低版本的设备上使用铃声或震动。
- 在搭载Android 8.0(API级别为26)及更高版本的设备上,通知渠道的重要程度比较高。
但是现实往往是残酷的,当你按照要求照做之后发现8.0以上系统还是不能实现你想要的效果。下面来大概说道说道原因。
由于国内大多数App都比较流氓,喜欢这通知也发,那通知也发,导致用户被一堆垃圾信息困扰,系统为了保护用户不受此类消息的困扰,就关闭了权限,如果想要显示,就只能由用户手动打开该权限才行。而且系统没办法获取该权限是否打开。所以,当产品再拿QQ等国民应用来跟你说人家都能实现怎么怎么滴的时候请勇敢的怼回去。人家那些应用厂商一般都默认打开权限的,这没什么好说的,级别在那放着呢。
打开权限管理页面的方法,不同版本有些差别,需适配下:
//打开系统消息通知设置页面
Intent intent = new Intent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//android 8.0引导,引导到CHANNEL_ID对应的渠道设置下
intent.setAction(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getContext().getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, newChannel);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//android 5.0-7.0,引导到所有渠道设置下(单个渠道没有具体的设置)
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra("app_package", getContext().getPackageName());
intent.putExtra("app_uid", getContext().getApplicationInfo().uid);
} else {
//其他
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getContext().getPackageName(), null));
}
startActivity(intent);
上面是一种解决方式,还有人说了,那我想任何时候用户都能弹出横幅通知呢,那就自己写咯。大概思路就是用悬浮窗的方式展示一个Toast或者Dialog。
大概代码如下:
private static void showHeadsUpNotification(Context context, UMessage uMessage) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//1、判断是否开启了悬浮通知栏权限 注:部分华为无效
if (!Settings.canDrawOverlays(MyApplication.getInstance())) {
MessageDialog.show((AppCompatActivity) ActivityUtils.getTopActivity(), "提示",
"请打开" + AppUtils.getAppName() + "的悬浮窗权限,否则会有很多重要消息漏掉哦!", "确定", "取消")
.setOnOkButtonClickListener(new OnDialogButtonClickListener() {
@Override
public boolean onClick(BaseDialog baseDialog, View v) {
//未显示消息入队
headsupMsgQueue.offer(uMessage);
requestOverlayPermission();
return false;
}
})
.setOnCancelButtonClickListener(new OnDialogButtonClickListener() {
@Override
public boolean onClick(BaseDialog baseDialog, View v) {
return false;
}
});
} else {
//显示横幅通知
showHeadsUpNotification_(context, uMessage);
}
}
}
//请求悬浮窗权限
@TargetApi(Build.VERSION_CODES.M)
private static void requestOverlayPermission() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + AppUtils.getAppPackageName()));
ActivityUtils.getTopActivity().startActivityForResult(intent, ALERT_WINDOW_PERMISSION_REQCODE);
}
public static void showHeadsUpNotification_(Context context) {
if (headsupMsgQueue.isEmpty())
return;
//由于一开始没有悬浮窗权限,所以当收到消息通知时先将他们暂时都存入队列中,当获得权限之后再显示横幅通知
while (!headsupMsgQueue.isEmpty()) {
UMessage uMessage = headsupMsgQueue.poll();
if (uMessage != null) {
showHeadsUpNotification_(context, uMessage);
}
}
}
private static void showHeadsUpNotification_(Context context, UMessage uMessage) {
new Handler(Looper.getMainLooper()).post(() -> {//这里要看是否在子线程中再加
// 传入 Application 对象表示设置成全局的,但需要有悬浮窗权限
new XToast<>(MyApplication.getInstance())
.setView(R.layout.layout_headsup_notification)
// 设置成可拖拽的
//.setDraggable()
// 设置显示时长
.setDuration(6000)
// 设置动画样式
//.setAnimStyle(android.R.style.Animation_Translucent)
// 设置外层是否能被触摸
//.setOutsideTouchable(false)
// 设置窗口背景阴影强度
// .setBackgroundDimAmount(0.5f)
.setGravity(Gravity.TOP)
.setText(R.id.notification_app_name, AppUtils.getAppName())
.setText(R.id.notification_title, uMessage.title)
.setText(R.id.notification_text, uMessage.text)
.setOnClickListener(new XToast.OnClickListener<View>() {
@Override
public void onClick(XToast<?> toast, View view) {
// 点击这个 View 后消失
toast.cancel();
if (uMessage.extra != null && uMessage.extra.containsKey("type") && uMessage.extra.containsKey("content")) {
// 跳转到某个Activity
Intent handleIntent = new Intent(context, NotifyActivity.class);
toast.startActivity(handleIntent);
}
}
})
.show();
});
}
代码仅提供实现思路,不能直接运行,有需要还是结合自己的需求自己写吧。
最近一直搞这个通知,唉,系统升级版本越高权限控制是越来越严格了,有时候发现我们利用系统提供的能力无能为力的时候还是自己动手实现吧。