前言

在学习《第一行代码》的Notification的时候,代码写好了发现无法打开通知窗口,想着可能是权限的问题,但是书中没有提到通知需要用到权限,所以就去网上查找,最后找到 郭霖大佬(《第一行代码》的作者)的一篇博客,学习一下:Android通知栏微技巧,8.0系统中通知栏的适配

正文

Android 8.0之后出了通知适配这个概念,每个App可以自由地将通知分类(称之为通知渠道),而用户可以选择屏蔽掉某一类通知,这样既可以使得开发者多多思考哪些通知用户可能很重视,哪些可能并不想被弹出通知,又可以让用户自由选择接收来自一个app的通知。而不是像以前一样,要么全部接收,要么全部屏蔽。

注意:如果你将项目中的targetSdkVersion指定到了26或者更高,那么Android系统就会认为你的App已经做好了8.0系统的适配工作,当然包括了通知栏的适配。这个时候如果还不使用通知渠道的话,那么你的App的通知将完全无法弹出。

进入正题。

(1)建立通知渠道
我将建立通知渠道的代码封装成一个函数,先上代码再解释:

void createMyNotificationChannel(){
        //判断当前SDK版本大于26(Build.VERSION_CODES.O == 26)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            //建立一个通知渠道需要三个参数(id, name(给用户看的), 优先级)
            String channelId1 = "chat";
            String channelName1 = "聊天消息";
            int importance1 = NotificationManager.IMPORTANCE_HIGH;
            NotificationChannel channel1 = new NotificationChannel(channelId1,channelName1,importance1);

            //实际上可以一条代码新建NotificationChannel
            NotificationChannel channel2 = new NotificationChannel("subscribe","订阅消息",NotificationManager.IMPORTANCE_DEFAULT);

            //通过NotificationManager.createNotificationChannel(channel)方法建立通知渠道
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            manager.createNotificationChannel(channel1);
            manager.createNotificationChannel(channel2);
        }
    }

解释:在当前App的SDK版本大于26的情况下,建立你想要的通知渠道(必须检查sdk版本,因为在26以下的版本里没有通知渠道的概念,强行执行会崩溃)。参数(id, name, importance);id用来建立Notification时指定渠道,name是显示给用户看的(之后会说到),importance是优先级,有以下几种:int型,数字越大优先级越高,弹出通知的方式也不太一样,具体可以自行尝试。

Android state_enabled 无效 androidadaptivenotifications_App


建立通知渠道的函数一般放在onCreate()方法中,只要在发出Notification之前被执行即可。而且只会被执行一次,之后系统再次执行到这段代码的时候会检查此App是否已经有通知渠道了,有的话就会忽略这段代码,因此不会重复创建,也不会对性能造成影响。(2)用户管理通知渠道

正文一开始说到的,通知渠道是可以被用户自主管理的,那么怎么管理呢?

如下图,用户可以在设置->应用->通知里看到某个App创建的通知渠道,以及每个渠道对应的通知方式,这个是跟importance优先级有关的(比如“聊天消息”是发出声音并且弹出到屏幕上,而“订阅消息”只是发出声音),并可以选择关闭/打开通知渠道。(参数name也就是用在了这里)

Android state_enabled 无效 androidadaptivenotifications_App_02

(3)发送通知
创建了通知渠道后,发送通知并没有变得多复杂,其实基本和以前一样,只是在NotificationCompat.Builder(this, id)这个方法里面多加入一个参数id(就是通知渠道的id),这样就可以轻松指定这个Notification属于哪个渠道了。代码如下:

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(MainActivity.this,"chat");   //这里多了一个参数"chat",代表这条消息属于chat渠道
builder.setContentTitle("This is content title")
        .setContentText("This is a Notification")
        .setSmallIcon(R.mipmap.ic_launcher)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
        .setWhen(System.currentTimeMillis());

manager.notify(1,builder.build());

小插曲:在Android Studio的虚拟机(Android 8.0系统)上创建渠道然后发出通知后,系统界面崩溃,无限闪屏,提示“System UI has stopped”。查找原因得知是Android 8.0系统bug,解决方案看链接:来自简书用户“巧阿”的解决方案 我采用的解决办法是:删掉崩溃的虚拟机新建一个,然后删掉res/mipmap-anydpi-v26自适应图标文件夹。

最终运行效果如下(代码去掉通知使用:NotificationManger.cancel(id) ):

Android state_enabled 无效 androidadaptivenotifications_App_03


(4)开发者管理通知渠道

Android赋予了开发者读取通知渠道配置的权限,如果我们的某个功能是必须按照指定要求来配置通知渠道才能使用的,那么就可以提示用户去手动更改通知渠道配置(也就是说开发者不能自行更改渠道的配置,这个权力掌握在用户手中,很合理,这也是引入通知渠道的目的之一:防止开发者滥用通知权限)。

比如上例中,用户将chat渠道关闭了(关闭了的意思就是chat渠道的importance改成了IMPORTANCE_NONE),那么我们要检测到这一点,然后发出提示。那么具体怎么做呢?

首先还是要检查SDK版本大于26,因为读取通知渠道配置的API也是26以上才有的;通过NotificationManger.getNotificationChannel(id)得到某个渠道的实例channel,判断channel的优先级进行操作,具体看代码:

/*此操作一般是在发出通知前做*/
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = manager.getNotificationChannel("chat");   //得到channel实例
            if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {  //判断优先级
            	
            	//Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS是设置->通知
                Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
                //需要的两个数值
                intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
                intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
                //跳转到(2)中的界面并Toast提示
                startActivity(intent);
                Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show();
            }
        }
}

这样,如果在发出通知前发现某个重要的通知渠道被用户关闭了,就能够跳转到(2)中的设置界面,并弹出Toast提示用户手动打开该渠道。从而达到管理通知渠道的目的(不过开不开渠道的权力还是在用户手上)。