Android开发通知栏的那些事

对于通知栏的使用,Android各个版本其实都有比较大的调整。例如老版本的不兼容,大小图标问题以及自定义通知栏适配问题,这些都是比较头大的事,当然弄懂了就清楚了,本篇就处理下这些疑惑。

通知栏的使用

显示一个普通的通知栏

public static void showNotification(Context context) {
    Notification notification = new NotificationCompat.Builder(context)
            /**设置通知左边的大图标**/
            .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
            /**设置通知右边的小图标**/
            .setSmallIcon(R.mipmap.ic_launcher)
            /**通知首次出现在通知栏,带上升动画效果的**/
            .setTicker("通知来了")
            /**设置通知的标题**/
            .setContentTitle("这是一个通知的标题")
            /**设置通知的内容**/
            .setContentText("这是一个通知的内容这是一个通知的内容")
            /**通知产生的时间,会在通知信息里显示**/
            .setWhen(System.currentTimeMillis())
            /**设置该通知优先级**/
            .setPriority(Notification.PRIORITY_DEFAULT)
            /**设置这个标志当用户单击面板就可以让通知将自动取消**/
            .setAutoCancel(true)
            /**设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)**/
            .setOngoing(false)
            /**向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用defaults属性,可以组合:**/
            .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND)
            .setContentIntent(PendingIntent.getActivity(context, 1, new Intent(context, MainActivity.class), PendingIntent.FLAG_CANCEL_CURRENT))
            .build();
    NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
    /**发起通知**/
    notificationManager.notify(0, notification);
}

显示一个下载带进度条的通知

public static void showNotificationProgress(Context context) {
    //进度条通知
    final NotificationCompat.Builder builderProgress = new NotificationCompat.Builder(context);
    builderProgress.setContentTitle("下载中");
    builderProgress.setSmallIcon(R.mipmap.ic_launcher);
    builderProgress.setTicker("进度条通知");
    builderProgress.setProgress(100, 0, false);
    final Notification notification = builderProgress.build();
    final NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
    //发送一个通知
    notificationManager.notify(2, notification);
    /**创建一个计时器,模拟下载进度**/
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        int progress = 0;

        @Override
        public void run() {
            Log.i("progress", progress + "");
            while (progress <= 100) {
                progress++;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                //更新进度条
                builderProgress.setProgress(100, progress, false);
                //再次通知
                notificationManager.notify(2, builderProgress.build());
            }
            //计时器退出
            this.cancel();
            //进度条退出
            notificationManager.cancel(2);
            return;//结束方法
        }
    }, 0);
}

显示一个悬挂式的通知

悬挂式,部分系统厂商可能不支持。

public static void showFullScreen(Context context) {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
    Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://blog.csdn.net/linglongxin24"));
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mIntent, 0);
    builder.setContentIntent(pendingIntent);
    builder.setSmallIcon(R.mipmap.ic_launcher);
    builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher));
    builder.setAutoCancel(true);
    builder.setContentTitle("悬挂式通知");
    //设置点击跳转
    Intent hangIntent = new Intent();
    hangIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    hangIntent.setClass(context, MainActivity.class);
    //如果描述的PendingIntent已经存在,则在产生新的Intent之前会先取消掉当前的
    PendingIntent hangPendingIntent = PendingIntent.getActivity(context, 0, hangIntent, PendingIntent.FLAG_CANCEL_CURRENT);
    builder.setFullScreenIntent(hangPendingIntent, true);
    NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
    notificationManager.notify(3, builder.build());
}

显示一个折叠式的通知

public static void shwoNotify(Context context) {
    //先设定RemoteViews
    RemoteViews view_custom = new RemoteViews(context.getPackageName(), R.layout.view_custom);
    //设置对应IMAGEVIEW的ID的资源图片
    view_custom.setImageViewResource(R.id.custom_icon, R.mipmap.icon);
    view_custom.setTextViewText(R.id.tv_custom_title, "今日头条");
    view_custom.setTextColor(R.id.tv_custom_title, Color.BLACK);
    view_custom.setTextViewText(R.id.tv_custom_content, "金州勇士官方宣布球队已经解雇了主帅马克-杰克逊,随后宣布了最后的结果。");
    view_custom.setTextColor(R.id.tv_custom_content, Color.BLACK);
    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);
    mBuilder.setContent(view_custom)
            .setContentIntent(PendingIntent.getActivity(context, 4, new Intent(context, MainActivity.class), PendingIntent.FLAG_CANCEL_CURRENT))
            .setWhen(System.currentTimeMillis())// 通知产生的时间,会在通知信息里显示
            .setTicker("有新资讯")
            .setPriority(Notification.PRIORITY_HIGH)// 设置该通知优先级
            .setOngoing(false)//不是正在进行的   true为正在进行  效果和.flag一样
            .setSmallIcon(R.mipmap.icon);
    Notification notify = mBuilder.build();
    NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
    notificationManager.notify(4, notify);
}

低版本不兼容处理

Android在appcompat-v7库中提供了一个NotificationCompat类来处理新老版本的兼容问题,我们在编写通知功能时都使用NotificationCompat这个类来实现,appcompat-v7库就会自动帮我们做好所有系统版本的兼容性处理了。一段基本的触发通知代码如下所示:

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
Notification notification = builder
.setContentTitle("这是通知标题")
.setContentText("这是通知内容")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.build();
manager.notify(1, notification);

现在我们的app直接面对的设备一般都在android 5.0以上,所以也不需要做这种处理了。

大小图标问题

注意看一下我们给通知设置的图标,一个小图标、一个大图标,都是使用的R.mipmap.ic_launcher这张图,这在较低的编译版本上是没问题的,如果将targetSdkVersion指定成21或者更高的话,那么小图标则不可见(通知栏和大图的右下角有一个白白的圆),导致界面很不友好。

这到底是为什么呢?实际上,Android从5.0系统开始,对于通知栏图标的设计进行了修改。现在Google要求,所有应用程序的通知栏图标,应该只使用alpha图层来进行绘制,而不应该包括RGB图层(通俗点来讲,就是让我们的通知栏图标不要带颜色就可以了)。下边是支付宝和网易新闻的展示:
支付宝通知状态栏小图标展示
支付宝通知样式
网易新闻通知样式

上图你会发现网易的图标更好看一些,因为系统给右下角的这个小圆圈默认是设置成灰色的,和我们的整体色调并不搭配,而网易则将这个小圆圈改成了红色,因此总体视觉效果更好。这种也很好处理,只需要在NotificationCompat.Builder中再多连缀一个setColor()方法就可以了:

Notification notification = builder
    ......
    .setColor(Color.parseColor("#EAA935"))
    .build();

自定义通知栏

自定义通知需要定义一个layout文件,使用RemoteViews加载它并设置一些点击事件,再设置到builder,如下:

public void showNotification(){
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    builder.setSmallIcon(R.mipmap.small_launch_ic);
    
    //自定义布局
    RemoteViews rv = new RemoteViews(getPackageName(),R.layout.message);
    rv.setTextViewText(R.id.tv,"有新通知了");
    builder.setContent(rv);

    //点击跳转
    Intent intent = new Intent(this, MainAct.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    remoteViews.setOnClickPendingIntent(R.id.root, pendingIntent);

    Notification notification = builder.build();
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(NOTIFICATION_ID,notification);
}

认识RemoteViews

RemoteViews主要用在通知栏和桌面小部件上,简单来说RemoteViews是一个可以跨进程显示view的类,显示的view是从布局文件inflate出来,且该类提供了一些基本的方法来修改这个view的内容。

RemoteViews并不是一个view, 但可以表示一个layout的布局;又因为是继承parcelable,所以可以跨进程使用,但因为是跨进程,所以没办法像我们之前通过findviewById方法来访问布局里的每个view,所以RemoteViews提供了一些set方法来更新view 的显示,RemoteViews可以支持大部分系统控件,但是不支持自定义控件。

原理

自定义通知栏和桌面小部件,是由NotificationManager和AppWidgetmanager管理,而NotificationManager和AppWidgetManager是通过Binder分别和SystemServer进程中的NotificationManagerServer以及AppWidgetService进行通信,他们是运行在系统进程中,即SystemServer进程, 而我们是要在自身的应用进程中来更新远程系统进程的UI。这样就构成来跨进程通信的场景。 最开始的一节我们知道RemoteViews 是实现了Parcelable接口的,这样就可以跨进程使用了。从构造方法开始,系统首先根据包名去得到该应用的资源,然后inflate出布局文件,在SystemServer进程中是一个普通的view,而在我们的进程看来这是一个RemoteViews,然后会通过一系列set方法来更新该RemoteViews。

认识PendingIntent

所谓的 PendingIntent 是区别于 Intent 而存在的。Intent(即意图)是立即发生的,而 PendingIntent 是在将来的某个时刻发生的。PendIntent其实是Intent的封装。

PendingIntent的使用场景主要用于闹钟、通知、桌面部件。

与Intent的区别

  • Intent 是意图的意思。Android 中的 Intent 正是取自这个意思,它是一个消息对象,通过它,Android 系统的四大组件能够方便的通信,并且保证解耦。Intent 可以说明某种意图,携带一种行为和相应的数据,发送到目标组件。
  • PendingIntent是对Intent的封装,但它不是立刻执行某个行为,而是满足某些条件或触发某些事件后才执行指定的行为。

我们的 Activity 如果设置了 exported = false,其他应用如果使用 Intent 就访问不到这个 Activity,但是使用 PendingIntent 是可以的。

即:PendingIntent将某个动作的触发时机交给其他应用;让那个应用代表自己去执行那个动作(权限都给他)

获取PendingIntent

关于PendingIntent的实例获取一般有以下五种方法,分别对应Activity、Broadcast、Service:

  • getActivity()
  • getActivities()
  • getBroadcast()
  • getService()
  • getForegroundService()

它们的参数都相同,都是四个:Context, requestCode, Intent, flags,分别对应上下文对象、请求码、请求意图用以指明启动类及数据传递、关键标志位。前面三个参数共同标志一个行为的唯一性。

PendingIntent的FLAG

  • FLAG_CANCEL_CURRENT:如果当前系统中已经存在一个相同的PendingIntent对象,那么就将先将已有的PendingIntent取消,然后重新生成一个PendingIntent对象。
  • FLAG_NO_CREATE:如果当前系统中不存在相同的PendingIntent对象,系统将不会创建该PendingIntent对象而是直接返回null,如果之前设置过,这次就能获取到。
  • FLAG_ONE_SHOT:该PendingIntent只作用一次。在该PendingIntent对象通过send()方法触发过后,PendingIntent将自动调用cancel()进行销毁,那么如果你再调用send()方法的话,系统将会返回一个SendIntentException。
  • FLAG_UPDATE_CURRENT:如果系统中有一个和你描述的PendingIntent对等的PendingInent,那么系统将使用该PendingIntent对象,但是会使用新的Intent来更新之前PendingIntent中的Intent对象数据,例如更新Intent中的Extras

8.0通知栏新增通知渠道

Android 8.0 系统,Google引入通知渠道,提高用户体验,方便用户管理通知信息,同时也提高了通知到达率

什么是通知渠道呢?顾名思义,就是每条通知都要属于一个对应的渠道。每个App都可以自由地创建当前App拥有哪些通知渠道,但是这些通知渠道的控制权都是掌握在用户手上的。用户可以自由地选择这些通知渠道的重要程度,是否响铃、是否振动、或者是否要关闭这个渠道的通知。

build.gradle中targetSdkVersion设置大于等于26。这时如果不对通知渠道适配,通知就无法显示。

所以我们要额外处理:

1.创建NotificationChannel对象,指定Channel的id、name和通知的重要程度

2.使用NotificationMannager的createNotificationChannel方法来添加Channel。

    private NotificationCompat.Builder getNotificationBuilder() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("channel_id", "channel_name",
                    NotificationManager.IMPORTANCE_DEFAULT);
            //是否绕过请勿打扰模式
            channel.canBypassDnd();
            //闪光灯
            channel.enableLights(true);
            //锁屏显示通知
            channel.setLockscreenVisibility(VISIBILITY_SECRET);
            //闪关灯的灯光颜色
            channel.setLightColor(Color.RED);
            //桌面launcher的消息角标
            channel.canShowBadge();
            //是否允许震动
            channel.enableVibration(true);
            //获取系统通知响铃声音的配置
            channel.getAudioAttributes();
            //获取通知取到组
            channel.getGroup();
            //设置可绕过  请勿打扰模式
            channel.setBypassDnd(true);
            //设置震动模式
            channel.setVibrationPattern(new long[]{100, 100, 200});
            //是否会有灯光
            channel.shouldShowLights();
            getNotificationManager().createNotificationChannel(channel);
        }
        NotificationCompat.Builder notification = new NotificationCompat.Builder(this, "channel_id");
        notification.setContentTitle("新消息来了");
        notification.setContentText("周末到了,不用上班了");
        notification.setSmallIcon(R.mipmap.ic_launcher);
        notification.setAutoCancel(true);
        return notification;
    }

3.设置通知重要性级别

Android 8.0 及以上是使用NotificationManager.IMPORTANCE_,Android 7.1 及以下是使用NotificationCompat.PRIORITY_它们都是定义的常量:
Android开发通知栏的那些事_第1张图片

总结

以上是我对通知栏相关使用或自定义方式的总结,这块也很简单,重点关注是RemoteViews和PendingIntent的知识点的认识和理解。

你可能感兴趣的:(Android)