oreo上的notification詳解

黃燜雞米花發表於2018-06-23

前言

本來只想簡單寫下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錯誤提示,並且通知不會發出

oreo上的notification詳解

//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的例項:

oreo上的notification詳解

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) 來設定優先順序。

oreo上的notification詳解
這時的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

相關文章