從0開始認識android(二十二):最全notification詳解
這裡涉及到的是v4支援包中的通知API,因為這些API能將一些比較新的特性相容到4.0版本的裝置,所以,我們第一步要做的是為專案新增v4包依賴:
implementation 'com.android.support:support-compat:26.0.0'
當然,如果你的專案中已經依賴了其他以com.android.support開頭的庫的話,就可以不用再新增上面的依賴了。
一、建立通知 一個基本的通知至少必須有三個部分:小圖示,通知標題和通知的內容,如下:
NotificationCompat.Builder mNotifyBuilder= new NotificationCompat.Builder(this, App.CHANNEL_ID) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("我是通知標題") .setContentText("我是通知內容:" + text) //8.0以下的優先順序直接在這裡設定,如果是8.0及以上的則在notification channel中設定 .setPriority(NotificationCompat.PRIORITY_DEFAULT); NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //這裡的1010為當前這個通知的id,之後我們可以根據id對這個通知進行其他操作,如更新、刪除等 mNotificationManager.notify(1010, mNotifyBuilder.build());
在上面的程式碼中,可以看到,構造器中有一個CHANNEL_ID,其實這個通道ID是8.0以上的系統要求的,8.0以下的裝置可以忽略,所以,我們需要在程式開始執行的時候就用這個CHANNEL_ID向系統註冊一個我們自己的通知通道,程式碼如下:
public class App extends Application { private static App instance; public static final String CHANNEL_ID="com.jackbear.notificationtimer"; @Override public void onCreate() { super.onCreate(); instance=this; createNotificationChannel(); } private void createNotificationChannel() { //NotificationChannel這個類只存在於8.0以上的系統,所以需要判斷一下 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = getString(R.string.notification_channel_name); String description = getString(R.string.notification_channel_description); //配置這個通道的相關資訊和通知的優先順序 int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); //是否需要呼吸燈提示 channel.enableLights(true); //呼吸燈顏色 // channel.setLightColor(); //是否需要振動提示 channel.enableVibration(true); //振動模式 // channel.setVibrationPattern(); //上述資訊一旦註冊,後續的更改都無效了 NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } }
8.0以上的裝置一定要註冊通道,否則無法顯示通知!!!
二、點選通知內容開啟某個activity 要開啟的activity分兩種型別來判斷,根據不同型別的activity來選擇對應的開啟方式。 特殊的activity:即在你的APP裡面沒有入口,只能通過通知開啟的那些activity。針對這類activity,谷歌建議我們開啟後,點選返回按鈕時,直接返回到手機桌面,哪怕點選通知之前,我們的app為當前程序,點選通知打開了activity後,按返回按鈕會直接退到桌面,而不是我們的APP。所以我們主要用以下方式來開啟這類activity: 首先在清單檔案中為目標activity配置android:taskAffinity=""屬性使其不屬於任何任務棧和 android:excludeFromRecents="true"屬性,禁止其出現在 最近應用 列表中:
<activity android:name="com.CardviewTestActivity"
android:taskAffinity=""
android:excludeFromRecents="true"
/>
為Intent設定如下Flags,將 Activity 設定為在新的空任務中啟動:
Intent resultIntent = new Intent(this, CardviewTestActivity.class);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 101,
resultIntent, PendingIntent
.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(resultPendingIntent);
非特殊的activity:即在我們的APP裡面有入口的activity,針對這類activity,我們可以為其指定一個上級介面,當我們點選通知開啟這類activity時,返回的是為它指定的這個上級介面,而非直接退到手機桌面。 在清單檔案中為目標activity配置android:parentActivityName屬性指定上級介面:
<activity android:name="com.CardviewTestActivity"
android:parentActivityName=".MainActivity"
>
<!--相容4.1以下的裝置-->
<meta-data android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"/>
</activity>
通過TaskStackBuilder為目標activity建立一個新的任務棧:
Intent resultIntent = new Intent(this, CardviewTestActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(resultPendingIntent);
三、新增操作按鈕 通知允許我們通過addAction方法新增最多三個操作按鈕,每個操作也都是通過延期意圖來定義:
Intent soonzIntent=new Intent(this,TestBroadcastReceiver.class);
soonzIntent.setAction("app.action.soonz");
soonzIntent.putExtra("notificationId",App.NOTIFICATION_TEST_ID);
PendingIntent broadcast = PendingIntent.getBroadcast(this, 0,
soonzIntent, 0);
mNotifyBuilder.addAction(R.drawable.notify,"稍後提醒",broadcast);
mNotifyBuilder.addAction(R.drawable.notify,"按鈕2",broadcast);
mNotifyBuilder.addAction(R.drawable.notify,"按鈕3",broadcast);
mNotifyBuilder.addAction(R.drawable.notify, "按鈕4", broadcast);
四、7.0裝置在通知上直接輸入文字 相應的字串資源:
<string name="reply_label">輸入回覆內容</string>
<string name="label">回覆</string>
構建可以回覆文字的通知:
String replyLabel = getResources().getString(R.string.reply_label);
//KEY_TEXT_REPLY為儲存輸入文字時的key
RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY)
//通知面板中輸入框的hint文字
.setLabel(replyLabel)
.build();
//使用者點選通知面板上的傳送按鈕後傳送的意圖,這裡是將文字廣播給action為
//app.action.reply的廣播接受者TestBroadcastReceiver
Intent replyIntent = new Intent();
replyIntent.setAction("app.action.reply");
replyIntent.putExtra("notificationId", App.NOTIFICATION_TEST_ID);
replyIntent.putExtra(NOTIFY_CONTENT,text);
PendingIntent replyPendingIntent =
PendingIntent.getBroadcast(getApplicationContext(),
618, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//label為通知面板中的回覆按鈕文字
NotificationCompat.Action replyAction =
new NotificationCompat.Action.Builder(R.drawable.send,
getString(R.string.label), replyPendingIntent)
.addRemoteInput(remoteInput)
.build();
mNotifyBuilder.addAction(replyAction);
在廣播接收器中獲取使用者輸入的回覆內容:
String action = intent.getAction();
int notificationId = intent.getIntExtra("notificationId", -1);
if (action.equals("app.action.reply")) {
String notifyContent = intent.getStringExtra(NOTIFY_CONTENT);
//隱藏通知面板中的回覆按鈕
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(context, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setContentTitle("我是通知標題")
.setContentText("我是通知內容:" + notifyContent)
//8.0以下的優先順序直接在這裡設定,如果是8.0及以上的則在notification channel中設定
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(notificationId, mNotifyBuilder.build());
//獲取在通知面板中輸入的內容
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
CharSequence messageReplied = remoteInput.getCharSequence(KEY_TEXT_REPLY);
}
//TODO 獲取到輸入內容後傳送邏輯
}
對應的通知面板介面:
五、設定通知類別 setCategory()函式可以將通知設定為一個系統級的類別,當手機處於勿擾模式時,系統會根據你所設定的類別來決定是否顯示你的通知,一般有五種類別,分別是: CATEGORY_ALARM: 警報或計時器通知。 CATEGORY_REMINDER: 上次通知使用者決定稍後提醒的通知。 CATEGORY_EVENT: 日曆事件的通知。 CATEGORY_CALL: 來電等類似的通知。
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setContentTitle("我是通知標題")
.setContentText("我是通知內容")
.setCategory(NotificationCompat.CATEGORY_ALARM);
六、設定是否鎖屏顯示及顯示水平 可以通過 setVisibility()函式設定通知在鎖屏介面上的顯示水平,有三種顯示水平: VISIBILITY_PUBLIC: 顯示通知小圖示、通知標題、全部內容。 VISIBILITY_PRIVATE: 只顯示通知小圖示、標題,隱藏全部內容。 VISIBILITY_SECRET: 不顯示該條通知。
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setContentTitle("我是通知標題")
.setContentText("我是通知內容:")
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
七、移除通知 移除方式主要有以下幾種: setAutoCancel(): 建立通知時呼叫該方法,當用戶點選該條通知後,通知自動消失。 setTimeoutAfter(): 建立通知時傳入對應的時間,過了這個時間自動消失,當然,在這個時間之前你也可以通過其他方式移除 cancel() : 傳入對應通知的id,可移除已經顯示以及正在顯示的通知。 cancelAll(): 移除所有通知。
八、即時通訊的訊息通知模板 7.0開始,系統為即時通訊的軟體提供了一個訊息通知模板類NotificationCompat.MessagingStyle,如下程式碼:
long timeMillis = System.currentTimeMillis();
NotificationCompat.Builder notification = new NotificationCompat.Builder(this,
App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
.setStyle(new NotificationCompat.MessagingStyle("我:")
//會話標題,群組會話可以用群名稱,沒有群名稱則顯示部分群成員名稱
.setConversationTitle("點餐群")
//第三個引數傳null即表示這條資訊是當前使用者發的,系統會自動替換為你在
//MessagingStyle中傳入的名稱
.addMessage("嗨", timeMillis, null)
.addMessage("什麼事?", timeMillis, "同事:")
.addMessage("沒什麼", timeMillis, null)
.addMessage("一起午餐怎麼樣?", timeMillis, "同事:"));
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
managerCompat.notify(0,notification.build());
除了文字資訊,也可以通過NotificationCompat.MessagingStyle.Message.setData()函式顯示圖片等多媒體檔案。 8.0開始,你還可以通過Notification.MessagingStyle.addHistoricMessage()方法向通知中插入歷史性的訊息。
九、顯示右側大圖和擴充套件大圖
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_notify_big);
//右側大圖
mNotifyBuilder.setLargeIcon(bitmap);
//擴充套件大圖
mNotifyBuilder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap));
十、顯示一段文字 通知的內容預設只能顯示一行,可以通過以下方式在通知上顯示一段文字:
mNotifyBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(
"我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字" +
"我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字" +
"我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字我是一大段文字"
));
十一、顯示多行獨立的文字 通知允許我們最多顯示6行文字,這裡的獨立是指每行文字都是單獨的,與其他行互相沒有意思上的聯絡,區別與上面的一段文字或即時通訊的歷史資訊。
NotificationCompat.InboxStyle inboxStyle =
new NotificationCompat.InboxStyle();
String[] events = new String[8];
// Sets a title for the Inbox in expanded layout
inboxStyle.setBigContentTitle("我是獨立行標題:");
// Moves events into the expanded layout
for (int i = 0; i < events.length; i++) {
inboxStyle.addLine(events[i] = "我是獨立行我是獨立行我是獨立行我是獨立行我是獨立行我是獨立行" + i);
}
// Moves the expanded layout object into the notification object.
mNotifyBuilder.setStyle(inboxStyle);
十二、將同一型別的通知整合到一個通知組裡 從7.0開始,我們可以將同一個應用的同類型的通知整合為一個通知組顯示,例如,你的電子郵件應用收到了多個不同的人給你發來的多封郵件,即時聊天應用收到了多個人給你發來了聊天資訊。此時,我們不必每收到一封郵件或一個資訊就去建立一個獨立的通知,反之,可以將這些通知整合為一個通知組來顯示。 以下為整合程式碼:
public static final String GROUP_NOTIFY_KEY="com.jackbear.notificationtimer.groupKey";
public void groupNotify(){
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.notify);
Notification summaryNotification =
new NotificationCompat.Builder(this, App.CHANNEL_ID)
.setContentTitle("歸納性通知標題")
//為支援7.0以下裝置設定通知歸納性通知的通知內容
.setContentText("您有新的資訊")
.setSmallIcon(R.drawable.notify)
//歸納性通知的相關資訊
.setStyle(new NotificationCompat.InboxStyle()
.addLine("我是擴充套件的獨立行文字1")
.addLine("我是擴充套件的獨立行文字2")
.setBigContentTitle("新資訊到了!")
.setSummaryText("[email protected]"))
//指定這個歸納性通知屬於哪個組
.setGroup(GROUP_NOTIFY_KEY)
//指定這個通知作為歸納性通知
.setGroupSummary(true)
.build();
for (int i = 0; i < 5; i++) {
NotificationCompat.Builder notify=new NotificationCompat.Builder(this,App.CHANNEL_ID)
.setContentTitle("子通知標題"+i)
.setContentText("子通知內容"+i)
.setSmallIcon(R.drawable.notify)
.setLargeIcon(bitmap)
.setGroup(GROUP_NOTIFY_KEY);
//子通知必須作為一個完整單獨的通知,所以每個子通知notify中的引數id不能相同
managerCompat.notify(i,notify.build());
}
//
managerCompat.notify(110,summaryNotification);
}
整合時的顯示樣式: 展開顯示時的樣式:
注意:按照谷歌的說法,7.0以上的裝置,只要為每個子通知呼叫setGroup函式設定同一分組,系統會給我們自動歸納顯示,不需要額外建立上述程式碼中的summaryNotification作為歸納性通知去歸納其他子通知,但 我在華為mate10(8.0)上不通過summaryNotification 的話還是沒辦法將子通知歸納到同一組,所以為了以防萬一,我們最好還是額外借助summaryNotification 來作為歸納性通知來歸納其他子通知。
十三、通知通道notification channel 文章開頭我們知道,8.0的系統要求我們必須向系統註冊通道,當我們建立一個通知時,需要傳入相應的通道id,然後這個通知就會走這個通道。我們可以根據重要性來將通道分為四種等級: Urgent: 該通道中的通知一般作為最重要的警報性通知出現,並伴有提示音。 High: 高重要性通道,走該通道的通知會有提示音。 Medium: 中等重要性通道,其通知不會有提示音。 Low: 非重要通道,其通知既不會有提示音,也不會出現在狀態列中,只有往下拉狀態列時才看到。 注意: 除了將通知繫結到對應的通道,我們還應該將通知設定為不同的種類,如本文第五部分中所述。 因此,我們的一個APP可以同時向系統註冊四種通道,最重要的通知我們讓其走Urgent通道,高重要性的通知走High通道,不重要的通知走Low通道,這樣,我們就可以在APP的設定介面中讓使用者去控制哪些型別的通知需要提示音、呼吸燈、振動等。我們只需要將對應通道的ID傳入一下intent,開啟通知管理介面讓使用者自己設定即可:
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, App.CHANNEL_ID);
startActivity(intent);
十四、自定義通知UI 自定義通知的UI,我們可以只自定義通知標題和通知內容的區域(不包括通知小圖示、時間戳、主題和操作按鈕所在的部分),也可以將整個通知區域全部替換為我們的自定義介面。需要注意的是:自定義的佈局高度是有限制的,摺疊時的佈局最高為64dp,擴充套件時的佈局最高為256dp, 高於這個高度,則會顯示不全。以下是隻自定義標題和內容區域佈局的程式碼:
RemoteViews collapseView=new RemoteViews(getPackageName(),R.layout.notification_collapse_view);
RemoteViews expandedView=new RemoteViews(getPackageName(),R.layout.notification_expanded_view);
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
//告知系統我們要自定義佈局
.setStyle(new NotificationCompat.DecoratedCustomViewStyle())
//摺疊顯示的自定義佈局
.setCustomContentView(collapseView)
//擴充套件顯示的自定義佈局
.setCustomBigContentView(expandedView)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setCategory(NotificationCompat.CATEGORY_ALARM)
//8.0以下的優先順序直接在這裡設定,如果是8.0及以上的則在notification channel中設定
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
以下為自定義整個通知區域佈局的程式碼,與上面相比,只需要將setStyle(new NotificationCompat.DecoratedCustomViewStyle())和.setCustomContentView(collapseView)註釋掉即可:
RemoteViews collapseView=new RemoteViews(getPackageName(),R.layout.notification_collapse_view);
RemoteViews expandedView=new RemoteViews(getPackageName(),R.layout.notification_expanded_view);
NotificationCompat.Builder mNotifyBuilder = new
NotificationCompat.Builder(this, App.CHANNEL_ID)
.setSmallIcon(R.drawable.notify)
//告知系統我們要自定義佈局
// .setStyle(new NotificationCompat.DecoratedCustomViewStyle())
//摺疊顯示的自定義佈局
// .setCustomContentView(collapseView)
//擴充套件顯示的自定義佈局
.setCustomBigContentView(expandedView)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setCategory(NotificationCompat.CATEGORY_ALARM)
//8.0以下的優先順序直接在這裡設定,如果是8.0及以上的則在notification channel中設定
.setPriority(NotificationCompat.PRIORITY_DEFAULT);