阅读 891

oreo上的notification详解

前言

本来只想简单写下oreo上通知的变更,在收集资料时觉得不妨完整的梳理一遍,毕竟说起来P都要来了,现在整理好了,到时候把P的变更加上就行了,逐渐迭代更新也方便更好的使用。下面主要是对官方文档的概述,方便查阅。oreo上的notification详解(二)的内容更贴近实际。

1 概述

官方介绍 其实官方的说明最准确,但实际操作中可能会有些不清楚的地方,就像看一个机器的使用说明书总有些地方怪怪的,非得自己尝试才清楚。下面就按照链接的顺序结合实例一一说明。

2 使用

2.1 基本用法

2.1.1 创建通知

notification三大件:Notification.Builder 、 Notification、NotificationManager
1 Notification.Builder:使用建造者模式构建 Notification 对象。在目标API26(oreo)以下,只需要设置三个基本属性: 1.小图标,由 setSmallIcon() 设置
2.标题,由 setContentTitle() 设置
3.详细文本,由 setContentText() 设置
在目标版本API26(oreo)及以上需要设置channelId,否则会弹出toast错误提示,并且通知不会发出

//string PRIMARY_CHANNEL = “yourChannel” 是开发着定义的 ChannelId
/*NotificationCompat.Builder nb = new NotificationCompat.Builder(this)
                                      .setContentTitle(title)
                                      .setContentText(body)
                                      .setSmallIcon(getSmallIcon())
                                      .setChannelId(PRIMARY_CHANNEL)*/ //第一种设置法
Notification.Builder nb = Notification.Builder(getApplicationContext(), PRIMARY_CHANNEL) //第二种设置法
                                      .setContentTitle(title)
                                      .setContentText(body)
                                      .setSmallIcon(getSmallIcon())
                                      
//标准写法
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle(textTitle)
        .setContentText(textContent)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT);//The priority determines how intrusive the notification should be on Android 7.1 and lower. (For Android 8.0 and higher, you must instead set the channel importance—shown in the next section.)
复制代码

Notification.Builder 仅支持 Android 4.1及之后的版本,Google 在 Android Support v4 中加入了 NotificationCompat.Builder 类解决兼容性问题。所以在以后的开发中,使用第三个标准写法就好了。

2 Notification: 通知对应类,保存通知相关的数据。NotificationManager 向系统发送通知时会用到。在上面就是nb。

3 NotificationManager:NotificationManager 是通知管理类,它是一个系统服务。调用 NotificationManager 的 notify() 方法可以向系统发送通知。Class to notify the user of events that happen. This is how you tell the user that something has happened in the background.

 NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);//(2)
 Notification notification = nb.build();//(3)
 nm.notify(NOTIFICATION_ID, notification);//(4)
复制代码

可以说一个最基本的通知,就三行。上述(1)(2)(3)(4)

2.1.2 优先级

setPriority() 来设置优先级,不过在新版本上已经被弃用啦,但是使用兼容性在API26上还是可以使用。具体的等级有和通知渠道对应,会在后面的通知渠道再具体介绍。

@deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.
复制代码

2.1.3 兼容性

现在都向高版本适配,但为了兼顾低版本设备,以下摘自官方需要的时候看下。
为了确保最佳兼容性,请使用 NotificationCompat 及其子类(特别是 NotificationCompat.Builder)创建通知。此外,在实现通知时,请遵循以下流程:

1.为所有用户提供通知的全部功能,无论他们使用何种版本的 Android 系统。 为此,请验证是否可从应用的 Activity 中获得所有功能。要执行此操作,您可能需要添加新的 Activity。 例如,若要使用 addAction() 提供停止和启动媒体播放的控件,请先在应用的 Activity 中实现此控件。

2.确保所有用户均可通过点击通知启动 Activity 来获得该Activity中的功能。 为此,请为 Activity 创建 PendingIntent。调用 setContentIntent() 以将 PendingIntent 添加到通知。 3.现在,将要使用的扩展通知功能添加到通知。请记住,您添加的任何功能还必须在用户点击通知时启动的 Activity 中可用。

2.1.4 通知设计

material design

2.2 管理

2.2.1 更新 (update)

若上一个notification还未被清除,则发出一个相同ID的通知即可更新(覆盖)之前的通知。
nm.notify(NOTIFICATION_ID, nb); //即这里的 int NOTIFICATION_ID

Post a notification to be shown in the status bar. If a notification with
     * the same id has already been posted by your application and has not yet been canceled, it
     * will be replaced by the updated information.
public void notify(int id, Notification notification) //(1)
    {
        notify(null, id, notification); //最终还是走到了notifyAsUser
    }
Post a notification to be shown in the status bar. If a notification with
     * the same tag and id has already been posted by your application and has not yet been
     * canceled, it will be replaced by the updated information.
public void notify(String tag, int id, Notification notification) //(2)
    {
        notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
    }
复制代码

上面两个notify本质上都是notifyAsUser,不过(1)的TAG默认为null罢了。

2.2.2 清除(cancel)

· 点击通知栏的清除按钮,会清除所有可清除的通知
· 设置了 setAutoCancel() 或 FLAG_AUTO_CANCEL 的通知,点击该通知时会清除它
· 通过 NotificationManager 调用 cancel(int id) 方法清除指定 ID 的通知
· 通过 NotificationManager 调用 cancel(String tag, int id) 方法清除指定 TAG 和 ID 的通知
· 通过 NotificationManager 调用 cancelAll() 方法清除所有该应用之前发送的通知
上面有提到 notify(tag,id,notification) 中的 tag 可为 null,若设置了tag 和 id,则需要 cancel(String tag, int id)来清除,用 cancel(int id) 是不行滴。

2.3 进阶使用

2.3.1 启动Activity

Intent notifyIntent = new Intent(this, ResultActivity.class);
// Sets the Activity to start in a new, empty task
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Creates the PendingIntent
PendingIntent notifyPendingIntent = PendingIntent.getActivity( this, 0 , notifyIntent , PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);
复制代码

当点击notification时,会send notifyPendingIntent,上面的notifyPendingIntent是PendingIntent.getActivity,所以会相当于startActivity(notifyIntent)

/**
         * Supply a {@link PendingIntent} to send when the notification is clicked.
         * If you do not supply an intent, you can now add PendingIntents to individual
         * views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent
         * RemoteViews.setOnClickPendingIntent(int,PendingIntent)}.  Be sure to
         * read {@link Notification#contentIntent Notification.contentIntent} for
         * how to correctly use this.
         */
        public Builder setContentIntent(PendingIntent intent) {
            mContentIntent = intent;
            return this;
        }
复制代码

2.3.2 显示进度

平台的 ProgressBar 类实现中显示有进度指示器。
通过调用 setProgress(max, progress, false) 将进度栏添加到通知,然后发出通知;
递增 progress 并更新通知;
操作结束时, progress 应该等于 max;
调用 setProgress(0, 0, false)删除进度栏。

mNotifyManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setContentTitle("Picture Download")
    .setContentText("Download in progress")
    .setSmallIcon(R.drawable.ic_notification);
// Start a lengthy operation in a background thread
new Thread(
    new Runnable() {
        @Override
        public void run() {
            int incr;
            // Do the "lengthy" operation 20 times
            for (incr = 0; incr <= 100; incr+=5) {
                    // Sets the progress indicator to a max value, the
                    // current completion percentage, and "determinate"
                    // state
                    mBuilder.setProgress(100, incr, false);
                    // Displays the progress bar for the first time.
                    mNotifyManager.notify(0, mBuilder.build());
                        // Sleeps the thread, simulating an operation
                        // that takes time
                        try {
                            // Sleep for 5 seconds
                            Thread.sleep(5*1000);
                        } catch (InterruptedException e) {
                            Log.d(TAG, "sleep failure");
                        }
            }
            // When the loop is finished, updates the notification
            mBuilder.setContentText("Download complete")
            // Removes the progress bar
                    .setProgress(0,0,false);
            mNotifyManager.notify(ID, mBuilder.build());
        }
    }
// Starts the thread by calling the run() method in its Runnable
).start();
复制代码

2.3.3 扩展布局

2.3.4 浮动通知

对于 Android 5.0(API 级别 21),当设备处于活动状态时(即,设备未锁定且其屏幕已打开),通知可以显示在小型浮动窗口中(也称为“浮动通知”)。 这些通知看上去类似于精简版的通知,只是浮动通知还显示操作按钮。 用户可以在不离开当前应用的情况下处理或清除浮动通知。

可能触发浮动通知的条件示例包括: 用户的 Activity 处于全屏模式中(应用使用 fullScreenIntent),
或者通知具有较高的优先级并使用铃声或振动(在API26之后,较高的优先级表示Channel级别在IMPORTANCE_HIGH=4)

PS:目前来看想要发出浮动通知,可以设置 IMPORTANCE_HIGH = 4(oreo) 或者 setPriority(PRIORITY_MAX) 下面截取部分notificationChannel和Priority的对比。详细会在后面的2.3.9说明。

    /**
     * Default notification importance: shows everywhere, makes noise, but does not visually
     * intrude.
     */
    public static final int IMPORTANCE_DEFAULT = 3; //注意does not visually
    /**
     * Higher notification importance: shows everywhere, makes noise and peeks. May use full screen
     * intents.
     */
    public static final int IMPORTANCE_HIGH = 4;
    /**
     * Unused.
     */
    public static final int IMPORTANCE_MAX = 5; //还未使用
复制代码
    /**
     * Highest {@link #priority}, for your application's most important items that require the
     * user s prompt attention or input.
     *
     * @deprecated use {@link NotificationManager#IMPORTANCE_HIGH} instead.使用IMPORTANCE_HIGH代替
     */
    @Deprecated
    public static final int PRIORITY_MAX = 2;
复制代码

2.3.5 锁屏通知

2.3.5.1 可见性

要在 Android 5.0 系统的锁定屏幕上显示媒体播放控件,调用 setVisibility() 并指定以下值之一来控制通知在锁屏上的显示:
VISIBILITY_PUBLIC 显示通知的完整内容。
VISIBILITY_SECRET 不会在锁定屏幕上显示此通知的任何部分。
VISIBILITY_PRIVATE 显示通知图标和内容标题等基本信息,但是隐藏通知的完整内容。

设置 VISIBILITY_PRIVATE 后,您还可以提供其中隐藏了某些详细信息的替换版本通知内容。例如,短信 应用可能会显示一条通知,指出“您有 3 条新短信”,但是隐藏了短信内容和发件人。要提供此替换版本的通知,请先使用 NotificationCompat.Builder 创建替换通知。创建专用通知对象时,请通过 setPublicVersion() 方法为其附加替换通知。

2.3.5.2 多媒体播放

将 Notification.MediaStyle 模板与 addAction()方法结合使用,后者可将操作转换为可点击的图标。
这是官方给出的示例:

Notification notification = new Notification.Builder(context)
    // Show controls on lock screen even when user hides sensitive content.
    .setVisibility(Notification.VISIBILITY_PUBLIC)
    .setSmallIcon(R.drawable.ic_stat_player)
    // Add media control buttons that invoke intents in your media service
    .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
    .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
    .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
    // Apply the media style template
    .setStyle(new Notification.MediaStyle()
    .setShowActionsInCompactView(1 /* #1: pause button */)
    .setMediaSession(mMediaSession.getSessionToken())
    .setContentTitle("Wonderful music")
    .setContentText("My Awesome Band")
    .setLargeIcon(albumArtBitmap)
    .build();
复制代码

这是AOSP中music的实例:

notificationBuilder.setStyle(new Notification.MediaStyle().setShowActionsInCompactView(playPauseButtonPosition) // show only play/pause in
                                  // compact view
                .setMediaSession(mSessionToken))
                .setColor(mNotificationColor)
                .setSmallIcon(R.drawable.ic_notification)
                .setVisibility(Notification.VISIBILITY_PUBLIC)
                .setUsesChronometer(true)
                .setContentIntent(createContentIntent())
                .setContentTitle(description.getTitle())
                .setContentText(description.getSubtitle())
                .setLargeIcon(art);
复制代码

2.3.7 自定义布局

2.3.8 创建一组通知

官方

2.3.9 创建和管理通知渠道

Notification Channel 是在oreo上新加入的。
Starting in Android 8.0 (API level 26), all notifications must be assigned to a channel. For each channel, you can set the visual and auditory behavior that is applied to all notifications in that channel. Then, users can change these settings and decide which notification channels from your app should be intrusive or visible at all.
官方说明
Table 1. Channel importance levels

User-visible importance level Importance (Android 8.0 and higher) Priority (Android 7.1 and lower)
Urgent Makes a sound and appears as a heads-up notification IMPORTANCE_HIGH PRIORITY_HIGH or PRIORITY_MAX
High Makes a sound IMPORTANCE_DEFAULT PRIORITY_DEFAULT
Medium No sound IMPORTANCE_LOW PRIORITY_LOW
Low No sound and does not appear in the status bar IMPORTANCE_MIN PRIORITY_MIN

下面用google时钟三张截图来说明下: 应用设置的应用通知:

应用设置的应用通知
选择“错过的时钟”通知类别(Channel):
选择“错过的时钟”通知类别
选择重要程度(importance level):
选择重要程度
这样用户可以根据个人需要自定义通知级别。

源码中的实例
在 Android 7.1 and lower 使用的是 Priority
下面是AOSP上的DeskClock某个通知的构建,并未适配最新的通知渠道,可以看到构造方法被弃用
'Builder(android.content.Context)' is deprecated less... (Ctrl+F1) This inspection reports where deprecated code is used in the specified inspection scope.
使用了 setPriority(NotificationCompat.PRIORITY_HIGH) 来设置优先级。

这时的DeskClock目标版本不能高于25,否则会翻车。如果去AOSP的gerrit上看的话,2017.11.29 deskclock 有一笔修改就是Lock targetSdkVersion to 25。也算是简单的规避了API26及以后会发生的问题。
不过后面还有2笔修改是加上了notification channel的, Adding Notification Channel这一笔修改算是非常好的了,不过存在一些细节问题。 简单修改下即可

//NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
  NotificationCompat.Builder builder = new NotificationCompat.Builder(context, HIGH_NOTIFICATION)
复制代码

这时只是添加了一个channel,看上图,它的优先级还是 PRIORITY_HIGH, 若想变更 importance level ,稍作修改

NotificationManager nm = context.getSystemService(NotificationManager.class);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_ALARM,context.getString(R.string.default_label),NotificationManager.IMPORTANCE_DEFAULT);
nm.createNotificationChannel(channel);
复制代码

emmm才发现还有一个 Create a notification channel group, 稍微看了下,大概意思是为设备上不同用户做细分。还没发现哪里用到了这个,先放一放~~
这是示例代码:

// The id of the group.
String groupId = "my_group_01";
// The user-visible name of the group.
CharSequence groupName = getString(R.string.group_name);
NotificationManager mNotificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannelGroup(new NotificationChannelGroup(group_id, group_name));
复制代码

3 一个DEMO

结合了上面的内容,写的一个demo, 凑合看的一个demo