1.概述

    当应用程序在后台运行,希望向用户发出一些提示学习,就需要借助Notification(通知)来实现。在发出一条通知后,手机最上方的状态栏会显示一个通知的图标,下拉状态栏后就可以看到通知的详细内容。

1.1 视图分类

Notification有两种视觉风格,一种是标准视图(Normal View),另外一种是大视图(Big view)。标准视图在Android中各版本是通用的,但是对于大视图而言,仅支持Android4.1+的版本。 

1.2 标准视图

     从官方文档了解到,一个标准视图显示的大小保持在64dp高。如下图所示:

android 通知怎么搞 android通知消息_android

     其属性描述如下:

1代表: 通知标题 
2代表: 大图标 
3代表:  通知内容 
4代表: 通知消息 
5代表:小图标 
6代表: 通知时间,一般为系统时间,也可以使用setWhen()设置。

1.3 大视图

对于大视图(Big View)而言,它的细节区域只能显示256dp高度的内容,并且只对Android4.1+之后的设备才支持,它比标准视图不一样的地方,均需要使用setStyle()方法设定,它大致的效果如下:

android 通知怎么搞 android通知消息_android_02

Android为我们提供了三个实现类,用于显示不同的场景。分别是: 
Notification.BigPictureStyle, 在细节部分显示一个256dp高度的位图。 
Notification.BigTextStyle,在细节部分显示一个大的文本块。 
Notification.InboxStyle,在细节部分显示一段行文本

Notification.FLAG_SHOW_LIGHTS //三色灯提醒,在使用三色灯提醒时候必须加该标志符 
Notification.FLAG_ONGOING_EVENT //发起正在运行事件(活动中) 
Notification.FLAG_INSISTENT //让声音、振动无限循环,直到用户响应 (取消或者打开) 
Notification.FLAG_ONLY_ALERT_ONCE //发起Notification后,铃声和震动均只执行一次 
Notification.FLAG_AUTO_CANCEL //用户单击通知后自动消失 
Notification.FLAG_NO_CLEAR //只有全部清除时,Notification才会清除 ,不清楚该通知(QQ的通知无法清除,就是用的这个。还有百度通知栏里面的搜索框也是这个)。 
使用方法:在设置完属性后,设置 
Notification notification = builder.build(); 
notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
--------------------------------

2.基本用法

    通知可以在活动里面创建,也可以在广播接收器里面创建,也可以在服务里面创建。下面介绍创建通知的步骤:

2.1 创建一个NotificationManager

      创建一个NotificationManager来对通知进行管理。通过调用Context.getSystemService(String s)方法获取到NotificationManager实例对象,字符串s参数用于确定获取系统的哪个服务,这里传入Context.NOTIFICATION_SERVICE即可,如下所示:

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

     其中,Context.NOTIFICATION_SERVICE的值为:notification

public abstract class Context { ...... public static final String NOTIFICATION_SERVICE = "notification"; }

 

2.2 使用Builder构造器来创建Notification对象

      在这里需要注意的是,为了解决API不稳定性问题和新老版本的兼容问题,使用support-v4提供的NotificationCompat类的Builder构造器来创建Notification对象,可以保证程序在所有的版本上都能正常工作。同时,Android8.0开始,废弃了Builder(@NonNull Context context)方法,改用public Builder(@NonNull Context context, @NonNull String channelId),如下所示:

//创建Notification,传入Context和channelId Notification notification = new NotificationCompat.Builder(this, "chat") .setAutoCancel(true) .setContentTitle("收到聊天消息") .setContentText("今天晚上吃什么") .setWhen(System.currentTimeMillis()) .setSmallIcon(R.mipmap.ic_launcher) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setContentIntent(pendingIntent) //在build()方法之前还可以添加其他方法 .build();

      Builder构造器其中一些方法说明如下:

setAutoCancel(boolean boolean)设置点击通知后自动清除通知setContent(RemoteView view)设置自定义通知setContentTitle(String string)设置通知的标题内容setContentText(String string)设置通知的正文内容setContentIntent(PendingIntent intent)设置点击通知后的跳转意图setWhen(long when)设置通知被创建的时间setSmallIcon(int icon)

设置通知的小图标

注意:只能使用纯alpha图层的图片进行设置,小图标会显示在系统状态栏上

setLargeIcon(Bitmap icon)

设置通知的大图标

下拉系统状态栏时就能看见

setPriority(int pri)设置通知的重要程度setStyle(Style style)

设置通知的样式

比如设置长文字、大图片等等

setVisibility(int defaults)设置默认setLight(int argb, int onMs, int offMs)设置呼吸闪烁效果setSound(Uri sound)设置通知音效setVibrate(long[] pattern)

设置震动效果,数组包含手机静止时长和震动时长

下标0代表手机静止时长

下标1代表手机整的时长

下标2代表手机静止时长

下标3,4,5.......以此类推

还需要在AndroidManifest.xml中声明权限:

<uses-permission android:name="android.permission.VIBRATE"/>

setColor(int argb)设置通知栏颜色setCategory(String category)设置通知类别setFullScreenIntent(PendingIntent intent, boolean b)设置弹窗显示

    其中setPriority(int pri)方法参数一共有5个常量值可选,调用NotificationCompat的常量值,如下所示:

public class NotificationCompat { ....... //默认的重要程度,和不设置效果是一样的 public static final int PRIORITY_DEFAULT = 0; //最低的重要程度,系统可能只会在特定的场合显示这条通知 public static final int PRIORITY_MIN = -2; //较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序 public static final int PRIORITY_LOW = -1; //较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序 public static final int PRIORITY_HIGH = 1; //最高的重要程度,表示这类通知消息必须让用户看到,甚至做出响应 public static final int PRIORITY_MAX = 2; }

注意:当设置最高重要程度后,其显示效果和QQ发送好友消息一样,如果正在其他APP内,消息会显示在屏幕上让用户看见

2.3 调用NotificationManager的notify(int id, Notification notification)让通知显示

        notify()方法接收两个参数,其中id表示每个通知所指定的id,要不一样。代码如下:

notificationManager.notify(1, notification);

使用此方法前,需要将NotificationChannel(通知渠道创建出来)

2.4 让通知从状态栏消失方法

setAutoCancel(true),另外一种就是通过动态代码方式,NotificationManager.cancel(int id),如下:

//传入对应通知的id notificationManager.cancel(1);

 

3.NotificationChannel(通知渠道)

3.1 概述

       从Android 8.0系统开始,Google引入了通知渠道这个概念。

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

      即NotificationChannel 其实是把 Notification 分了个类别,设置不同优先级,开关之类的。如果你的 app 适配了的话,用户可以关掉不喜欢的通知,以提高用户体验。

垃圾推送消息的打扰了,因为用户可以自主地选择自己关心哪些通知、不关心哪些通知。举个具体的例子,我希望可以即时收到支付宝的收款信息,因为我不想错过任何一笔收益,但是我又不想收到支付宝给我推荐的周围美食,因为我没钱只吃得起公司食堂。这种情况,支付宝就可以创建两种通知渠道,一个收支,一个推荐,而我作为用户对推荐类的通知不感兴趣,那么我就可以直接将推荐通知渠道关闭,这样既不影响我关心的通知,又不会让那些我不关心的通知来打扰我了。

      对于每个App来说,通知渠道的划分是非常需要仔细考究的,因为通知渠道一旦创建之后就不能再修改了,因此开发者需要仔细分析自己的App一共有哪些类型的通知,然后再去创建相应的通知渠道。

      这里我们来参考一下Twitter的通知渠道划分,如下所示:

android 通知怎么搞 android通知消息_android_03

     可以看到,Twitter就是根据自己的通知类型,对通知渠道进行了非常详细的划分,这样用户的自主选择性就比较高了,也就大大降低了用户不堪其垃圾通知的骚扰而将App卸载的概率。
--------------------- 

3.2 创建NotificationChannel

代码如下所示:

public class NotificationUtil {
    private static final int NOTIFICATION_MUSIC_ID = 10000;
    private static NotificationManager notificationManager;
    ......
    
    //初始化NotificationManager
    private static void initNotificationManager(Context context){
        if (notificationManager == null){
            notificationManager = (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);

        }
        //判断是否为8.0以上:Build.VERSION_CODES.O为26
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //创建通知渠道ID
            String channelId = "musicNotification";
            //创建通知渠道名称
            String channelName = "音乐播放器通知栏";
            //创建通知渠道重要性
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            createNotificationChannel(context, channelId, channelName, importance);
        }
    }

    //创建通知渠道
    @TargetApi(Build.VERSION_CODES.O)
    private static void createNotificationChannel(Context context, String channelId, String channelName, int importance) {
        NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
        //channel有很多set方法
        ......
        //为NotificationManager设置通知渠道
        notificationManager.createNotificationChannel(channel);
    }


}

说明:这里传入的channelId要和创建的通知channelId一致,才能为指定通知建立通知渠道

1)NotificationChannel的方法列表

getId()获取ChannelIdenableLights(boolean boolean)是否开启指示灯(是否在桌面icon右上角展示小红点)setLightColor()设置指示灯颜色enableVibration()是否开启整的setVibrationPattern() 设置震动频率setImportance()设置频道重要性getImportance() 获取频道重要性setSound() 设置声音getSound() 获取声音setGroup()设置 ChannleGroupgetGroup() 得到 ChannleGroupsetBypassDnd() 设置绕过免打扰模式canBypassDnd() 检测是否绕过免打扰模式getName() 获取通知渠道名称setLockScreenVisibility() 设置是否应在锁定屏幕上显示此频道的通知getLockscreenVisibility() 检测是否应在锁定屏幕上显示此频道的通知setShowBadge()设置是否显示角标canShowBadge() 检测是否显示角标

2)重要程度

     数值越高,提示权限就越高,最高的支持发出声音和悬浮通知,如下所示:

public class NotificationManager {
    ......
    public static final int IMPORTANCE_DEFAULT = 3;
    public static final int IMPORTANCE_HIGH = 4;
    public static final int IMPORTANCE_LOW = 2;
    public static final int IMPORTANCE_MAX = 5;
    public static final int IMPORTANCE_MIN = 1;
    public static final int IMPORTANCE_NONE = 0;
    public static final int IMPORTANCE_UNSPECIFIED = -1000;

}


3)发出通知

     调用NotificationManager的notify()方法即可

4)删除NotificationChann

int chatChannelId)即可。

4.案例

1)xml布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        android:id="@+id/chat"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="发送聊天通知"/>
</LinearLayout>

2)代码

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private NotificationManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

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

        Button chat = findViewById(R.id.chat);
        chat.setOnClickListener(this);
        Button get = findViewById(R.id.get);
        get.setOnClickListener(this);
    }

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

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.chat:    //聊天消息
                Notification notification = new NotificationCompat.Builder(this, "chat")
                        .setAutoCancel(true)
                        .setContentTitle("收到聊天消息")
                        .setContentText("今天晚上吃什么")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        //设置红色
                        .setColor(Color.parseColor("#F00606"))
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pendingIntent)
                        .build();
                manager.notify(1, notification);
                break;
            case R.id.get:   //订阅消息
                Notification notificationGet = new NotificationCompat.Builder(this, "subscribe")
                        .setAutoCancel(true)
                        .setContentTitle("收到订阅消息")
                        .setContentText("新闻消息")
                        .setWhen(System.currentTimeMillis())
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                        .setContentIntent(pendingIntentGet)
                        .build();
                manager.notify(2, notificationGet);
                break;
        }
    }
}


3)效果

android 通知怎么搞 android通知消息_xml_04

         

android 通知怎么搞 android通知消息_android 通知怎么搞_05

     注意这里出现了大图标和小图标,聊天消息之所以是红色,因为setColor为红色,详情见:


5.使用RemoteViews自定义Notification

        需要使用RemoteViews.RemoteViews描述了一个视图层次的结构,可以显示在另一个进程。

        RemoteViews提供了多个构造函数,一般使用RemoteViews(String packageName,int layoutId)。第一个参数为包的名称,第二个为layout资源的Id。当获取到RemoteViews对象之后,可以使用它的一系列setXxx()方法通过控件的Id设置控件的属性。最后使用NotificationCompat.Builder.setContent(RemoteViews)方法设置它到一个Notification中。 
 

6.PendingIntent

6.1 概述

         对于一个通知而言,它显示的消息是有限的,一般仅用于提示一些概要信息。但是一般简短的消息,并不能表达需要告诉用户的全部内容,所以需要绑定一个意图,当用户点击通知的时候,调用一个意图展示出一个Activity用来显示详细的内容。而Notification中,并不使用常规的Intent去传递一个意图,而是使用PendingIntent。 
        Intent和PendingIntent的区别:PendingIntent可以看做是对Intent的包装,通过名称可以看出PendingIntent用于处理即将发生的意图,而Intent用来用来处理马上发生的意图。而对于通知来说,它是一系统级的全局通知,并不确定这个意图被执行的时间。当在应用外部执行PendingIntent时,因为它保存了触发应用的Context,使得外部应用可以如在当前应用中一样,执行PendingIntent里的Intent,就算执行的时候响应通知的应用已经被销毁了,也可以通过存在PendingIntent里的Context照常执行它,并且还可以处理Intent说带来的额外信息。 

      因此可以将PendingIntent看做是延迟执行的Intent。

6.2 创建PendingIntent

     获取PendingInten实例可以根据需求从如下方法中获取:

PendingInteng.getBroadcast(contex, requestCode, intent, flags) 
PendingInteng.getService(contex, requestCode, intent, flags) 
PendingInteng.getActivity(contex, requestCode, intent, flags) 
PendingInteng.getActivities(contex, requestCode, intent, flags) 

其中flags属性参数用于确定PendingIntent的行为: 
FLAG_ONE_SHOT: 表示返回的PendingIntent仅能执行一次,执行完后自动消失 
FLAG_NO_CREATE: 表示如果描述的PendingIntent不存在,并不创建相应的PendingIntent,而是返回NULL 
FLAG_CANCEL_CURRENT: 表示相应的PendingIntent已经存在,则取消前者,然后创建新的PendingIntent 
FLAG_UPDATE_CURRENT: 表示更新的PendingIntent,如果构建的PendingIntent已经存在,则替换它,常用。
setContentIntent(PendingIntent intent)方法,构建一个PendingIntent。

--------------------- 

6.3 使用案例

这里对目录4案例的代码进行改造,在代码中添加如下方法:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private NotificationManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ......
    }

    @TargetApi(Build.VERSION_CODES.O)
    private void createNotificationChannel(String channelId, String channelName, int importance) {
       ......
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.chat:
                Intent intent = new Intent(this, NotificationActivity.class);
                PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

                Notification notification = new NotificationCompat.Builder(this, "chat")
                        ......
                        .setContentIntent(pendingIntent)
                        .build();
                manager.notify(1, notification);
                break;

            case R.id.get:
                Intent intentGet = new Intent(this, NotificationActivity.class);
                PendingIntent pendingIntentGet = PendingIntent.getActivity(this, 0, intentGet, 0);

                Notification notificationGet = new NotificationCompat.Builder(this, "subscribe")
                        ......
                        .setContentIntent(pendingIntentGet)
                        .build();
                manager.notify(2, notificationGet);
                break;
        }
    }
}


       点击通知消息后,就可以跳转到NotificationActivity对应的界面了