對於通知欄的使用,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_它們都是定義的常量:
總結
以上是我對通知欄相關使用或自定義方式的總結,這塊也很簡單,重點關注是RemoteViews和PendingIntent的知識點的認識和理解。