Android开发-Notification(含封装工具类)

前言

Notification,相信大部分 学Android都对他都很熟悉,而网上很多关于Notification的使用教程都是基于Android 8.0以下的,而 现在普遍的Android设备基本都在Android 6.0到Android9.0,而本节给大家介绍的是Android 8.0以下,Android8.0以上的适配;

具体可以查看

[官方文档]  developer.android.google.cn/guide/topic…

通知栏介绍

通知栏解析.png

通知栏的作用

通知栏的主要目的就是将一些重要的信息即使告诉用户,通知栏的设计非常巧妙,不用占用空间,只是在通知栏显示,当用户下拉就可以看到了,如果用户设置了通知的程度为最高的话,当受到消息会在当前界面显示内容(时间为2秒),之后就会隐藏到通知栏中。

通知栏适配

这里主要的难点是8.0(targetSdkVersion=26) ,在8.0以下的话我们可以在应用中找到显示通知,来进行关闭,这样的话所有的推送我们都收不到(包括一些重要的信息),所以8.0之后google新增了通知渠道,就是每条通知都要属于一个对应的渠道。每个App都可以自由地创建当前App拥有哪些通知渠道,但是这些通知渠道的控制权都是掌握在用户手上的。

一定要适配吗?

Google这次对于8.0系统通知渠道的推广态度还是比较强硬的。

首先,如果你升级了appcompat库,那么所有使用appcompat库来构建通知的地方全部都会进行废弃方法提示,如下所示:

适配错误.png 上图告诉我们,此方法已废弃,需要使用带有通知渠道的方法才行。

使用方法

创建通知渠道

首先需要确保targetSdkVersion已经指定到了26或者更高,如下所示:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.notificationtest"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}
复制代码

创建渠道代码

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);
}

@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);
    }
复制代码

代码不长,我来简单解释下。这里我们在MainActivity中创建了两个通知渠道,首先要确保的是当前手机的系统版本必须是Android 8.0系统或者更高,因为低版本的手机系统并没有通知渠道这个功能,不做系统版本检查的话会在低版本手机上造成崩溃。

创建一个通知渠道的方式非常简单,这里我封装了一个createNotificationChannel()方法,里面的逻辑相信大家都看得懂。需要注意的是,创建一个通知渠道至少需要渠道ID、渠道名称以及重要等级这三个参数,其中渠道ID可以随便定义,只要保证全局唯一性就可以。渠道名称是给用户看的,需要能够表达清楚这个渠道的用途。重要等级的不同则会决定通知的不同行为,当然这里只是初始状态下的重要等级,用户可以随时手动更改某个渠道的重要等级,App是无法干预的。

封装工具类

记录一下,以便后面学习

/**
 * 通知工具类
 *
 * @author iwen大大怪
 * @create 2021/11/16 15:36
 */
public class QlNotificationUtil {

    private static final String TAG = "QlNotificationUtil";

    /**
     * 请求代码
     */
    public static final int RequestCode = 1;

    /**
     * 新消息
     */
    public static final String NEW_MESSAGE = "chat";

    /**
     * 群消息
     */
    public static final String NEW_GROUP = "chat_group";

    /**
     * 其他消息
     */
    public static final String OTHER_MESSAGE = "other";

    /**
     * 提示
     */
    public static final String Ticker = "您有一条新的消息";

    /**
     *
     */
    public static final String CHECK_OP_NO_THROW = "checkOpNoThrow";

    /**
     * 发布通知
     */
    public static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";

    /**
     * notifyId
     */
    public static int notifyId = 0;

    /**
     * 适配 Android8.0  创建通知渠道
     * tips:可以写在MainActivity中,也可以写在Application中,实际上可以写在程序的任何位置,
     * 只需要保证在通知弹出之前调用就可以了。并且创建通知渠道的代码只在第一次执行的时候才会创建,
     * 以后每次执行创建代码系统会检测到该通知渠道已经存在了,因此不会重复创建,也并不会影响任何效率。
     */
    public static void setNotificationChannel(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String channelId = NEW_MESSAGE;
            String channelName = "新消息通知";
            createNotificationChannel(context, channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
            channelId = OTHER_MESSAGE;
            channelName = "其他通知";
            createNotificationChannel(context, channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
        }
    }

    /**
     * 创建配置通知渠道
     *
     * @param channelId   渠道id
     * @param channelName 渠道name
     * @param importance  优先级
     */
    @TargetApi(Build.VERSION_CODES.O)
    private static void createNotificationChannel(Context context, String channelId, String channelName, int importance) {
//        createNotificationGroup(context,channelId,channelName);
        NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
        // 禁止该渠道使用角标
        channel.setShowBadge(false);
        // 设置渠道组id
//        channel.setGroup(channelId);
        // 配置通知渠道的属性
//        channel .setDescription("渠道的描述");
        // 设置通知出现时的闪灯(如果 android 设备支持的话)
        channel.enableLights(true);
        // 设置通知出现时的震动(如果 android 设备支持的话)
        channel.enableVibration(true);
        // 如上设置使手机:静止1秒,震动2秒,静止1秒,震动3秒
//        channel.setVibrationPattern(new long[]{1000, 2000, 1000,3000});
        // 设置锁屏是否显示通知
        channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
        // 设置呼吸灯颜色
        channel.setLightColor(Color.BLUE);
        // 设置是否可以绕过请勿打扰模式
        channel.setBypassDnd(true);
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(channel);
    }

    /**
     * 创建渠道组(若通知渠道比较多的情况下可以划分渠道组)
     *
     * @param groupId   渠道组id
     * @param groupName 渠道组name
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public static void createNotificationGroup(Context context, String groupId, String groupName) {
        NotificationChannelGroup group = new NotificationChannelGroup(groupId, groupName);
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannelGroup(group);
    }

    /**
     * 发送通知(刷新前面的通知)
     *
     * @param context      上下文
     * @param contentTitle 标题
     * @param contentText  内容
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void show(Context context, String contentTitle, String contentText, Class<?> cls) {
        show(context, contentTitle, contentText, null, 0, NEW_MESSAGE, cls);
    }

    /**
     * 发送自定义通知(刷新前面的通知)
     *
     * @param context      上下文
     * @param contentTitle 标题
     * @param contentText  内容
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void show(Context context, String contentTitle, String contentText, RemoteViews views, Class<?> cls) {
        show(context, contentTitle, contentText, views, 0, NEW_MESSAGE, cls);
    }

    /**
     * 发送通知(刷新前面的通知,指定通知渠道)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     * @param channelId    渠道id
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void show(Context context, String contentTitle, String contentText, String channelId, Class<?> cls) {
        show(context, contentTitle, contentText, null, 0, channelId, cls);
    }

    /**
     * 发送自定义通知(刷新前面的通知,指定通知渠道)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     * @param channelId    渠道id
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void show(Context context, String contentTitle, String contentText, RemoteViews views, String channelId, Class<?> cls) {
        show(context, contentTitle, contentText, views, 0, channelId, cls);
    }

    /**
     * 发送多条通知(默认通知渠道)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void showMuch(Context context, String contentTitle, String contentText, Class<?> cls) {
        show(context, contentTitle, contentText, null, ++notifyId, NEW_MESSAGE, cls);
    }

    /**
     * 发送多条自定义通知(默认通知渠道)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void showMuch(Context context, String contentTitle, String contentText, RemoteViews views, Class<?> cls) {
        show(context, contentTitle, contentText, views, ++notifyId, NEW_MESSAGE, cls);
    }

    /**
     * 发送多条通知(指定通知渠道)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     * @param channelId    渠道id
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void showMuch(Context context, String contentTitle, String contentText, String channelId, Class<?> cls) {
        show(context, contentTitle, contentText, null, ++notifyId, channelId, cls);
    }

    /**
     * 发送多条自定义通知(指定通知渠道)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     * @param channelId    渠道id
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void showMuch(Context context, String contentTitle, String contentText, String channelId, RemoteViews views, Class<?> cls) {
        show(context, contentTitle, contentText, views, ++notifyId, channelId, cls);
    }

    /**
     * 发送通知(设置默认:大图标/小图标/小标题/副标题/优先级/首次弹出文本)
     *
     * @param contentTitle 标题
     * @param contentText  内容
     * @param notifyId     通知栏id
     * @param channelId    设置渠道id
     * @param cls          意图类
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void show(Context context, String contentTitle, String contentText, RemoteViews views, int notifyId, String channelId, Class<?> cls) {
        show(context, 0, 0, contentTitle, null, contentText, NotificationManager.IMPORTANCE_DEFAULT, null, views, notifyId, channelId, cls);
    }

    /**
     * 发送通知
     *
     * @param largeIcon    大图标
     * @param smallIcon    小图标
     * @param contentTitle 标题
     * @param subText      小标题/副标题
     * @param contentText  内容
     * @param priority     优先级
     * @param ticker       通知首次弹出时,状态栏上显示的文本
     * @param notifyId     定义是否显示多条通知栏
     * @param cls          意图类
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static void show(Context context, int largeIcon,
                            int smallIcon, String contentTitle,
                            String subText, String contentText,
                            int priority, String ticker, RemoteViews view,
                            int notifyId, String channelId, Class<?> cls) {
        //flags
        // FLAG_ONE_SHOT:表示此PendingIntent只能使用一次的标志
        // FLAG_IMMUTABLE:指示创建的PendingIntent应该是不可变的标志
        // FLAG_NO_CREATE : 指示如果描述的PendingIntent尚不存在,则只返回null而不是创建它。
        // FLAG_CANCEL_CURRENT :指示如果所描述的PendingIntent已存在,则应在生成新的PendingIntent,取消之前PendingIntent
        // FLAG_UPDATE_CURRENT : 指示如果所描述的PendingIntent已存在,则保留它,但将其额外数据替换为此新Intent中的内容
        PendingIntent pendingIntent = null;
        // 添加隐示意图
        if (cls != null) {
            Intent intent = new Intent(context, cls);
            pendingIntent = PendingIntent.getActivity(context, RequestCode, intent, FLAG_UPDATE_CURRENT);
        }
        // 获取通知服务管理器
        NotificationManager manager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
        // 判断应用通知是否打开
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (!openNotificationChannel(context, manager, channelId)) {
                return;
            }
        }

        // 创建 NEW_MESSAGE 渠道通知栏  在API级别26.1.0中推荐使用此构造函数 Builder(context, 渠道名)
        NotificationCompat.Builder builder = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? new NotificationCompat.Builder(context, channelId) : new NotificationCompat.Builder(context);
        builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), largeIcon == 0 ? R.mipmap.app_icon : largeIcon)) // 设置自动收报机和通知中显示的大图标。
                .setSmallIcon(smallIcon == 0 ? R.mipmap.app_icon : smallIcon) // 小图标
                .setContentText(TextUtils.isEmpty(contentText) ? null : contentText) // 内容
                .setContentTitle(TextUtils.isEmpty(contentTitle) ? null : contentTitle) // 标题
                .setSubText(TextUtils.isEmpty(subText) ? null : subText) // APP名称的副标题
                .setPriority(priority) // 设置优先级 PRIORITY_DEFAULT
                .setTicker(TextUtils.isEmpty(ticker) ? Ticker : ticker) // 设置通知首次弹出时,状态栏上显示的文本
                .setContent(view)
                .setWhen(System.currentTimeMillis()) // 设置通知发送的时间戳
                .setShowWhen(true)// 设置是否显示时间戳
                .setAutoCancel(true)// 点击通知后通知在通知栏上消失
                .setDefaults(Notification.PRIORITY_HIGH)// 设置默认的提示音、振动方式、灯光等 使用的默认通知选项
                .setContentIntent(pendingIntent) // 设置通知的点击事件
                // 锁屏状态下显示通知图标及标题
                // 1、VISIBILITY_PUBLIC 在所有锁定屏幕上完整显示此通知
                // 2、VISIBILITY_PRIVATE 隐藏安全锁屏上的敏感或私人信息
                // 3、VISIBILITY_SECRET 不显示任何部分
                //.setVisibility(Notification.VISIBILITY_PRIVATE)// 部分手机没效果
                .setFullScreenIntent(pendingIntent, true)// 悬挂式通知8.0需手动打开
//                .setColorized(true)
//                .setGroupSummary(true)// 将此通知设置为一组通知的组摘要
//                .setGroup(NEW_GROUP)// 使用组密钥
//                .setDeleteIntent(pendingIntent)// 当用户直接从通知面板清除通知时 发送意图
//                .setFullScreenIntent(pendingIntent,true)
//                .setContentInfo("大文本")// 在通知的右侧设置大文本。
//                .setContent(RemoteViews RemoteView)// 设置自定义通知栏
//                .setColor(Color.parseColor("#ff0000"))
//                .setLights()//希望设备上的LED闪烁的argb值以及速率
//                .setTimeoutAfter(3000)//指定取消此通知的时间(如果尚未取消)。
        ;

        // 通知栏id
        manager.notify(notifyId, builder.build()); // build()方法需要的最低API为16 ,
    }

    /**
     * 判断应用渠道通知是否打开(适配8.0)
     *
     * @return true 打开
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static Boolean openNotificationChannel(Context context, NotificationManager manager, String channelId) {
        // 判断通知是否有打开
        if (!isNotificationEnabled(context)) {
            toNotifySetting(context, null);
            return false;
        }
        // 判断渠道通知是否打开
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = manager.getNotificationChannel(channelId);
            if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
                // 没打开调往设置界面
                toNotifySetting(context, channel.getId());
                return false;
            }
        }
        return true;
    }

    /**
     * 判断应用通知是否打开
     *
     * @return
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public static boolean isNotificationEnabled(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
            return notificationManagerCompat.areNotificationsEnabled();
        }
        AppOpsManager mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        Class appOpsClass = null;
        /* Context.APP_OPS_MANAGER */
        try {
            appOpsClass = Class.forName(AppOpsManager.class.getName());
            Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE, Integer.TYPE, String.class);
            Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
            int value = (Integer) opPostNotificationValue.get(Integer.class);
            return ((Integer) checkOpNoThrowMethod.invoke(mAppOps, value, context.getApplicationInfo().uid, context.getPackageName()) == AppOpsManager.MODE_ALLOWED);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 手动打开应用通知
     */
    public static void toNotifySetting(Context context, String channelId) {
        Intent intent = new Intent();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //适配 8.0及8.0以上(8.0需要先打开应用通知,再打开渠道通知)
            if (TextUtils.isEmpty(channelId)) {
                intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
                intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
            } else {
                intent.setAction(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
                intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
                intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
            }
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {// 适配 5.0及5.0以上
            intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
            intent.putExtra("app_package", context.getPackageName());
            intent.putExtra("app_uid", context.getApplicationInfo().uid);
        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {// 适配 4.4及4.4以上
            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setData(Uri.fromParts("package", context.getPackageName(), null));
        } else {
            intent.setAction(Settings.ACTION_SETTINGS);
        }
        context.startActivity(intent);
    }
}
复制代码

使用方法

  • 先初始化
  • 直接调用show方法
// 通知栏初始化(适配8.0)
QlNotificationUtil.setNotificationChannel(getContext());
// 发送同一个通知
QlNotificationUtil.show(getContext(),"收到一条聊天消息", "今天中午吃什么?",null);
// 发送多个通知
QlNotificationUtil.showMuch(getContext(),"收到一条订阅消息", "今天中午吃什么?",null);
复制代码

好啦,今天的介绍就到这里啦~

猜你喜欢

转载自juejin.im/post/7031390878174281735