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_它們都是定義的常量:
android8.0通知渠道級別

總結

以上是我對通知欄相關使用或自定義方式的總結,這塊也很簡單,重點關注是RemoteViews和PendingIntent的知識點的認識和理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章