1. 程式人生 > >Android 8.0 通知適配

Android 8.0 通知適配

下面說一下Android O (8.0)(API 26)通知的相關適配

一、分析

Android O 之前開啟一個App的設定的通知是這樣的

s_1

傳送一條通知通過下面程式碼
/*
* 簡單的傳送通知
*/
private void showNotification() {
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    Notification notification = new NotificationCompat.Builder(this)
            .setSmallIcon(R.drawable.push)
            .setContentTitle("title")
            .setContentText("content")
            .build();
    notificationManager.notify(1, notification);
}
在Android O 之前呼叫上面程式碼App是可以正常收到一條通知的,但是在Android O之後呼叫上面程式碼並不會收到通知,會打印出下面的log資訊
No Channel found for pkg=com.bill.notificationtest, channelId=null, id=1, tag=null, opPkg=com.bill.notificationtest, callingUid=10246, userId=0, incomingUserId=0, notificationUid=10246, notification=Notification(channel=null pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x0 color=0x00000000 vis=PRIVATE)
從Android8.0(API26)開始,Google 規定所有的通知必須分配一個渠道。每一個渠道,你都可以設定渠道中所有通知的視覺和聽覺行為。然後,使用者能夠隨意修改這些設定來決定通知的行為。

適配Android O 通知需要將App的targetSdkVersion修改為26以上,然後在Android 8.0以上的機型就可以使用,在Android 8.0以下的機型還是按照原有的規則。

下面看一下國內已經適配了Android O的App,下面為百度地圖和愛奇藝的設定通知頁,和上面頭條(未適配Android O)的對比一下,可以看到,百度地圖下面多了個類別,有百度地圖和未分類兩類,這兩個就是兩個渠道,其中"中"和"低"代表當前渠道訊息的重要等級,程式碼可以初始設定,使用者可以點進去幹預設定,並且可以單獨關閉某一個渠道的訊息,後面以使用者的設定為主。看下愛奇藝的設定下面東西更多了,有遊戲或應用下載、聊天訊息推送訊息等,這些是開發者建立的渠道組,組下面的是真正的渠道,開發者可以建立一些渠道組為渠道歸類,渠道組下面至少包含一個渠道,否則不顯示。其中渠道組不是必須的,但是渠道是必須要有的,沒有建立渠道組預設渠道放在一個叫類別的渠道組下,如百度地圖那樣。

在這裡插入圖片描述

二、適配

1、建立渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    String channelId = "channel_chat";
    String channelName = "新聊天訊息";
    int importance = NotificationManager.IMPORTANCE_HIGH;
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
    notificationManager.createNotificationChannel(channel);
}
建立渠道就上面幾行程式碼,可以寫在任何地方,需要在傳送通知前呼叫,一個渠道Id系統只會建立一次,以後不會重複建立。建立渠道首先需要判斷版本,如果不判斷,在Android 8.0 以前的機型呼叫將會找不到Api會造成崩潰。然後建立渠道需要三個必填引數,channelId是渠道Id,開發者自己定義,在當前App內保證和其他渠道資訊唯一就好了,後面傳送訊息也是根據渠道Id傳送的。channelName是渠道名稱,是給使用者看的,文件建議最長為40個字元,否則可能被截斷,例如上面百度地圖App裡的百度地圖和未分類。importance是渠道訊息的重要等級,如上面百度地圖的中和低,使用者可以修改等級,具體取值如下:

IMPORTANCE_HIGH:緊急。有提示音和震動,在狀態列顯示,並會懸浮到App上。 IMPORTANCE_DEFAULT:高。有提示音和震動,在狀態列顯示。 IMPORTANCE_LOW:中。沒有提示音和震動,在狀態列顯示。 IMPORTANCE_MIN:低。沒有提示音和震動,不在狀態列顯示,摺疊在狀態列二級選單中,下拉可以看到。 IMPORTANCE_NONE:關閉通知。

NotificationChannel 還有幾個設定項,一般都不修改,須在createNotificationChannel()方法前呼叫。如下:
/*
* 為當前渠道新增一個描述資訊,使用者在點選渠道進入詳情頁,會在最下面看到
* 文件提示最長為300個字元,否則可能被截斷
*/
setDescription(String description);

/*
* 設定此渠道所屬的渠道組,僅用於展示。即不可以向一個組傳送通知,可以刪除一組渠道。
*/
setGroup(String groupId);

/*
* 設定App在桌面上顯示Icon上是否顯示角標,true:顯示(default)
*/
setShowBadge(boolean showBadge);

/*
* 設定通知的提示音,預設系統的,使用者可修改
* 注意只有在重要等級IMPORTANCE_DEFAULT和IMPORTANCE_HIGH才有提示音
*/
setSound(Uri sound, AudioAttributes audioAttributes);

/*
* 設定通知的指示燈顏色,手機需支援
*/
setLightColor(int argb);

/*
* 設定通知的震動模式
*/
setVibrationPattern(long[] vibrationPattern);

/*
* 繞過免打擾模式
* 注:Only modifiable by the system and notification ranker.(只能被系統和通知服務修改)
*/
setBypassDnd(boolean bypassDnd);

/*
* 是否在鎖定螢幕上顯示通知
* 注:Only modifiable by the system and notification ranker.(只能被系統和通知服務修改)
*/
setLockscreenVisibility(int lockscreenVisibility);
呼叫上面程式碼後會渠道就會被建立成功,在設定介面如下:

s_4

2、傳送通知
/*
* 簡單的傳送通知
*/
private void showNotification() {
    String channelId = "channel_chat";
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    Notification notification = new NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.push)
            .setContentTitle("title")
            .setContentText("content")
            .build();
    notificationManager.notify(1, notification);
}
傳送通知只在建立Builder時多了一個channelId引數,在Android 8.0及以上機型會根據渠道Id傳送通知,Andoird 8.0以下會忽略。
3、建立渠道組
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    String groupId = "group_chat";
    String groupName = "聊天訊息";
    NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    NotificationChannelGroup group = new NotificationChannelGroup(groupId, groupName);
    notificationManager.createNotificationChannelGroup(group);

    String channelId = "channel_chat";
    String channelName = "新聊天訊息";
    int importance = NotificationManager.IMPORTANCE_HIGH;
    NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
    channel.setGroup(groupId);
    notificationManager.createNotificationChannel(channel);
}
建立渠道組只需要一個groupId必填引數,並保證在App內和其他渠道組唯一。通過上面程式碼就建立了一個叫聊天訊息的渠道組,並將新聊天訊息這個渠道放到聊天訊息渠道組裡。執行上面程式碼,檢視設定頁如下:

s_5

渠道組僅用於展示,傳送通知和上面一樣。
4、刪除渠道和組
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    notificationManager.deleteNotificationChannel(channelId);
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    notificationManager.deleteNotificationChannelGroup(groupId);
}
開發者可以刪除一個或一個渠道組(多個渠道),不過刪除之後會在設定頁顯示已刪除的渠道數,非常不美觀。謹慎刪除。

s_12

5、通知關閉
/*
* 判斷App通知是否開啟(總開關)
* true:開
*/
public boolean areNotificationsEnabled() {
    NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
    return notificationManagerCompat.areNotificationsEnabled();
}

/*
* 判斷當前渠道通知是否開啟
* true:開
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public boolean areChannelsEnabled(@NonNull String channelId) {
    NotificationChannel notificationChannel = notificationManager.getNotificationChannel(channelId);
    if (notificationChannel != null && notificationChannel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
        return false;
    }
    return true;
}

/*
* 跳轉到渠道設定頁
*/
public void gotoChannelSetting(@NonNull String channelId, @NonNull Context context) {
    Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
    intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
    intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
    context.startActivity(intent);
}
注:當通知總開關被關閉時即areNotificationsEnabled()返回false時,渠道開關即areChannelsEnabled("")可能是true。所以判斷渠道版本開關前應該先判斷總開關。

三、完整示例

1、在Activity的onCreate中新增如下程式碼
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // 聊天訊息
            String groupId_1 = "group_chat";
            String groupName_1 = "聊天訊息";
            NotificationChannelGroup group = new NotificationChannelGroup(groupId_1, groupName_1);
            notificationManager.createNotificationChannelGroup(group);

            String channelId1 = "channel_chat";
            String channelName1 = "新聊天訊息";
            int importance1 = NotificationManager.IMPORTANCE_HIGH;
            NotificationChannel channel1 = new NotificationChannel(channelId1, channelName1, importance1);
            channel1.setGroup(groupId_1);
            notificationManager.createNotificationChannel(channel1);

            // 下載訊息
            String groupId2 = "group_download";
            String groupName2 = "下載訊息";
            NotificationChannelGroup group2 = new NotificationChannelGroup(groupId2, groupName2);
            notificationManager.createNotificationChannelGroup(group2);

            String channelId_2_1 = "channel_download_complete";
            String channelName_2_1 = "下載完成";
            int importance_2_1 = NotificationManager.IMPORTANCE_LOW;
            NotificationChannel channel_2_1 = new NotificationChannel(channelId_2_1, channelName_2_1, importance_2_1);
            channel_2_1.setGroup(groupId2);
            notificationManager.createNotificationChannel(channel_2_1);

            String channelId_2_2 = "channel_download_error";
            String channelName_2_2 = "下載失敗";
            int importance_2_2 = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel_2_2 = new NotificationChannel(channelId_2_2, channelName_2_2, importance_2_2);
            channel_2_2.setGroup(groupId2);
            notificationManager.createNotificationChannel(channel_2_2);

            // 未分類
            String channelId_3 = "channel_other";
            String channelName_3 = "未分類";
            int importance_3 = NotificationManager.IMPORTANCE_MIN;
            NotificationChannel channel_3 = new NotificationChannel(channelId_3, channelName_3, importance_3);
            notificationManager.createNotificationChannel(channel_3);
        }
    }

2、傳送通知
    /**
     * 傳送通知
     *
     * @param channelId 渠道Id,按渠道傳送通知
     */
    private void sendNotification(@NonNull String channelId) {
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        Notification notification = new NotificationCompat.Builder(context, channelId)
                .setSmallIcon(R.drawable.push)
                .setContentTitle("title")
                .setContentText("content")
                .build();
        int notifyId = new Random().nextInt(50000);
        notificationManager.notify(notifyId, notification);
    }
3、截圖

s_10

在這裡插入圖片描述

四、程式碼封裝
上面使用重複程式碼太多了,不方便使用,下面簡單封裝一下
/**
 * Created by Bill on 2018/10/22.
 * 通知管理類
 */
public class NotifyManager {

    private Context context;
    private NotificationManager notificationManager;
    private Random random;

    public NotifyManager(@NonNull Context context) {
        this.context = context.getApplicationContext();
        init();
    }

    private void init() {
        notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        random = new Random();
    }

    /**
     * 建立渠道
     *
     * @param channelId   渠道Id
     * @param channelName 渠道名
     * @param importance  等級
     * @param description 渠道描述
     */
    public void createNotificationChannel(@NonNull String channelId, @NonNull String channelName, @ImportanceType int importance, @Nullable String description) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel(channelId, channelName, importance, description, null);
        }
    }

    /**
     * 建立渠道組和一個渠道
     *
     * @param groupId
     * @param groupName
     * @param channel
     */
    public void createNotificationGroupWithChannel(@NonNull String groupId, @Nullable String groupName, @NonNull ChannelEntity channel) {
        ArrayList<ChannelEntity> channelList = new ArrayList<>();
        channelList.add(channel);
        createNotificationGroupWithChannel(groupId, groupName, channelList);
    }

    /**
     * 建立渠道組和一組渠道
     *
     * @param groupId
     * @param groupName
     * @param channelList
     */
    public void createNotificationGroupWithChannel(@NonNull String groupId, @Nullable String groupName, @NonNull ArrayList<ChannelEntity> channelList) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            if (!TextUtils.isEmpty(groupId)) {
                createNotificationGroup(groupId, groupName);
            }

            for (ChannelEntity channel : channelList) {
                createNotificationChannel(channel.getChannelId(), channel.getChannelName(), channel.getImportance(), channel.getDescription(), groupId);
            }
        }
    }

    /**
     * 建立渠道,並建立組
     *
     * @param channelId
     * @param channelName
     * @param importance
     * @param description
     * @param groupId
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void createNotificationChannel(@NonNull String channelId, @NonNull String channelName, @ImportanceType int importance,
                                           @Nullable String description, @Nullable String groupId) {
        NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
        if (!TextUtils.isEmpty(description))
            channel.setDescription(description);
        if (!TextUtils.isEmpty(groupId))
            channel.setGroup(groupId);
        notificationManager.createNotificationChannel(channel);
    }

    /**
     * 建立渠道組
     *
     * @param groupId
     * @param groupName
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void createNotificationGroup(@NonNull String groupId, @Nullable String groupName) {
        NotificationChannelGroup group = new NotificationChannelGroup(groupId, groupName);
        notificationManager.createNotificationChannelGroup(group);
    }

    /**
     * 刪除渠道
     *
     * @param channelId
     */
    public void deleteNotificationChannel(@NonNull String channelId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationManager.deleteNotificationChannel(channelId);
        }
    }

    /**
     * 刪除組
     *
     * @param groupId
     */
    public void deleteNotificationChannelGroup(@NonNull String groupId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notificationManager.deleteNotificationChannelGroup(groupId);
        }
    }

    /**
     * 傳送通知
     *
     * @param notification 通知具體內容
     * @return 通知Id
     */
    public int notifyNotify(@NonNull Notification notification) {
        int notifyId = getRandomId();
        return notifyNotify(notifyId, notification);
    }

    /**
     * 傳送通知
     *
     * @param notifyId     通知Id
     * @param notification 通知具體內容
     * @return
     */
    public int notifyNotify(int notifyId, @NonNull Notification notification) {
        notificationManager.notify(notifyId, notification);
        return notifyId;
    }

    /**
     * 關閉狀態列通知的顯示
     *
     * @param notifyId 通知Id
     */
    public void cancelNotify(int notifyId) {
        notificationManager.cancel(notifyId);
    }

    /**
     * 預設設定,呼叫方可以新增和修改
     *
     * @param channelId
     * @return
     */
    public NotificationCompat.Builder getDefaultBuilder(@NonNull String channelId) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
        builder.setSmallIcon(R.drawable.push)
                .setColor(Color.parseColor("#E92110"));
        return builder;
    }

    /**
     * 檢查當前渠道的通知是否可用,Android O及以上版本呼叫
     * <p>
     * 注:areNotificationsEnabled()返回false時,即當前App通知被關時,此方法仍可能返回true,
     *
     * @param channelId 渠道Id
     * @return false:不可用
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    public boolean areChannelsEnabled(@NonNull String channelId) {
        NotificationChannel notificationChannel = notificationManager.getNotificationChannel(channelId);
        if (notificationChannel != null && notificationChannel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
            return false;
        }
        return true;
    }

    /**
     * 檢查通知是否可用
     *
     * @return false:不可用
     */
    public boolean areNotificationsEnabled() {
        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
        return notificationManagerCompat.areNotificationsEnabled();
    }

    /**
     * 調轉到渠道設定頁
     *
     * @param channelId
     */
    public void gotoChannelSetting(@NonNull String channelId) {
        Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
        intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
        intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
        context.startActivity(intent);
    }

    /**
     * Generate a random integer
     *
     * @return int, [0, 50000)
     */
    private int getRandomId() {
        return random.nextInt(50000);
    }

}
@IntDef({ImportanceType.IMPORTANCE_NONE,
        ImportanceType.IMPORTANCE_MIN,
        ImportanceType.IMPORTANCE_LOW,
        ImportanceType.IMPORTANCE_DEFAULT,
        ImportanceType.IMPORTANCE_HIGH})
public @interface ImportanceType {

    int IMPORTANCE_NONE = 0;
    int IMPORTANCE_MIN = 1;
    int IMPORTANCE_LOW = 2;
    int IMPORTANCE_DEFAULT = 3;
    int IMPORTANCE_HIGH = 4;
}

public class ChannelEntity {

    private String channelId;
    private String channelName;
    private int importance;
    private String description;

    public ChannelEntity(@NonNull String channelId, @NonNull String channelName, @ImportanceType int importance) {
        this.channelId = channelId;
        this.channelName = channelName;
        this.importance = importance;
    }

    public String getChannelId() {
        return channelId;
    }

    public String getChannelName() {
        return channelName;
    }

    public int getImportance() {
        return importance;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
呼叫如下:
    private NotifyManager notifyManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        notifyManager = new NotifyManager(this);

        ChannelEntity chatChannel = new ChannelEntity(Constants.CHANNEL_CHAT, "新聊天訊息", ImportanceType.IMPORTANCE_HIGH);
        chatChannel.setDescription("個人或群組發來的聊天訊息");
        notifyManager.createNotificationGroupWithChannel(Constants.GROUP_CHAT, "聊天訊息", chatChannel);

        ArrayList<ChannelEntity> channelEntityArrayList = new ArrayList<>();
        ChannelEntity downloadCompleteChannel = new ChannelEntity(Constants.CHANNEL_DOWNLOAD_COMPLETE, "下載完成", ImportanceType.IMPORTANCE_LOW);
        downloadCompleteChannel.setDescription("下載完成後通知欄顯示");
        channelEntityArrayList.add(downloadCompleteChannel);
        ChannelEntity downloadProgressChannel = new ChannelEntity(Constants.CHANNEL_DOWNLOAD_ERROR, "下載失敗", ImportanceType.IMPORTANCE_DEFAULT);
        downloadProgressChannel.setDescription("下載出現問題,下載失敗");
        channelEntityArrayList.add(downloadProgressChannel);
        notifyManager.createNotificationGroupWithChannel(Constants.GROUP_DOWNLOAD, "下載訊息", channelEntityArrayList);

        notifyManager.createNotificationChannel(Constants.CHANNEL_OTHER, "未分類", ImportanceType.IMPORTANCE_MIN, null);
    }
傳送通知:
    /**
    * 傳送通知
    *
    * @param channelId 渠道Id,按渠道傳送通知
     */
    private void sendNotification() {
        NotificationCompat.Builder builder = notifyManager.getDefaultBuilder(Constants.CHANNEL_DOWNLOAD_COMPLETE);
        builder.setContentTitle("下載完成");
        builder.setContentText("下載完成,可在我的下載中檢視");
        Notification notification = builder.build();
        notifyManager.notifyNotify(notification);
    }

完整程式碼

參考連結: